Documentation
Everything you need to know about installing, configuring, and using SageFs — the live F# development environment with instant feedback.
Installation
SageFs is distributed as a .NET global tool. You need the .NET 10 SDK installed.
dotnet tool install --global SageFs
Verify the installation:
SageFs --version
Quick Start
Start the daemon — it runs bare and waits for your editor to connect:
# Start the daemon (bare — no project flag needed)
SageFs
# Then create a session for your project from your editor, MCP client, or dashboard
# Example target: path/to/MyProject.fsproj
SageFs launches a daemon that listens for client connections. When your editor
(Neovim, VS Code, Visual Studio) creates a session, the daemon auto-discovers your project,
pre-compiles dependencies, and starts an interactive session. Hit Alt+Enter to
evaluate code, or just save a file — the daemon watches for changes and hot-reloads automatically.
Startup Modes
SageFs operates in several modes depending on how you invoke it:
| Mode | What It Does |
|---|---|
SageFs |
Default (bare daemon). Starts the daemon with no project loaded. Clients (editors, AI agents) create sessions and the daemon auto-discovers projects from their working directory. |
SageFs tui |
Connect to an already running daemon with the terminal UI. Fails if no daemon is found. |
SageFs gui |
Connect to a running daemon with the Raylib GPU GUI — same features, rendered with GPU quads instead of ANSI. |
SageFs connect |
Attach a plain REPL to a running daemon. No TUI chrome — just stdin/stdout eval. |
SageFs status |
Print daemon info (PID, port, sessions, uptime) and exit. |
SageFs stop |
Gracefully stop the running daemon. |
Subcommands
| Subcommand | Description |
|---|---|
daemon | Start as background daemon (default when no subcommand given) |
worker | Internal: launched by the daemon to run an isolated FSI worker process |
connect | Plain REPL connection to a running daemon |
tui | Terminal UI client — full interactive TUI connected to the daemon |
gui | Raylib GPU GUI client — same features, GPU-rendered window |
stop | Stop the running daemon |
status | Show daemon info and exit |
Source: SageFs/Program.fs
Flags
| Flag | Description |
|---|---|
--dir <path> | Set the working directory |
--no-watch | Disable file watching — no auto-reload on save |
--no-resume | Don't restore previous session state on startup |
--bare | Minimal startup — skip warmup, no project pre-loading |
--prune | Clean up stale session data before starting |
--persist | Enable persistent event store for session history |
--mcp-port <n> | Set custom MCP server port (default: 37749) |
--supervised | Run under watchdog — auto-restart on crash |
-r:<file> | Add an assembly reference |
--load <file> | Load and execute an F# script file on startup |
--use <file> | Use an F# script file (like --load but in fresh scope) |
--lib <dirs> | Add library search directories (comma-separated) |
-h, --help | Show help and exit |
-v, --version | Show version and exit |
Source: SageFs.Core/Args.fs
Environment Variables
| Variable | Description | Default |
|---|---|---|
SageFs_MCP_PORT | Override the MCP server port | 37749 |
SAGEFS_BIND_HOST | Bind address for the daemon HTTP server | localhost |
MCP — Model Context Protocol
SageFs exposes an MCP server that allows AI agents (Claude, GitHub Copilot, etc.) to interact with live F# sessions. The MCP server runs on port 37749 by default and serves tools over Server-Sent Events (SSE).
The available tools change based on session state — you can't evaluate code while the session is still warming up, for example. This is by design: it prevents race conditions and gives agents clear feedback about what's possible at any moment.
Source: SageFs.Core/Mcp.fs,
SageFs.Core/Affordances.fs
MCP Tool Reference (30 tools)
| Tool | Description |
|---|---|
send_fsharp_code |
Execute F# code in the live session. Each ;; is an isolated transaction — partial progress survives failures. Returns evaluation output or errors. |
check_fsharp_code |
Type-check a code snippet against the current FSI session state without executing it. Returns compiler diagnostics. Note: runs in the active session context — open statements from prior send_fsharp_code calls are in scope, but project namespaces are not automatically opened. "Type X is not defined" means a missing open, not a real bug. |
load_fsharp_script |
Load and execute an .fsx file. Each statement runs independently, so partial progress is preserved if a statement fails. |
get_completions |
Code completions at a cursor position. Returns available types, functions, and members from the loaded project and all referenced assemblies. |
explore_namespace |
Browse types, functions, and sub-namespaces within a given .NET namespace. No documentation needed — explore the type system interactively. |
explore_type |
View members, constructors, and properties of a specific type. Provide the fully-qualified name (e.g., System.String). |
run_tests |
Execute tests by name pattern, category (unit / integration / property / browser / benchmark / architecture), or run all. Waits up to 15 s for any in-progress hot reload to complete before running, so results always reflect the latest code. Default timeout: 30 s. |
get_live_test_status |
Query the current state of all discovered tests — passing, failing, stale, running. Optionally filter by source file. |
get_fsi_status |
Session health check: loaded projects, session statistics, available capabilities, and current state. |
get_startup_info |
Detailed startup information: loaded projects, enabled features, command-line arguments. |
get_recent_fsi_events |
Recent FSI events (evaluations, errors, script loads) with timestamps. Default: last 10 events. |
get_test_trace |
Pipeline diagnostics: enabled state, provider list, run policies, and per-test timing for finding bottlenecks. |
reset_fsi_session |
Soft reset: clears all user-defined types and values but keeps the session alive. Project namespaces are re-warmed. |
hard_reset_fsi_session |
Hard reset: disposes the session, releases DLL locks, optionally rebuilds the project, creates a fresh session. Use rebuild=true after editing .fs files. |
cancel_eval |
Cancel a running evaluation. Use when an eval is stuck or taking too long. |
set_run_policy |
Control when a test category auto-runs. every — on every hot reload that touches covered symbols (default for unit). save — only on explicit file save. demand — never automatic, only via run_tests (default for integration/browser). disabled — excluded even from run_tests. |
enable_live_testing |
Turn on automatic test execution after hot reload. |
disable_live_testing |
Turn off automatic test execution. |
get_available_projects |
Discover .fsproj and .sln/.slnx files in the working directory. |
create_session |
Create a new FSI session with specified project(s). Each session runs in its own worker process. |
list_sessions |
List all active FSI sessions with metadata: session ID, projects, state, working directory, last activity. |
switch_session |
Switch the active session. Subsequent tool calls route to the selected session. |
stop_session |
Stop an active FSI session. The worker process is gracefully shut down. |
get_elm_state |
Get the current Elm model rendered as regions — shows editor content, output, diagnostics, sessions. |
visualize_domain_model |
Visualize a discriminated union type as a state machine diagram. Provide a fully-qualified DU type name (e.g., MyNamespace.OrderState). Returns ASCII art diagram plus case names, fields, and entry/terminal state classification. |
set_test_timeouts |
Configure per-test and global-run timeout limits. Affects both automatic and explicit test runs. Call with no arguments to see current values. Defaults: 5 s per test, 120 s total run. |
explain_test_run |
Explain why a specific test was selected to run. Shows trigger reason (SymbolCoverage, NewTest, ExplicitRun, or DepGraphFallback), which changed symbols cover it, duration from last run, and flaky status. Substring match on test name. |
query_test_coverage |
Return all tests that transitively cover a given symbol, with their last result status. Symbol must be fully-qualified (e.g., MyModule.myFunction). Useful before refactoring: see which tests protect this code path. |
get_file_coverage |
Per-line coverage data for a source file. Each line shows which tests cover it and its health: AllPassing (all covering tests pass), SomeFailing (at least one covering test is failing), or NoData (no tests found). Accepts full path or partial filename. |
explain_test_failure |
Enriched failure context for a test that recently transitioned Passed→Failed. Shows time since last pass, causal changes (which symbols/files changed between the last passing and first failing run), and property violation counterexamples for FsCheck tests. |
Session State Machine
The MCP tools available to an agent depend on the session's current state. This prevents race conditions — you can't evaluate code while the session is still loading your project.
| State | Available Tools |
|---|---|
| Uninitialized | get_fsi_status |
| WarmingUp | get_fsi_status, get_recent_fsi_events |
| Ready | send_fsharp_code, load_fsharp_script, check_fsharp_code, get_completions, reset_fsi_session, hard_reset_fsi_session, cancel_eval, get_fsi_status, get_startup_info, get_recent_fsi_events, explore_namespace, explore_type, visualize_domain_model, run_tests, get_live_test_status, get_test_trace, set_run_policy, set_test_timeouts, enable_live_testing, disable_live_testing, explain_test_run, explain_test_failure, query_test_coverage, get_file_coverage, get_available_projects, create_session, list_sessions, switch_session, stop_session, get_elm_state |
| Evaluating | cancel_eval, get_fsi_status, get_recent_fsi_events, get_completions, check_fsharp_code |
| Faulted | get_fsi_status, get_recent_fsi_events, reset_fsi_session, hard_reset_fsi_session |
Daemon & Workers
SageFs uses a daemon + worker architecture. When you start SageFs, a single daemon process launches and manages everything: HTTP endpoints, SSE subscriptions, MCP server, session routing. Actual F# evaluation happens in separate worker processes — one per session.
Why separate processes?
- Crash isolation — a worker crash doesn't take down the daemon or other sessions
- DLL locking — each worker has its own assembly load context via shadow copying
- Parallelism — multiple sessions can evaluate code concurrently
Worker Protocol
The daemon communicates with workers over HTTP using a typed message protocol defined in
WorkerProtocol.fs.
Each message is a discriminated union case — evaluate, check types, get completions, reset, etc.
Standby Pool
To eliminate cold-start delays, the daemon maintains a standby pool of pre-warmed workers
(StandbyPool.fs).
When you reset a session, a warm standby swaps in instantly — your project is already compiled and loaded.
The pool auto-replenishes when .fsproj changes are detected.
Crash Recovery
The Watchdog
monitors every worker. If one crashes, it auto-restarts with exponential backoff. State is
State is persisted to binary manifests (EventStore.fs),
so a recovered session resumes where it left off.
Elm Architecture
All state management follows the Elm architecture (TEA — The Elm Architecture):
type Update = Msg → Model → Model * Effect list
Every event — keystrokes, file changes, test results, SSE messages, MCP requests — dispatches
through a pure update function. No shared mutable state. No locks. Side effects
execute after the model updates. The UI is always a pure function of the current model.
Key files:
ElmLoop.fs— generic event loop implementationSageFsApp.fs— main app wiring: routes messages to featuresElmDaemon.fs— daemon-mode loop with SSE and HTTPAppState.fs— the single immutable model record
Rendering Pipeline
SageFs has two UI frontends — a terminal TUI and a Raylib GPU GUI — that share the exact same rendering pipeline:
ElmModel → render → RenderRegion list → Screen.draw → Cell[,] → Backend.emit
├── AnsiEmitter (TUI)
└── RaylibEmitter (GUI)
The shared Cell[,] grid is a 2D array of characters with colors and attributes.
All layout, drawing, and pane composition happens at this abstract level
(TerminalUI.fs).
The TUI backend emits ANSI escape codes; the Raylib backend draws GPU quads.
Same logic, pixel-perfect parity.
Live Testing
SageFs discovers and runs tests continuously as you code. The three-speed pipeline:
- Discovery (~50ms) —
TestTreeSitter.fsfinds test functions using Tree-sitter, even in code that won't compile - Dependency Graph (~350ms) — the F# Compiler Service builds a graph of what changed and what depends on it
- Execution — only affected tests run via
LiveTestingExecutors.fs
Run Policies
Tests are categorized, and each category has a configurable run policy:
| Policy | When Tests Run |
|---|---|
every | On every change (keystroke-level). Default for unit tests. |
save | On file save only. Good for integration tests. |
demand | Manual trigger only. For slow browser/benchmark tests. |
disabled | Never auto-run. |
Hot Reload
When you save an .fs file, SageFs patches method pointers at runtime — no IL instrumentation,
no process restart. Changed functions are live in ~100ms. The pipeline:
FileWatcher.fsdetects the change- Only the changed file recompiles (F# Compiler Services incremental compilation)
HotReloading.fspatches the method table- Live testing pipeline re-runs affected tests
- All connected editors receive updated results via SSE
Neovim Plugin
The Neovim plugin (sagefs.nvim)
is a first-class client with 38 Lua modules and 1142 tests. It connects to the running daemon
via SSE and provides:
- Cell evaluation — send code to the daemon with
Alt+Enter - Inline results — evaluation output displayed as virtual text next to your code
- Gutter signs — green/red marks for test pass/fail status on each line
- Live test panel — floating window showing all tests and their current state
- Coverage gutter — highlights which lines are exercised by passing tests
- Type explorer — browse .NET namespaces and types from within Neovim
- Session management — create, switch, reset sessions via commands
- Code completions — powered by the daemon's F# Compiler Services integration
- Daemon lifecycle — auto-detects or starts the daemon
VS Code Extension
The VS Code extension lives in sagefs-vscode/
and is written in F# compiled to JavaScript via Fable.
It manages the daemon lifecycle and connects to it for real-time feedback.
Visual Studio Extension
The Visual Studio extension lives in sagefs-vs/.
It uses the VS Extensibility SDK (net8.0-windows8.0) with a C# shim
(SageFs.VisualStudio)
and F# core logic
(SageFs.VisualStudio.Core).
Built-in TUI
The default SageFs experience. A full-screen terminal interface with multiple panes:
editor, output, sessions, tests, diagnostics, and a status bar showing frame time.
Built on the shared Cell[,] rendering pipeline. Source:
SageFs/TuiClient.fs.
Raylib GPU GUI
Same features as the TUI but rendered in a GPU-accelerated window using
Raylib. Provides crisp font rendering and smooth
scrolling. Lives in SageFs.Gui/.
Launch with SageFs gui or SageFs --both to run TUI and GUI simultaneously.