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
Point SageFs at your F# project and start coding:
# Start with a project
SageFs --proj path/to/MyProject.fsproj
# Or with a solution
SageFs --sln path/to/MySolution.slnx
# Or auto-detect from current directory
SageFs
SageFs launches a daemon that loads your project, pre-compiles your dependencies,
and opens an interactive session. You'll see a full-screen TUI with an editor pane, output pane,
and test status. 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 --proj X.fsproj |
Default (daemon + TUI). Starts the daemon if not running, then opens the terminal UI. This is the most common invocation. |
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 |
|---|---|
--proj <file> | Load a specific .fsproj project file |
--sln <file> | Load a .sln or .slnx solution file |
--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
| 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 code without executing it. Returns compiler diagnostics (errors, warnings) — the AI equivalent of "does this compile?" |
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, or run all. Returns structured pass/fail results with timing data. |
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_pipeline_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 |
Set when tests in a category auto-run. Categories: unit, integration, browser, benchmark, architecture, property. Policies: every, save, demand, disabled. |
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. |
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 |
| 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
event-sourced (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 23 Lua modules and 669 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.