Raw capture β€” raw/serf.md


url: https://github.com/prime-radiant-inc/serf date_fetched: 2026-07-05 backfilled: true


A non-interactive coding agent. Give it a prompt, it does the work.

Serf uses the LLM's native tool-calling to read files, write files, run commands, and search code in a loop until the work is complete. It supports OpenAI, Anthropic, and Google models.

For how the code is organized β€” modules, layout, and the build workspace β€” see docs/architecture.md. For the runtime contracts subagents, plugins, hooks, and helpers operate under (capability policy, lifecycle hooks, helper isolation, lineage), see docs/subagent-runtime-contracts.md; for background jobs and the job-control tools, see docs/job-control.md.

make buildBuild the standalone one-shot client (no agent loop):

make build-llmcallBuild the multi-session web orchestrator:

make build-hubInstall the latest release on Linux x64 or macOS Apple silicon:

curl -fsSL https://raw.githubusercontent.com/prime-radiant-inc/serf/main/install.sh | shThe release installer downloads the matching GitHub release archive, installs serf, serf-hub, serf-tui, and serf-doctor under ~/.local/share/serf/bin, and symlinks them into ~/.local/bin.

Install a specific tagged release:

curl -fsSL https://raw.githubusercontent.com/prime-radiant-inc/serf/main/install.sh | env SERF_INSTALL_VERSION=v1.2.3 shInstall the latest successful build from main:

curl -fsSL https://raw.githubusercontent.com/prime-radiant-inc/serf/main/install.sh | env SERF_INSTALL_VERSION=snapshot shOverride the install prefix, using sudo for system-owned paths:

curl -fsSL https://raw.githubusercontent.com/prime-radiant-inc/serf/main/install.sh | sudo env PREFIX=/usr/local shFrom a source checkout:

make installThis builds serf, serf-hub, serf-tui, and serf-doctor, installs the binaries under ~/.local/share/serf/bin, and symlinks them into ~/.local/bin. make install-home is an alias for the same layout.

System-style install:

sudo make install-systemThis uses the same layout under /usr/local by default. Override PREFIX to stage elsewhere.

The installer only installs binaries and symlinks. Runtime/config directories are created by Serf when the relevant binary runs.

Verify the installed commands with:

serf --version
serf-tui --help
serf-doctor --help

Upgrade installed binaries manually:

serf upgrade``serf upgrade follows the binary's install channel: release builds upgrade to the latest release, and snapshot builds upgrade to the latest successful main build. You can override the target with serf upgrade release, serf upgrade snapshot, or a tagged version such as serf upgrade v1.2.3. The TUI and web UI also expose a manual /upgrade command that calls through the hub and uses the same channel tracking.

On first use, Serf creates:

The user skill and plugin directories are extension roots; installing Serf does not automatically enable their contents. Add standalone skill paths to skills_dirs and plugin paths to plugin_dirs in ~/.serf/launch.toml, or pass them with the corresponding CLI flags for a single run. Plugin-contained skills live under that plugin and become available through the plugin path.

Provider credentials are not created by install. Configure them through the Hub or TUI credentials UI, ~/.serf/credentials.toml, provider environment variables such as OPENAI_API_KEY, or OpenAI OAuth. See docs/environment.md for the complete environment variable reference.

serf --model <provider/model> [flags] <prompt>

The prompt can be passed as arguments or piped via stdin:

# Prompt as arguments
serf --model openai/gpt-5.2 "add input validation to the signup handler"
# Prompt piped via stdin
echo "refactor auth to use JWT" | serf --model anthropic/claude-opus-4-6

This repo also includes llmcall, a minimal CLI wrapper around the unified llm library for single β€œthrowaway” calls.

Properties:

Build:

make build-llmcallExamples:

./llmcall --provider openai --model gpt-5-mini-2025-08-07 "Write a haiku about build pipelines."
# JSON mode: parses and re-prints as JSON (fails if output isn't valid JSON)
echo 'Return JSON: {"ok": true}' | ./llmcall --provider openai --model gpt-5-mini-2025-08-07 --format json
# JSON Schema mode: enforces + validates structured output
./llmcall --provider openai --model gpt-5-mini-2025-08-07 --schema /path/to/schema.json "Return an object matching the schema."

llmcall resolves provider/model from env if omitted:

Serf takes a provider-qualified model in one value: --model <provider/model>. Providers: openai, anthropic, google, minimax, openrouter, openrouter-anthropic, kimi, glm, ollama.

Use --model or set SERF_MODEL to the same provider/model format.

For local models via Ollama, see docs/ollama.md.

See docs/environment.md for the complete list. Common variables:

