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
Tip: SageFs auto-detects existing daemons. If one is already running for your project, it connects instead of starting a new one.

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:

ModeWhat 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

SubcommandDescription
daemonStart as background daemon (default when no subcommand given)
workerInternal: launched by the daemon to run an isolated FSI worker process
connectPlain REPL connection to a running daemon
tuiTerminal UI client — full interactive TUI connected to the daemon
guiRaylib GPU GUI client — same features, GPU-rendered window
stopStop the running daemon
statusShow daemon info and exit

Source: SageFs/Program.fs

Flags

FlagDescription
--proj <file>Load a specific .fsproj project file
--sln <file>Load a .sln or .slnx solution file
--dir <path>Set the working directory
--no-watchDisable file watching — no auto-reload on save
--no-resumeDon't restore previous session state on startup
--bareMinimal startup — skip warmup, no project pre-loading
--pruneClean up stale session data before starting
--persistEnable persistent event store for session history
--mcp-port <n>Set custom MCP server port (default: 37749)
--supervisedRun 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, --helpShow help and exit
-v, --versionShow version and exit

Source: SageFs.Core/Args.fs

Environment Variables

VariableDescriptionDefault
SageFs_MCP_PORTOverride the MCP server port37749
SAGEFS_BIND_HOSTBind address for the daemon HTTP serverlocalhost

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

ToolDescription
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.

StateAvailable 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
Note for AI agents: If you get "Operation could not be completed due to earlier error," that means your code has a bug — the session is fine. Read the diagnostics, fix the code, and resubmit. Do not reset the 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?

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:

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:

  1. Discovery (~50ms) — TestTreeSitter.fs finds test functions using Tree-sitter, even in code that won't compile
  2. Dependency Graph (~350ms) — the F# Compiler Service builds a graph of what changed and what depends on it
  3. Execution — only affected tests run via LiveTestingExecutors.fs

Run Policies

Tests are categorized, and each category has a configurable run policy:

PolicyWhen Tests Run
everyOn every change (keystroke-level). Default for unit tests.
saveOn file save only. Good for integration tests.
demandManual trigger only. For slow browser/benchmark tests.
disabledNever 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:

  1. FileWatcher.fs detects the change
  2. Only the changed file recompiles (F# Compiler Services incremental compilation)
  3. HotReloading.fs patches the method table
  4. Live testing pipeline re-runs affected tests
  5. 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:

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.