A small, terminal-first coding harness. Bring your own model, your own key, or your own MCP server.
Install · Run it · First session · Backends · Tools & commands · Cost & credentials · Going further · Configuration · Troubleshooting
A coding agent that lives in your terminal. Bring your own API key (OpenAI, OpenRouter, or xAI) or point it at a local model (Ollama, LM Studio, MLX, llama.cpp) — same tools, same commands, same session log either way. It ships with the usual tool kit — read, edit, grep, shell, run tests — plus a few that aren't usual:
- Local or cloud, one TUI. Switch providers mid-session with
/backend <name>— the tools, commands, and session log don't change. - Per-turn cost on the status line.
$0.003 this turn · $0.41 sessionwhen you're on a cloud backend with a cataloged model. Local turns just show tokens. - Real undo.
/undoreverts the last agent turn's file mutations, including files the agent created or files that weren't tracked when the turn started. - Session paths.
/path forkbranches the conversation and workspace so you can try two fixes, diff them, and/path pickthe winner — no worktree required. - Plan, then grade the work.
/planexpands a one-line intent into a spec;/iterateruns a generate→evaluate loop where a separate critic agent scores each pass against a rubric and feeds back until it clears the bar — the generator never grades itself. - Reset over compaction.
/resetwrites a handoff artifact and starts a clean session seeded with it — better coherence on long tasks than summarizing in place. - MCP-native. Drop servers into
mcpServersin your config; their tools show up asmcp__<server>__<tool>to the model on next launch. /authinstead of.env. Paste API keys once into a0600file under~/.config/small-harness/. Env vars still win when set.- Approval gates you can live with. Every mutating call shows you the
diff first, with
allow once / allow session / always allowcaching.
Homebrew (macOS):
brew install getsmallai/tap/small-harnessFrom source (Rust 1.75+):
git clone https://github.com/GetSmallAI/SmallHarness.git
cd SmallHarness
cargo build --release # binary at target/release/small-harnessLaunch the interactive session:
small-harnessFrom a source checkout without installing, use
cargo run --releaseinstead.
The first launch runs a short setup wizard (it writes agent.config.json —
backend, model, approval policy). Skip it with SMALL_HARNESS_NO_WIZARD=true.
Every launch after that opens straight into a session.
Small Harness talks to one backend at a time — pick the path that fits.
Fastest to start, frontier-model quality, nothing to install locally.
-
Set your key — OpenAI, OpenRouter, or xAI:
export OPENAI_API_KEY=sk-... # or export OPENROUTER_API_KEY=sk-or-... # or export XAI_API_KEY=xai-...
-
Launch, then select the provider in the first-run wizard (or any time with
/backend openai):small-harness
Prefer not to put the key in your environment? Launch first, then run
/auth set openai inside the app and paste it once — it's stored in a 0600
file under ~/.config/small-harness/. Cost per turn and per session shows
live on the status line.
If you want to use a ChatGPT/Codex subscription instead of OpenAI API billing, log in with OAuth inside the TUI:
/login openai-codex
/backend openai-codex
This is intentionally separate from /auth set openai: openai uses an
OPENAI_API_KEY and the public OpenAI API, while openai-codex stores a
refreshable ChatGPT OAuth token in auth.json and talks to the Codex Responses
backend.
If you have SuperGrok/Grok OAuth access and want the same style of provider as
pi-xai-oauth, log in inside the TUI:
/login xai
/backend xai
Small Harness also detects official Grok CLI credentials in
~/.grok/auth.json and can import/use them. This is separate from
/auth set xai: the API-key path uses XAI_API_KEY, while /login xai
stores a refreshable OAuth token in auth.json and sends model traffic to the
xAI Responses backend.
Private, free, offline — runs entirely on your machine.
-
Install Ollama, start it, and pull a coding model:
brew install ollama brew services start ollama ollama pull qwen2.5-coder:7b
-
Launch — Ollama is the default backend, so there's nothing else to set:
small-harness
LM Studio, MLX, and llama.cpp work the same way — see Backends for their ports and start commands.
Tip: switch backends mid-session with
/backend <name>, and run/doctorif a backend won't connect.
> what files are in src/?
Listed src/ (24 files)
src/ has 24 Rust files: main.rs is the entry point (input loop, banner,
warmup); agent.rs runs the chat-completions loop; backends.rs handles the
providers; tools/ contains the tool implementations…
1.2k in · 87 out · $0.0003 this turn · $0.0003 session
> add a function in src/util.rs that lowercases a string and trims it
Read src/util.rs
Edited src/util.rs
--- src/util.rs
+++ src/util.rs
@@ ...
+pub fn normalize(input: &str) -> String {
+ input.trim().to_lowercase()
+}
Apply? [y/n/a]: y
checkpoint saved (1 file) — /undo to revert
3.4k in · 412 out · $0.001 this turn · $0.0013 session
A handful of moves worth knowing right away:
/mode explore | edit | ship | reviewtoggles tool + approval + step-budget presets./undoreverts the last turn's file mutations./path forkbranches the session to try an alternate approach;/path switch,/path diff, and/path pickcompare and merge paths./shipchecksummarizes git state;/handoffdrafts a commit message, changelog bullets, and a release post from local context./plan <intent>drafts a spec;/iterate <goal>runs a generate→evaluate loop where a separate critic grades each pass against a rubric./play fix-failing-testruns a bundled demo in an isolated sandbox so you can try a real agent loop without touching your repo.Ctrl-Jfor newline;Entersubmits.small-harness --continueresumes the most recent session in the cwd.
| Backend | Default URL | Notes |
|---|---|---|
ollama |
http://localhost:11434/v1 |
Easiest setup; mature tool-call templates |
lm-studio |
http://localhost:1234/v1 |
GUI model browser; explicit load / unload |
mlx |
http://localhost:8080/v1 |
Fastest inference on Apple Silicon (via mlx_lm.server) |
llamacpp |
http://localhost:8080/v1 |
Direct GGUF serving (via llama-server) |
openrouter |
https://openrouter.ai/api/v1 |
Cloud A/B with /compare; access to frontier models |
openai |
https://api.openai.com/v1 |
Direct provider access with your own key |
openai-codex |
https://chatgpt.com/backend-api/codex/responses |
ChatGPT/Codex subscription OAuth via /login openai-codex |
xai |
https://api.x.ai/v1/responses |
Grok via XAI_API_KEY, /auth set xai, /login xai, or existing ~/.grok/auth.json |
Switch at runtime with /backend <name>. Endpoint overrides:
OLLAMA_BASE_URL, LM_STUDIO_BASE_URL, MLX_BASE_URL, LLAMACPP_BASE_URL,
OPENAI_BASE_URL, OPENAI_CODEX_BASE_URL, XAI_BASE_URL. API backends require
an API key (set via /auth or env var); openai-codex
requires /login openai-codex, and xai can use either XAI_API_KEY or
/login xai.
Each backend has one sensible default; local backends default to a 7B coder
that runs on modest hardware. Override any time with /model, AGENT_MODEL,
or modelOverride in your config.
| Backend | Default model |
|---|---|
ollama |
qwen2.5-coder:7b |
lm-studio |
qwen2.5-coder-7b-instruct |
mlx |
mlx-community/Qwen2.5-Coder-7B-Instruct-4bit |
llamacpp |
gpt-3.5-turbo |
openrouter |
qwen/qwen-2.5-coder-32b-instruct |
openai |
gpt-4o-mini |
openai-codex |
gpt-5.5 |
xai |
grok-4.3 |
All model-tuning lives under /doctor:
/doctor recommend rank installed + default + cached models for your hardware
/doctor autotune apply switch to the top-scoring cached local model
/doctor --deep probe streaming, usage chunks, tool calls, fallbacks
/doctor bench measure warmup, first-token, and total latency
/doctor models show cached per-model capability + benchmark records
| Class | Tools |
|---|---|
| Read | file_read, grep, list_dir, glob, repo_search |
| Mutate (approval-gated) | file_write, file_edit, apply_patch, batch_edit, shell |
| Workflow | run_tests, ship_status, web_fetch, update_plan, task, critique |
| xAI / Grok (approval-gated cloud tools) | xai_generate_text, xai_multi_agent, xai_web_search, xai_x_search, xai_code_execution, xai_generate_image, xai_critique, xai_analyze_image, xai_deep_research |
| MCP | anything an MCP server exposes, surfaced as mcp__<server>__<tool> |
The default toolSelection: "auto" keeps the full working pool available for
any real request (so "build me a site" writes files instead of dumping code
into the chat) and sends no tools only for plain greetings. fixed always
sends the pool. Set the pool with /tools file_read,grep,list_dir, or
persistently in agent.config.json.
The xai_* tool suite uses XAI_API_KEY when present, otherwise the same
Grok OAuth credential from /login xai / ~/.grok/auth.json. These tools call
xAI directly from inside the harness, including xAI's server-side built-ins
(web_search, x_search, and code_interpreter) and the Imagine image API.
| Policy | Behavior |
|---|---|
always (default) |
Every mutating call prompts you, with a diff preview |
dangerous-only |
Only shell calls matching rm, sudo, chmod, dd, mkfs, etc. prompt |
never |
No prompts — use only when you trust the model |
At each prompt: [y]es, [n]o, [a]lways for this tool, or [s]ession-allow this exact call. The session cache resets on /new.
Session and config
/help list commands
/new start a fresh conversation
/setup rerun the setup wizard
/config show resolved configuration
/session [title <…>] show / rename the current session
/sessions list saved sessions
/resume latest|<id> resume a saved session
/export current|<id> export transcript to markdown or json
/undo revert the last agent turn's file mutations
/path fork, switch, diff, pick, or drop parallel session paths
/paths list saved session paths
Operator modes and workflow
/mode explore|edit|ship|review switch operator preset
/plan <intent> expand a short intent into a spec (.small-harness/spec.md)
/shipcheck summarize git + test readiness
/handoff draft commit, changelog, release copy
/test discover|run|smart discover or run tests
/fix fix-until-green loop
/iterate <goal> generate→evaluate→improve loop (rubric-scored)
/batch / /refactor coordinated multi-file edits
/play fix-failing-test bundled demo in an isolated sandbox
Backend, model, tools
/backend <name> switch backend
/model [id] list / pick a model (shows context + cost when known)
/tools auto|fixed|<…> show or set the active tool pool
/auth manage API keys and OAuth credentials
/login openai-codex sign in with ChatGPT/Codex subscription OAuth
/login xai sign in with xAI/Grok OAuth (SuperGrok/Grok CLI)
/logout openai-codex clear the stored ChatGPT/Codex login
/logout xai clear the stored xAI/Grok login
/image <path> attach an image to the next user turn
/reasoning on|off toggle the streaming reasoning panel
/verbose on|off show every tool call with its full args + result
/compare [model] re-send the last prompt against OpenRouter for A/B
Memory, capabilities, context
/index build / refresh project memory
/map [query] print a repo map or focused hits
/remember <text> save a durable project note
/forget <id|all> remove notes
/context show prompt budget, model limit, auto-guard status
/compact summarize older turns (auto-runs at threshold)
/reset write a continuation handoff and start a fresh session
/doctor [--deep] probe backend, tools, streaming, capabilities
/doctor models show cached per-model capability + benchmark records
/doctor autotune pick the best cached local model (add `apply` to switch)
/doctor recommend rank models for your hardware
/doctor bench measure warmup + first-token + total latency
/checkpoints toggle per-turn snapshots
Run /help in the harness for the full list with descriptions.
API-key cloud backends authenticate with API keys. Paste them once and Small
Harness stores them at ~/.config/small-harness/auth.json (mode 0600).
Environment variables always win at lookup time, so CI and scripted users see
no change in behavior.
/auth show what's configured (keys are masked)
/auth set openai paste your OpenAI key, save to file + this session
/auth set openrouter paste your OpenRouter key
/auth set xai paste your xAI API key
/auth clear openai remove from the file (env stays for this session)
/login openai-codex browser/device-code login with ChatGPT/Codex
/login xai browser login with xAI/Grok OAuth
/logout openai-codex remove the stored OAuth credential
/logout xai remove the stored xAI OAuth credential
openai-codex is not an OPENAI_API_KEY replacement. It uses browser/device
OAuth, stores {access, refresh, expires, accountId} in the same auth.json,
refreshes the access token before use, and sends model traffic to the Codex
Responses backend.
Likewise, /login xai is not an XAI_API_KEY replacement. It stores an
xai-oauth credential ({access, refresh, expires}) in auth.json and talks
to xAI's Responses backend; if XAI_API_KEY is set, the env var still wins.
When you're on a cloud backend with a model in the catalog (currently OpenAI's GPT-4o family, o-series, GPT-4, GPT-3.5, plus Grok 4.3 / 4.2), every turn prints its own cost plus the running session total:
2.1k in · 845 out · $0.013 this turn · $0.094 session
Switch to Ollama mid-session and the line shows $0.00 this turn but keeps
the running total honest. OpenRouter and not-yet-cataloged OpenAI models
show $? for the turn and prefix the session total with ≥ to signal it's
a lower bound, not a fiction.
The /model picker shows the same data while you choose:
1) gpt-4o-mini 128k ctx · $0.15/$0.60 per Mtoken
2) gpt-4o 128k ctx · $2.50/$10.00 per Mtoken
3) o1-mini 128k ctx · $3.00/$12.00 per Mtoken
/plan <intent> expands a one- or two-sentence intent into an ambitious spec
— goal, user outcomes, scope, done criteria, open questions — and writes it to
.small-harness/spec.md. It deliberately stays at the level of what and
why, not implementation, so an early spec doesn't lock in the wrong details.
/plan show prints the saved spec; --export <path> writes elsewhere.
/iterate <goal> runs a generate→evaluate→improve loop. After each attempt a
separate, read-only critic agent (critique) scores the work 0–10 against
a weighted rubric and hands back actionable feedback; the loop repeats —
refining or pivoting — until the score clears the threshold or it runs out of
rounds (--max N, default 6, capped at 15; --threshold X). The harness, not
the model, computes the weighted total and pass/fail, so a critic that
over-rates can't wave weak work through.
The rubric defaults to quality / originality / craft / functionality and
penalizes generic "AI slop"; override it with a .small-harness/rubric.md
using ## Name (weight: N) sections. Set iterate.evaluatorModel to grade
with a different model than the generator — the cleanest version of the
generator/evaluator split. Turn on rubric.liveVerify and the critic runs your
test suite (via a fixed-surface verify tool — no arbitrary shell) before
scoring functionality. The critique tool is also available on its own for a
one-off, independent grade.
Workspace context is never sent to a cloud backend for grading unless you set
rubric.allowCloud.
On a long task, /reset writes a structured handoff artifact — done, in
progress, key decisions, next steps, key files — to
.small-harness/continue.md, then starts a fresh session seeded with only
that artifact. Unlike /compact, which summarizes in place, this is a clean
context window carrying just what's needed to continue, which holds coherence
better over long runs. /reset --dry-run writes the artifact without clearing;
cloud backends require --cloud, since drafting the note sends the
conversation to the model.
Drop a markdown file at .small-harness/prompt.md in your repo and Small
Harness prepends it to the system prompt every turn. Use it for project
conventions ("snake_case everywhere", "ship via make release", "never
edit vendor/"). Auto-truncated at 8 KB.
Add an mcpServers block to agent.config.json:
{
"mcpServers": {
"fs": {
"command": "/usr/local/bin/some-mcp-server",
"args": ["--root", "/tmp"],
"env": { "TOKEN": "abc" }
}
}
}Small Harness spawns each server at startup, lists its tools, and exposes
them through the same approval-gated tool layer with names like
mcp__fs__read_file. JSON-RPC over stdio; no extra dependencies.
/image <path> attaches an image to your next prompt. Small Harness encodes
it as a data:image/...;base64,... URL and sends it as a multi-part user
message. The catalog tracks which models accept images; you get a warning
if your current model isn't vision-capable.
web_fetch (off by default, approval-gated) lets the agent pull a URL,
strip HTML to text, and read the result. Useful for docs and RFCs the model
needs to consult mid-task. Enable per session with
/tools auto file_read,grep,list_dir,web_fetch or persistently in your
config.
/index builds a safe local repo map at .sessions/project-memory/. It
stores metadata only — paths, language, symbols, headings, capped keyword
terms — never file bodies. It honors .gitignore and skips .git,
.sessions, target, node_modules, binaries, oversized files, and
common secret/env files. /map prints a compact view; /remember <text>
saves a durable project note.
/compare re-sends your last prompt against any OpenRouter model so you can
A/B a local response against a frontier one without leaving the session.
Requires OPENROUTER_API_KEY.
Resolution order (later overrides earlier):
- Built-in defaults
agent.config.jsonin the working directory.env, then.env.local- Process environment variables
- Slash command overrides at runtime
BACKEND=ollama # ollama|lm-studio|mlx|llamacpp|openrouter|openai|openai-codex|xai
AGENT_MODEL=qwen2.5-coder:14b # overrides the backend default model
OPENAI_API_KEY=sk-... # required for openai
OPENROUTER_API_KEY=sk-or-... # required for openrouter / /compare
XAI_API_KEY=xai-... # optional for xai (or use /login xai)
OPENAI_BASE_URL=https://api.openai.com/v1 # point at a compatible proxy if needed
OPENAI_CODEX_BASE_URL=https://chatgpt.com/backend-api # override Codex backend base if needed
XAI_BASE_URL=https://api.x.ai/v1 # override xAI backend base if needed
APPROVAL_POLICY=always # always | dangerous-only | never
AGENT_TOOLS=file_read,grep,list_dir,file_edit,file_write,shell,update_plan,task
AGENT_TOOL_SELECTION=auto # auto | fixed
WARMUP=true # pre-warm prompt cache at startup
SMALL_HARNESS_NO_WIZARD=false # skip first-run setup
SMALL_HARNESS_NO_UPDATE_CHECK=false # skip the GitHub release checkFull list with comments in .env.example.
For project-level defaults, run /setup or drop a JSON file at the repo
root. Common shape:
{
"backend": "ollama",
"modelOverride": "qwen2.5-coder:14b",
"approvalPolicy": "dangerous-only",
"tools": ["file_read", "grep", "list_dir", "file_edit", "file_write", "shell", "update_plan", "task"],
"toolSelection": "auto",
"maxSteps": 20,
"workspaceRoot": "/path/to/project",
"outsideWorkspace": "prompt",
"context": {
"maxMessages": 40,
"modelContextTokens": 8192,
"autoCompact": true,
"compactThreshold": 0.85,
"reserveRatio": 0.25
},
"projectMemory": {
"enabled": true,
"autoInject": true,
"allowCloudContext": false
},
"checkpoints": { "enabled": true, "maxTurns": 10 },
"rubric": { "enabled": true, "passThreshold": 7.0, "allowCloud": false, "liveVerify": false },
"iterate": { "maxIters": 6, "evaluatorModel": null },
"paths": {
"enabled": true,
"maxPaths": 5,
"maxSnapshotBytes": 52428800,
"maxFileBytes": 1048576
},
"mcpServers": {
"fs": { "command": "/usr/local/bin/some-mcp-server", "args": [] }
}
}Anything in the config can be overridden by env or slash commands at runtime.
small-harness --continueresumes the most recent session in cwd without picking from a list.small-harness completions bash|zsh|fishprints a completion script you can source./reasoning on|offtoggles the streaming reasoning panel — adds a dim "thinking…" block above the answer for o-series and similar models./verbose on|offswitches to a debug tool view: every tool call is printed with its full arguments and a large result preview, so you can see exactly what the agent is doing./verbose offrestores the normal view.- Slash-command completion. Type
/and a menu of matching commands (with descriptions) appears beneath the prompt; the best match also shows as dim ghost text. ↑/↓ select, Tab accepts (with a trailing space), → accepts inline, Esc dismisses. It narrows live as you type. - Update check. Once a day, Small Harness checks GitHub for a newer
release and shows a one-line notice in the banner if there is one.
Background, cached, opt-out with
SMALL_HARNESS_NO_UPDATE_CHECK=true. - Crash log. If the harness panics, it writes a redacted log (API keys
scrubbed) to
.sessions/crashes/<timestamp>.logand prints the path so you have something to attach to an issue. - One-shot mode —
small-harness --print "summarize this repo"orprintf '…\n' | small-harnessfor scripts and CI. Approval-gated tools are denied by default; pass--allow-toolsto allow them. - Warmup. Small Harness sends a 1-token request with the full system
prompt + tools at startup so llama.cpp-derived engines have a hot
prompt-eval cache before your first prompt. Disable with
WARMUP=false.
- Ollama —
brew services start ollamaor runollama serve. Default port 11434. - LM Studio — open the app, go to Local Server, click Start. Default port 1234.
- MLX — start
mlx_lm.server --port 8080against an MLX-format model. - llama.cpp —
llama-server -m /path/to/model.gguf --host 127.0.0.1 --port 8080 --jinja(the--jinjaflag enables native tool calls). - OpenRouter — set
OPENROUTER_API_KEY(or use/auth set openrouter). - OpenAI — set
OPENAI_API_KEY(or use/auth set openai). UseOPENAI_BASE_URLfor a compatible proxy. - OpenAI Codex — run
/login openai-codex, then/backend openai-codex. - xAI / Grok — set
XAI_API_KEY(or use/auth set xai), or run/login xai; existing Grok CLI OAuth in~/.grok/auth.jsonis also supported.
Run /doctor --deep for a fuller capability probe (streaming, usage chunks,
native tool calls, inline JSON fallback). Reports land under .sessions/doctor/.
The cache becomes stale when you change /backend, /model, or /tools.
The next prompt re-evaluates the new system prompt and tools. One-time per
change.
Some small-model templates emit tool calls as plain content
({"name": "shell", "arguments": {…}}) instead of populating the
tool_calls field. Small Harness detects and synthesizes a real tool call.
If a particular model still misbehaves, llama3.1:8b has well-tested
tool-call templates.
Some bilingual models (notably qwen) drift into Chinese on short greetings.
The system prompt has an explicit language directive; if it's still
happening, strengthen it by editing SYSTEM_PROMPT in src/config.rs.
Install Rust via rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh.
+-------------------------+
| main.rs |
| banner / input loop / |
| warmup / approval |
+------------+------------+
|
v
+-----------+ +-------------------------+ +-------------------+
| config.rs |--->| agent.rs |<-->| tools/*.rs |
| + auth/ | | chat/completions loop | | + mcp__ adapters |
+-----------+ +-------------+-----------+ +-------------------+
|
v
+-------------------------+
| backends.rs |
| Ollama / LM Studio / |
| MLX / llama.cpp / |
| OpenRouter / OpenAI |
+-------------------------+
Source layout in src/ — agent.rs runs the loop, backends.rs
holds the backend providers, tools/ holds tool implementations, mcp.rs is
the stdio MCP client, catalog.rs has the per-model context + pricing
table, auth.rs manages the credential file, session.rs writes the JSONL
log. cargo doc --open for module-level docs.
cargo check # type-check without producing a binary
cargo run --release # optimized build + run
cargo build --release # target/release/small-harness
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo testGuidelines:
- Mutating tools implement
require_approvalon theTooltrait (returntrue, or compute from args — seeshell.rs). - New backends usually need an OpenAI-compatible
/v1/chat/completionsendpoint and a default model inbackends.rs; non-compatible transports should add an adapter likecodex_responses.rs. - Before opening a PR, run the full check suite:
cargo fmt --check,cargo clippy --all-targets -- -D warnings, andcargo test.
Release tags use a leading v (v0.4.0). The release workflow at
.github/workflows/release.yml builds
notarized macOS binaries when Apple Developer secrets are present.
MIT.