Variable Description
SERF_MODEL Default model as provider/model(used when--modelis omitted)
SERF_REASONING_EFFORT Default reasoning effort
SERF_PROVIDERS_CONFIG Path to providers.toml
OPENAI_API_KEY OpenAI API key
ANTHROPIC_API_KEY Anthropic API key
GEMINI_API_KEY Google Gemini API key
OPENROUTER_API_KEY OpenRouter API key
OLLAMA_BASE_URL Ollama base URL (default http://localhost:11434/v1)
OLLAMA_HOST Ollama host (Ollama's canonical env var; used if OLLAMA_BASE_URLis unset)
OLLAMA_API_KEY Optional API key for authenticated Ollama proxies / Ollama Cloud
Flag Description
--model <provider/model> LLM model identifier (required unless resuming an existing session)
--dir <path> Working directory (default: current directory)
--output-schema <json> Inline JSON Schema replacing the default communicate.outputschema
--verbose Emit NDJSON events to stderr (replaces human-readable output)
--resume <id> Resume a previous session by ID
--resume-with <id> Start a new prompt using a previous session's context
--resume-last Resume the most recent session
--list-sessions List saved sessions and exit

Pass --output-schema <json> to replace the communicate tool's output field schema with your own. The flag takes an inline JSON string (file paths are not supported).

serf --model openai/gpt-5.2 \
  --output-schema '{"type":"object","properties":{"plan":{"type":"string"}},"required":["plan"],"additionalProperties":false}' \
  "Draft a one-paragraph plan for fixing the flaky test."

The supplied schema replaces output wholesale β€” the default message/data/artifacts shape is removed. Provider-specific caveats:

stdout always receives only the final result text.

stderr shows progress in one of two modes:

Default (human-readable):

[model] gpt-5.2 (openai)
[tool] write_file {"file_path":"/tmp/test.txt","content":"he...
[tool] write_file: done
[assistant] I've created the file for you.
[thinking] (247 chars)
[usage] in=1234 out=567 total=1801

** --verbose (NDJSON):** Each event is a JSON object on one line, suitable for piping to

jq or log aggregation:serf --model openai/gpt-5.2 --verbose "fix the bug" 2>events.ndjsonNDJSON events include: SESSION_START, ASSISTANT_TEXT_END (with usage, reasoning, finish_reason), TOOL_CALL_START (with arguments), TOOL_CALL_END, WARNING, ERROR, and others.

Serf auto-saves session state under ${XDG_STATE_HOME:-~/.local/state}/serf/projects/<project-hash>/sessions/ after each assistant turn. This enables resuming interrupted work.

# List saved sessions
serf --list-sessions
# Resume the most recent session (provider and model from the original session are used)
serf --resume-last
# Resume a specific session
serf --resume 01JTEST000000000000000001
# New prompt, but carry forward a previous session's conversation context
serf --model openai/gpt-5.2 --resume-with 01JTEST000000000000000001 "now add tests"

When resuming, the provider and model from the original session are used by default. You can override them with --model <provider/model>.

serf-tui is a hub-backed terminal dashboard for Serf sessions. It connects to serf-hub, lists live and saved sessions, lets you drill into a transcript, and sends session actions through the hub API.

make build-tuiStart the dashboard:

serf-tuiBy default serf-tui connects to http://127.0.0.1:9180. If no local hub is running, it starts serf-hub automatically and waits for /api/health.

Connect to a specific hub:

serf-tui --hub-addr http://127.0.0.1:9180Use a specific hub binary or disable auto-start:

serf-tui --hub-bin /path/to/serf-hub
serf-tui --no-auto-start-hub
Flag Description
--hub-addr <url> Hub URL or host:port (default: 127.0.0.1:9180)
--hub-bin <path> Hub binary to auto-start when the local hub is down
--no-auto-start-hub Fail instead of starting a missing local hub
--log-file <path> Write auto-started hub logs to this file
--debug Disable the alternate screen

serf-hub is a sibling binary that runs alongside serf serve daemons and gives you a single browser-based interface for many concurrent sessions.

make build-hub
serf-hub  # default 127.0.0.1:9180

Open http://127.0.0.1:9180 in your browser. For production-style setup, credentials, Codex app-server sources, and smoke checks, see cmd/serf-hub/README.md.

~/.serf/hub.toml (optional):

addr = "127.0.0.1:9180"
spawn_timeout = "30s"
past_results_per_page = 50
# Optional; default is ~/.serf/index.db.
past_index_db = "/Users/you/.serf/index.db"

Hub launch model choices come from the Serf launch harness contract (serf launch-check --models), not from a static model roster in hub.toml. Launch defaults live in layered launch config files. For user-wide defaults, create ~/.serf/launch.toml:

app_replay_size = 4096
[env]
OPENAI_API_KEY = "..."

Daemons are loopback-only. Each writes a private rendezvous file to ~/.serf/run/<pid>.json; the hub watches the directory, probes daemons for state, and proxies AppWire/REST so the browser only ever talks to the hub origin. Hub-spawned daemons require the per-hub bearer token recorded in their rendezvous file. Daemon and Hub same-origin guards plus strict Hub CSP defend against DNS-rebinding and cross-origin attacks.

Design spec, plans, and notes live under docs/superpowers/.

The repo enforces a single naming rule across wire formats:

Run the linter via make lint-naming or directly with go run ./cmd/serf-namingcheck. CI runs it after go vet. The check is fast (< 1s on this tree) and exits non-zero on violations. A single field/key can opt out with a // serf:naming-ignore (Go) or # serf:naming-ignore (TOML) marker on the preceding line β€” use sparingly, and explain why.

Serf is forked from Kilroy by Dan Shapiro, originally built as part of the StrongDM Attractor project. The unified LLM client, provider adapters, and agentic tool-calling loop all trace their lineage to that work. Kilroy is licensed under the MIT License (see LICENSE-kilroy).