GitHub - justrach/codegraff: graff — a fast agentic coding harness in Zig: multi-provider, MCP, workflows, DGM evolution loop, TS/Python SDKs

**An AI that actually does the work. Not just talks about it.**
Install it on your Mac, Linux, or Windows machine, sign in with the AI subscription you _already have_, and hand it real tasks. graff writes and runs code, automates the boring stuff, digs through your files, researches the web, and runs its own experiments, on its own, until the job is done.
**You don't chat with it. You give it work.**


**The most token-efficient coding harness we've built so far.**
Prompt-cache max keeps repeated prefixes hot. RLM + spec-ptc turns wide work into a small streaming program. Learnt slimming keeps fat results out of history.
curl -fsSL https://github.com/justrach/codegraff/releases/latest/download/install.sh | sh
Prefer a window? Grab the desktop app. Then just run `graff` and tell it what you need.
What can I ask it?
[](https://github.com/justrach/codegraff#what-can-i-ask-it) If you could do it at a computer, you can ask graff to do it for you:
- _"Build me a little app to track my workouts."_ It writes it, runs it, and shows you.
- _"Turn this folder of messy CSVs into one clean spreadsheet."_
- _"Figure out why my site is slow, then fix it."_
- _"Scrape these five pages and summarize them."_
- _"Run an experiment: try three versions of this and tell me which scores best."_
It works in your real terminal, on your real files, with the real internet, and it can spin up a whole team of sub-agents to work in parallel. It even keeps score of which approaches work and gets better over time.
> **Don't write code?** You don't have to. Say what you want in plain English; graff figures out the steps and does them.
- * *
Token-efficient by construction
[](https://github.com/justrach/codegraff#token-efficient-by-construction)
[](https://github.com/justrach/codegraff/blob/main/token-efficient-loop.png)
Stable prefix → small program → parallel tools → slim result. Faint branches are measured variants; the proven path continues.
There is no single “token trick.” graff compounds three separate cuts:
- **Keep the reusable prefix stable.** Prompt-cache max holds the system prompt and tool-catalog head byte-identical, keeps provider cache keys sticky, and shows the last hit rate in the UI. A live same-process grok-4.6 `/btw` turn reused **3,712 of 3,721 prior prompt tokens (99.8%)**.
- **Program over context instead of pasting it back.** The default Recursive Language Model (RLM) loop is a Zig port of Alex Zhang's RLM and spec-ptc (speculative programmatic tool calling): the model writes a tiny program, finished host calls start while the rest of it is still streaming, and useful binds persist across turns.
- **Return the useful shape, not the payload.** Folded schemas, structural `codedb` reads, 4 KB tool-result handles, and learnt MCP slimming keep bulky tool output out of the model's working set.
The Darwin Gödel Machine-style evolutionary archive supplies the measuring discipline: record prompt/persona variants, score them, and keep what holds up. Prompt-cache max is a separate harness decision built with that same measure-and-keep instinct; the archive did not autonomously invent the cache layout.
One stable RLM reuse case
[](https://github.com/justrach/codegraff#one-stable-rlm-reuse-case) Live grok-4.6, same task, one rep. The model reads a file into a bind and reuses it on the next RLM call:
| harness | wall | input tokens | model calls | | --- | --- | --- | --- | | structured-only (`--old`) | 99.6s | 135k | 11 | | default RLM | **39.2s** | **25.3k** | **5** | | reduction | **61%** | **81%** | **55%** |
The broader five-task RLM suite kept the same **5/5** pass rate while cutting input from **203k to 105k**, output from **4,186 to 1,669**, and wall time from **161s to 136s**. SuperGrok OAuth is flat-rate; on a metered xAI key, the token cut is the bill. Full method and caveats: v0.0.276 release notes.
Same grok-4.6, same six coding tasks
[](https://github.com/justrach/codegraff#same-grok-46-same-six-coding-tasks)
[](https://github.com/justrach/codegraff/blob/main/social/graff-vs-grok-build-grok46.png)
At the same **5/6** pass rate, graff used **61% less summed wall time, 45% less input, 38% fewer model calls, and 95% less peak RSS**. This is one live rep of six DeepSWE-shaped tasks, not a universal leaderboard; `label-sort` failed both harnesses. The exact run and the rejected shortcuts are documented in v0.0.276.
Much of this implementation and benchmark work was made possible by Cursor Cloud Agents: parallel agents in isolated environments, with measured evidence merged back into the harness.
What shipped in v0.0.277–v0.0.280
[](https://github.com/justrach/codegraff#what-shipped-in-v00277v00280)
- **MCP results learn to slim themselves.** On the Linear fixture, the warm learnt path moved from **28.0s to 14.8s**, **112k to 31k input**, and **7 to 5 calls**. Return shapes ride the load result, never the stable prefix.
- **grok-4.6 gets xAI's hosted X search.** The same live prompt moved from a seven-call scrape (**101s, 64k input**) to one hosted call (**37s, 28k**), without adding another catalog tool.
- **Small turns stay small.** RLM and spec-ptc appear when work gets wide, the context crosses its measured threshold, or you explicitly ask for them; tiny turns do not pay the schema tax.
- **Removed images really leave.** The v0.0.278 privacy follow-up drops a pasted screenshot if its composer chip is removed, collapses duplicate payloads, and makes `/image clear` clear the whole queue before anything reaches the model.
- **The native app speaks ACP.** Thinking and tool chips stream mid-turn from `graff acp` (ADR 0032). `graff serve` is not required.
- **v0.0.280 leftover cut.** TUI `/tell``/peek`, experiment fan-out, ACP `graff-login`, local tools, `/schedule`, and JSONL channel workers. Native `codedb` / `read_file` stay the default readers when codedb-pro is licensed (ADR 0040).
Read the full v0.0.277 notes, the v0.0.278 privacy follow-up, the v0.0.279 ACP native cut, and the v0.0.280 leftover cut.
Broader comparison: model choice, footprint, and startup
[](https://github.com/justrach/codegraff#broader-comparison-model-choice-footprint-and-startup)
[](https://github.com/justrach/codegraff/blob/main/comparison.png)
The older cross-provider comparison answers a different question: what do model choice and a tiny native harness cost on three read-only repo tasks plus an eight-trial startup test?
**Model choice.** On `deepseek-v4-pro`, graff averaged **$0.022 per task**, against Claude Code's **$0.51** (Opus 4.8) and Codex's **$0.42** (gpt-5.5). That roughly 20× result comes from choosing a cheaper model; it is not the same-model harness comparison above.
**Footprint.** The focused runs used about **25 MB** of memory, against Claude Code's steady **~410 MB** and Codex's **~206 MB**.
**One-shot startup.** On the identical ChatGPT endpoint, graff completed a gpt-5.5 turn in **4.4s** versus Codex's **8.9s** in every paired trial. Long interactive sessions settle to model latency; this is a CLI/CI startup result, not a blanket speed claim.
**Method:** macOS, same machine, read-only code questions on this repo. Cost is each tool's own reported usage at codegraff gateway prices; memory is peak RSS via `/usr/bin/time -l`; latency is 8 concurrent graff/Codex pairs on a tool-free prompt with reasoning effort matched. Your numbers will vary with the task, the model, and the network. Reproduce it yourself: benchmarks/.
- * *
Under the hood
[](https://github.com/justrach/codegraff#under-the-hood)
**the super simple harness.** A minimal agentic coding harness in Zig 0.17 dev. One **2.7 MB** binary, zero runtime dependencies. Talks to **Anthropic**, any **OpenAI-compatible** endpoint (DeepSeek, OpenAI, …), or your **ChatGPT subscription**.
``` user text ─→ POST ─→ model asks for tools? ↑ │ │ results ▼ └─ io.async ×N (parallel: bash/files/subagents) … repeat until the model stops ```
A REPL that talks to the model directly over HTTPS (`std.http.Client`), hand-rolls every JSON wire format (`std.json`), and runs tool calls (subagents included) in parallel on the std.Io thread pool. It also compacts its own context when the conversation gets long.
**Contents**
- Token-efficient by construction
- Install · give it a key · run it
- Why
- * *
Install
[](https://github.com/justrach/codegraff#install)
Desktop app: macOS (Apple Silicon)
[](https://github.com/justrach/codegraff#desktop-app-macos-apple-silicon)
Prefer a window over a terminal? Download the latest signed, notarized build, drag it to Applications, and open it. The desktop app is **fully self-contained**: it bundles the `graff` agent, so there's nothing else to install to start coding, and it keeps itself up to date automatically. On first launch it drops two commands on your PATH: `codegraff <path>` (opens that folder in the app, `code`-style) and `graff` itself (the agent CLI, in your terminal), so the one install covers both the window and the command line. The terminal `graff` is symlinked into the app, so it auto-updates along with it. Not on Apple Silicon, or want a standalone CLI? Use the command-line install below.

Command line: macOS · Linux · Windows
[](https://github.com/justrach/codegraff#command-line-macos--linux--windows) Grab the latest prebuilt release binary: macOS builds are Developer ID signed and Apple notarized; on any other platform the installer builds from source with Zig `0.17.0-dev.813+2153f8143` (select it with `zigup`):
curl -fsSL https://github.com/justrach/codegraff/releases/latest/download/install.sh | sh
From a checkout, just run `./install.sh`. The binary lands in `~/bin` by default (override with `HARNESS_DIR`). The installer appends that directory to your `~/.zshrc` (and `~/.bashrc` / fish config when those are your shell or already exist), so a new terminal finds `graff` — the usual "it installed but `graff: command not found`" miss. Skip with `HARNESS_NO_PATH=1`. Open a new tab or `source ~/.zshrc` once after install:
| tool | purpose | | --- | --- | | `graff` | the agent CLI + REPL: the one binary this script installs | | `codedb` | optional code-intelligence companion (structural search/outline/callers). graff auto-detects it and points at the one-line install if it's missing; everything else works without it |
| `kuri` | optional browser companion backing `webfetch`'s markdown path, web crawling, and the kuri skill. Installed by default alongside graff; opt out with `HARNESS_NO_KURI=1`, never fatal if it fails |
On Windows, grab `graff-x86_64-windows.tar.gz` (or `aarch64`) from the latest release, unpack it, and put `graff.exe` on your `PATH`; the shell installer itself is Unix-only and points Windows at WSL.
Give it a key
[](https://github.com/justrach/codegraff#give-it-a-key) Three ways, pick whichever is easiest:
graff login # free codegraff key (device-code OAuth, no signup forms) graff login kimi # Kimi Code subscription OAuth (device-code) graff key set deepseek sk-... # store ANY provider's key (macOS Keychain, else 0600 file) export DEEPSEEK_API_KEY=sk-... # or just an env var (env always wins)
Already logged into the Codex CLI? Skip this step. Your ChatGPT subscription is picked up automatically from `${CODEX_HOME:-~/.codex}/auth.json`. Or run `graff login codex`.
> **Note on `login`:**`graff login` is the free codegraff key, `graff login codex` is the ChatGPT-subscription OAuth, and `graff login kimi` is Kimi Code's device-code OAuth. **Every other provider** (deepseek, openai, anthropic, xai, zai, minimax, xiaomi) is a key: set it with `graff key set <provider> <key>` or its `<PROVIDER>_API_KEY` env var, then select a model with `--model` / `/model`. See Providers & models.
Run it
[](https://github.com/justrach/codegraff#run-it)
graff # starts on the first provider you have a key for graff --model deepseek-reasoner # or pin one explicitly
First things to try once you're at the `›` prompt:
``` › what's in this directory? summarize the build setup. › /model sonnet # fuzzy-switches to claude-sonnet-4-6 › spawn three subagents to summarize src/, count TODOs, and check git status, in parallel › ultracode audit this repo for error-handling gaps # codeword → multi-agent workflow mode › /help # everything else ```
Zed (External Agents / ACP)
[](https://github.com/justrach/codegraff#zed-external-agents--acp)
`graff acp` speaks the Agent Client Protocol, so Zed can drive it as an External Agent. The same spawn is the hosted-agent recipe — see Embedding graff. There is no "Install from Registry" row yet; the submit recipe and the auth blocker are ACP Registry (#613). Until that lands, register it in `~/.config/zed/settings.json`:
{ "agent_servers": { "graff": { "type": "custom", "command": "/path/to/graff", "args": ["acp", "--model", "<default-model>"], "env": {} } } }
Then fully quit and reopen Zed (`agent_servers` is read at startup), and start a thread via `agent: new external agent thread` in the command palette — or the Agent Panel's new-thread menu → **graff** under External Agents.
Gotchas worth knowing:
- **Pin a default model** (`--model <name>`) — otherwise the ACP turn fails with `no language model configured`. Verify the route first with `graff route <name>`.
- **Zed's own model picker is empty for external agents** — it shows "no match / configure a provider", which looks like an error but isn't. Switch models inside the thread with `/model`.
- **Old threads stay broken**: a thread created before auth/model was configured keeps failing even after restart; start a fresh one.
- Provider logins are shared with terminal graff (codegraff OAuth etc.), but e.g. Codex models need their own `graff login codex`.
- Debugging: `dev: open acp logs` in Zed shows the raw ACP traffic.
- * *
Why
[](https://github.com/justrach/codegraff#why) Measured, not vibes: arm64 macOS, ReleaseFast; methodology and the budgets each change is held to live in architecture.md:
| metric | measured | | --- | --- | | binary | **2.74 MB**, self-contained with zero runtime dependencies | | cold start | **~1.8 ms** | | full agentic turn | **12 MB** peak RSS, ~4% CPU (network-bound) | | 8 parallel subagents | **+0.4 MB each** (15 MB total) | | tool output into history | one 4 KB handle, whatever the result's size: a 500 MB python child process never touches the harness's footprint |
Benchmarked against the Rust codegraff (justrach/codegraff, 39 MB binary, 934 crates) on the _same model through the same endpoint_, interleaved 3×: turn speed was a dead tie (2.94 s vs 2.93 s; the network and the model dominate the turn), but the Zig harness ran in **4.3× less memory** (11.3 MB vs 48.5 MB), starts roughly 3× faster, and is about 14× smaller on disk. An agent CLI rarely wins on turn speed; it can win on the cost of being there.
- * *
Code intelligence: token-efficient by default
[](https://github.com/justrach/codegraff#code-intelligence-token-efficient-by-default)
The fastest way to blow a context window is to read whole files into it. graff ships with a built-in **`codedb`** tool: read-only, structural code intelligence over a local index of the repo (github.com/justrach/codedb), and the system prompt steers the model to reach for it _before_`grep` or whole-file reads. Instead of paying for a 2,000-line file to find one function, the model asks for exactly the shape it needs:
``` codedb outline src/main.zig # just the symbol map, functions/types, no bodies codedb symbol switchProvider --body # one function, by name codedb callers recordUsage # who calls it (call sites, not files) codedb search "parse SSE" # indexed search, ranked hits, not a grep dump codedb context "add a new provider" # task-shaped orientation across the codebase ```
Why this keeps token cost low:
- **Structural slices, not files.**`outline`/`symbol`/`callers`/`deps` return a function map or a single definition, tens of lines where a `read_file` would spend thousands. The index is queried, not the raw bytes streamed into history.
- **It's free and indexed; the metered tools come second.** The system prompt encodes an explicit search order: try the free, indexed `codedb` first; fall to (metered) `muonry`/raw search only for literal/regex or non-indexed files. The cheap path is the default path.
- **Hard output cap.** A query is truncated at **64 KB** with a marker that nudges the model back toward targeted queries (`outline`, `symbol --body`) rather than whole-file reads, so even a broad search can't balloon the context.
- **Same index powers the `@` file picker** (`codedb glob`), so attaching a file by name never shells out to a directory walk.
Pure-Zig client to a pure-Zig server, zero dependencies on either side. Allowed subcommands: `search · symbol · callers · find · outline · read · tree · context · word · deps · glob · ls · file · hot`. Not installed? The tool says so and points at the one-line install; everything else keeps working without it.
- * *
An evolutionary harness
[](https://github.com/justrach/codegraff#an-evolutionary-harness)
graff doesn't just run an agent; it records every run as a node in a **Darwin Gödel Machine-style archive tree** (arXiv:2505.22954), so the harness itself is the substrate for agent self-improvement. Each run writes a unique `.graff/trajectories/<run-id>.jsonl`; archive readers aggregate the directory, so concurrent processes never share a truncate/append cursor:
- **A lineage tree, not a flat log.** Interactive root turns form a spine (each turn's parent is the previous one); every subagent and workflow task hangs off the turn that spawned it. Each node carries a **fingerprint of the system prompt it ran with** (`prompt_sha` = first 8 bytes of SHA-256), so prompt mutations ( `set_system_prompt` on the spine, per-child `system_prompt` overrides on the fan-out) show up as hash changes along edges. A lineage can be replayed or scored offline.
- **Personas are variants.** Subagents pick a persona with `agent` (built-ins: `reviewer · researcher · implementer · skeptic`, plus anything in `.harness/agents/`) or take a custom `system_prompt`; either way the trajectory records the lineage, so you can mine _which agent variant actually worked_.
- **A fitness ledger with integrity.** The `score` channel appends evaluation records (`prompt_sha`, `score`, `parent_sha`; the lineage edge DGM parent selection counts children with). Because the archive lives in the working directory, a forged `score` row could manufacture fitness, so every score the harness writes is **HMAC-signed** (keyed by `GRAFF_SCORE_KEY_FILE`, a secret outside the cwd that the evolving agent's confined tools can't reach). Readers recompute the HMAC and reject unsigned or forged rows. Signing is opt-in and backward-compatible (no key → unsigned, accepted as before).
- **Tool-use is mined too.** Each agent logs its tool calls (name + error flag, in order): the process signal behind "which tool combinations work", joinable to scores via `prompt_sha`.
- **Consent-scoped fleet loop.** Learning contributes prompt-free aggregate fitness by default, announced once per machine; `/privacy local` opts out entirely, and individually reviewed reusable templates need an exact per-artifact approval on top. `/trajectory` renders the current session's agent tree; see docs/hyperagents.md for the full design.
For controlled local hill-climbing, `graff learn` adds a separate parent → mutate → paired-evaluate → select loop with immutable evidence, manual promotion by default, explicitly gated automatic promotion, atomic activation, and rollback. It never treats trajectories or best-effort telemetry as promotion authority. See Local prompt-policy learning, including the no-sandbox trust boundary and the collective-learning design that is **not** yet an implemented remote authority.
- * *
Providers & models
[](https://github.com/justrach/codegraff#providers--models)
Direct API-key and OAuth providers across three wire formats. A `ProviderSpec` table holds each built-in provider's endpoint, auth style, env var, and default model; base URLs and key names come from models.dev's `api.json` (snapshot 2026-06-10).
| Provider | Wire format / auth | Key env var | | --- | --- | --- | | `anthropic` | Anthropic Messages, x-api-key | `ANTHROPIC_API_KEY` | | `codegraff` | OpenAI chat, bearer | `CODEGRAFF_API_KEY` (`cg_sk_...`) | | `deepseek` | OpenAI chat, bearer | `DEEPSEEK_API_KEY` | | `openai` | OpenAI chat, bearer | `OPENAI_API_KEY` | | `minimax` | Anthropic Messages, bearer | `MINIMAX_API_KEY` | | `xiaomi` (MiMo) | OpenAI chat, bearer | `XIAOMI_API_KEY` | | `kilo` | OpenAI chat, bearer | `KILO_API_KEY` | | `groq` | OpenAI chat, bearer | `GROQ_API_KEY` | | `cerebras` | OpenAI chat, bearer | `CEREBRAS_API_KEY` | | `vercel` | OpenAI chat, bearer (AI Gateway coding-agent) | `AI_GATEWAY_API_KEY` | | `openrouter` | OpenAI chat, bearer | `OPENROUTER_API_KEY` | | `mistral` | OpenAI chat, bearer | `MISTRAL_API_KEY` | | `kimi` | Live catalog-selected: native Kimi chat + bearer, or Anthropic beta Messages + x-api-key when declared | `graff login kimi` or `KIMI_API_KEY` | | `xai` (grok) / `zai` (GLM) | OpenAI chat, bearer | `XAI_API_KEY` / `ZAI_API_KEY` (via `graff key set`) | | `codex` | Responses API, ChatGPT login | `${CODEX_HOME:-~/.codex}/auth.json` (no API key) |
**Using a specific provider directly** is always the same two steps: give it the key, then name a model. For example, DeepSeek straight to `api.deepseek.com`:
graff key set deepseek sk-... # or: export DEEPSEEK_API_KEY=sk-... graff --model deepseek-reasoner # models: deepseek-v4-pro · deepseek-v4-flash · deepseek-chat · deepseek-reasoner
The same pattern works for every API-key row above: swap in the provider id and one of its models (`graff key set openai sk-...` → `--model gpt-...`, `graff key set anthropic sk-ant-...` → `--model sonnet`, and so on).
To add one workspace-local OpenAI-compatible router without changing Graff, create `.graff/.config.router`:
{ "id": "myrouter", "name": "My Router", "base_url": "https://router.example.com/v1", "env_key": "MYROUTER_API_KEY", "default_model": "example/model" }
`name` is optional. `takes_effort: true` is also available for routers that accept OpenAI-style reasoning-effort requests. The file contains no secret; use `export MYROUTER_API_KEY=...` or `graff key set myrouter ...`. Select it with `graff --model myrouter` or `/model myrouter`; `graff models refresh` pulls its full catalog. Graff derives `/chat/completions` and `/models` from `base_url`, caches that router's model catalog in `.graff/.models.router`, and exposes it to the CLI and GUI schema. Only one additional router is configured per workspace. `.graff/` is ignored by Git in this repository.
A model is routed to the first provider (in the table order above) that both has a key set **and** lists the model in the active catalog. Codex names, rollout visibility, ordering, and context windows come from its account-scoped `/models` endpoint, cached for five minutes with Graff's supported Codex protocol version (a separately installed older Codex CLI cannot hide newer models). Baked Codex rows are only the logged-out/offline fallback, currently including `gpt-5.6-sol`, Terra, and Luna. Unknown `claude*` models fall back to Anthropic; any other unknown model falls back to the codegraff gateway, and `/model` prints a warning when that fallback fires, since a typo'd name will be rejected by the API on the first request. The startup default is the first provider with a key, on its default model. `/models` prints the full table: context window, compaction point, provider, and which providers you have keys for; `/model <name>` switches (a bare `/model` opens an interactive fuzzy picker). `graff models refresh` forces a fresh Codex catalog request and refreshes the Codegraff/workspace-router catalogs plus the independent models.dev price/context metadata cache.
**Codex login (ChatGPT subscription)** · **why no Claude login**
If you're logged into the Codex CLI, the harness reads the ChatGPT OAuth token from `${CODEX_HOME:-~/.codex}/auth.json` at startup (the same on-disk-credential trick used for the codegraff key) and prints `logged into Codex (ChatGPT account …)`. Switch to it with `/model codex`; Graff selects the first visible model in your live, account-scoped Codex catalog. This is a third wire format: the **Responses API** against the ChatGPT backend (`chatgpt.com/backend-api/codex/responses`), not `api.openai.com`, so it uses your ChatGPT Pro/Plus subscription rather than a paid API key. Text, tool calling, compaction, and `/save`/`/resume` all work on it. Not logged in? `graff login codex` runs the PKCE browser flow itself.
Claude models route through a real `ANTHROPIC_API_KEY` or the codegraff gateway only. There is deliberately no Claude-subscription login: reusing a Claude Code OAuth token outside the official client violates Anthropic's terms of service.
Tool definitions are written once as comptime specs and rendered into both formats (Anthropic `input_schema` vs OpenAI `function.parameters`) at compile time. See `anthropicToolsJson` / `openaiToolsJson` in `src/main.zig`.
- * *
CLI reference
[](https://github.com/justrach/codegraff#cli-reference)
``` usage: graff [flags] start the REPL graff [-p] "prompt" one-shot: run the prompt, print the answer, exit graff login get a codegraff key (device-code OAuth) graff login codex [--refresh] ChatGPT/Codex OAuth login (PKCE) graff key set <provider> <key> store a key (macOS Keychain, else 0600 file) graff key list show which providers have keys graff mcp add <name> -- <cmd> add an MCP server to .mcp.json graff mcp list configured MCP servers graff plugins list Cursor/Claude/Grok/Codex plugin trees (in place) graff learn <command> local prompt-policy learning and rollback graff --schema print the machine-readable interface (SDK codegen)
flags: --model <name> start on this model (same fuzzy resolution as /model) --subagent-model <name> pin children/workflows/judges to this model on the root provider --subagent-provider <id> route pinned workers through this explicit provider --allow-cross-provider-subagents consent to worker prompts/code going to another provider --yolo skip all permission prompts for the session --no-local-tools embedder mode: hard-disable the built-in bash/file/codedb tools process-wide (see "Embedder mode" below) -p, --print one-shot print mode (answer on stdout, tool progress on stderr) --timing show per-tool wall-clock on result lines (✓ (312ms) …) --cost show running session spend in the prompt ([model · 12k tok · $0.0042]) --json structured stdio protocol (JSON in, JSONL events out, SDK transport) --max-model-calls N cap provider calls across root, children, retries, titles, compaction, and judges (default 0 = unlimited) -h, --help usage -V, --version version ```
Unknown flags are an error (with a pointer to `--help`), missing model-flag values are errors, and `--help`/`--version` are handled before subcommand dispatch, so `graff login --help` prints usage instead of starting an OAuth flow. With no key configured at all, startup fails with the three quickest fixes spelled out rather than a bare env-var list.
`graff learn help` lists the local learning commands. Configuration, adapter protocols, statistical gates, activation semantics, and security limitations are specified in docs/local-learning.md.
**One-shot mode** makes the harness scriptable without the SDK: `graff -p "how many TODOs in src/?"` runs a full agentic turn (tools included), prints only the final answer on stdout (progress lines go to stderr), and exits non-zero on failure. There's no human to ask, so the permission gate denies anything not already allowed. Pre-approve commands in `.harness/settings.json` or pass `--yolo`.
- * *
REPL commands
[](https://github.com/justrach/codegraff#repl-commands)
A bare `/` opens the whole list as a filterable full-screen menu (type to narrow, Enter runs it); **Esc during a response interrupts the turn**: generation stops (it works from the moment the request is sent, including a slow provider connect), what already streamed stays in history with an `[interrupted]` marker, and you're back at the prompt. A bare Esc at the prompt clears the input line.
While a response streams you stay in control: besides Esc to interrupt, **Ctrl-T (`^T`) folds/unfolds the live "Thinking" block** in place, and the mouse wheel scrolls your terminal's own scrollback: the REPL doesn't grab the mouse, so scrolling up to re-read earlier output works like any normal terminal (parity with Claude Code). Folding the Thinking block is keyboard-only (`^T`). There is no click-to-fold.
Streaming Markdown is rendered for terminal readability: heading levels get a clear colored hierarchy; bullets, numbered items, nested lists, task checkboxes, and blockquotes use terminal-native markers; bold and inline code drop their raw delimiters; tables align; and fenced code stays copyable without decorative prefixes on body lines.
The interactive UI uses Codegraff's accent-only **Ensō** palette: vermilion coral marks the model, prompt, active selections, tools, and primary Markdown structure; ordinary text and supporting metadata stay neutral. Success, warning, and error colors remain semantic, the terminal background is never overridden, and `NO_COLOR` is respected. The quiet `enso` thinking animation is the stable default; `/animation random` restores per-request variety and the other animation names remain available.
The full catalog, straight from the `/` menu (a bare `/` opens it as a filterable full-screen picker; `/help` prints this same list in the REPL). `/models` pings every keyed provider catalog live on each listing, so new gateway rollouts appear without a restart.
``` /model <name> switch model/provider, fuzzy match (e.g. "sonnet", "opus") /models [health] list known models, context windows, compaction points; health shows live state /clear wipe the conversation and start fresh /new start a fresh autosaved session /rename <title> set the current session title /goal [30m] [text|pause|resume|status|clear] set a standing objective and work it autonomously; an optional 30s/30m/2h budget paces the run; pause/resume steering, status shows state, clear removes it /loop [30m] <prompt> the same autonomous run as /goal, without adopting a standing objective /review <target or instructions> run one isolated read-only review pass; no edits, delegation, or workflows /never [<text>|rm <id-or-text>] standing constraints that ride every subagent brief and survive compaction; bare lists them, rm <id-or-text> retires one (alias /constraint) /tell <session|all> <text> message a running graff: <session> is a DM (only it hears, any folder); all broadcasts to every graff on this device; /sessions lists who's around /peek <session> see what a live co-resident session is doing right now (its transcript tail) /routes [<set>|add <set> <frontier|mid|small> <provider/model>] your own priced model lanes across providers: view the set and which seat wins each lane now /plan toggle plan mode: read-only explore + propose; writes/edits denied /ultracode toggle persistent workflow mode; bare opens an on/off picker, or /ultracode on|off /fallback [allow|remove|off] opt-in cross-provider fallback for this workspace (same-provider rollout stays on) /key [provider secret] show API-key status; /key <provider> <secret> adds one live (+ Keychain) /login [codegraff|codex|kimi] OAuth sign-in (no key to paste); bare opens a picker (codex alias: oai) /keepcontext toggle keeping the conversation when /model switches wire format (default on) /effort reasoning depth: low|medium|high|... (codex, deepseek, codegraff; persists) /reasoning alias for /effort /fast codex only: priority service tier for lower latency (toggle, persists) /thinking stream reasoning live vs spinner only (toggle, persists) /title name the tab from your first prompt (AI session title; toggle, persists) /strict toggle "every message is a tool" mode /yolo toggle bash auto-approval (skip permission prompts) /trace toggle this run's JSONL event trace (and show its path) /privacy [local|aggregate|templates|examples] control prompt-learning data egress for this session /trajectory show this session's agent tree: turns + spawned subagents /agents list agent types: builtin personas + .harness/agents/*.md /skills [add|remove <name>] list SKILL.md playbooks + companion tools; add/remove enables or disables one /plugins list Cursor/Claude/Grok/Codex plugin trees graff is reading in place /hooks list lifecycle hooks and the built-in codedb guard /doctor read-only health check: goal/todo invariants, and why steering will or will not be appended /btw <question> ask one side question about this conversation: billed, never added, rides the parent cache prefix /compact compact history into a fresh context (OpenAI server-side when available) /rewind [n] list past prompts; /rewind <n> drops prompt n+after & reverts its file edits /image <path> attach an image to your next message (vision models only) /images open image URLs from the last response (e.g. issue attachments) in your browser /paste attach the clipboard image: macOS; also Ctrl-V (⌘V can't be captured) /bash <command> run a shell command directly /save [name] write the conversation to <name>.session.json (default: current) /resume [name] restore a saved conversation (no arg → interactive picker) /sessions list saved sessions in the cwd /todo show the current task list /jobs list background jobs /cost session token usage and cost /usage alias for /cost /debug live content-free observability HUD (turns, tokens, tools, last events) /tools session tool balance: codedb-pro vs zigrep vs native usage, gate refusals, skew /animation pick the thinking animation; persists to settings /theme [name] pick a color theme; /theme off resets to your terminal default; persists /fleet [on|off] federated DGM contribution (propose/submit/elite_pull) /mcp [add …] list MCP servers/tools; /mcp add <name> <cmd> [args...] connects one live /import-claude copy Claude/Cursor MCP servers and skills into ~/.codegraff and this repo /help list every command exit | ctrl-d quit (also /exit; ctrl-c on an empty line) ```
`/plan`, `/yolo`, and `/strict` change how the permission gate behaves for the session. See Permission modes.
`/goal <objective>` sets a standing objective that steers every turn as a live checklist, and starts working it right away: it runs turn after turn (plan, act, verify) instead of pausing for confirmation between routine steps. `/goal pause` stops the steering without losing the objective, `/goal resume` turns it back on, and `/goal status` shows the objective and its current state. `/loop <prompt>` is the same autonomous run for a one-off task, without adopting a standing objective. Either way the run stops on its own with a named outcome: accepted once the work is done, idle when the model stops making tool progress without claiming completion, cancelled or blocked when you step in or it needs you, exhausted when a safety limit is hit, and expired when a time budget runs out. Start with a duration to give the run one: `/goal 30m fix the flaky test` (also `45s`, `2h`). Each continuation turn then tells the model where it stands: which continuation it is on, how long the run has taken, how much of the budget is left, and one phase hint (explore, implement, finish, wrap up). Nothing is enforced except the stop itself, and the model is never cut off mid-turn. Subagents spawned during a timed run are told the parent's remaining time, minus a margin for the parent to integrate their results. `/review <target or instructions>` is the deliberately narrower path for code review: it suppresses goal/eval/ultracode steering, admits only local read/search tools and read-only shell inspection, and runs with fresh model-visible history. There are no implicit review-specific tool or model-call limits; the ordinary invocation budget is unlimited by default, while explicit `--max-tool-calls` and `--max-model-calls` settings still apply. Only the request and final report join the parent transcript. Use a later, explicit turn to fix accepted findings.
Skills
[](https://github.com/justrach/codegraff#skills)
A skill is a markdown playbook graff loads only when a task calls for it. Drop one in `.harness/skills/<name>/SKILL.md` (or `~/.harness/skills/` for every project), give it `name` and `description` frontmatter, and write the instructions in the body. Skills already written for Claude Code, Cursor, Grok, or Codex work as they are: `.claude/skills/`, `~/.cursor/skills-cursor/`, `~/.grok/skills/`, plugin `skills/` and Claude `commands/*.md` trees, and `~/.agents/skills/` are read in place (not copied). A Claude plugin works the same way it does in Claude Code: `commands/`, a root `SKILL.md`, inline `mcpServers`, and `${CLAUDE_PLUGIN_ROOT}` are honored. `/plugins` and `graff plugins` list the trees; `/plugins load <name>` shows one. `GRAFF_NO_PLUGINS=1` skips them.
Only the name and description enter the system prompt, so a large skill library costs one line each. The model calls the `skill` tool to pull a body in when it needs it, and a skill written mid-session is loadable straight away.
Two skills ship inside the binary: `skill-creator` (how to author and install new skills) and `mcp-config` (how to inspect and change the MCP servers below). `/skills` lists everything with its source, `/skills remove <name>` hides one, and `/skills add <name>` brings it back. See docs/skills.md for the full reference.
MCP servers
[](https://github.com/justrach/codegraff#mcp-servers)
Graff speaks both MCP transports directly: local stdio servers and remote Streamable HTTP servers. Servers already configured for Claude, Cursor, or Grok are read in place (plugin `mcp.json` / `.mcp.json`, `~/.claude.json`, `~/.cursor/mcp.json`, `.cursor/mcp.json`) and fill names graff does not already define; they still need `/mcp trust` or `--yolo`. Plugin manifests may also declare `mcpServers` inline or as a path (Claude's shape). `/plugins` and `graff plugins` show which plugin trees contributed. Other servers can be added from the shell or during a session:
graff mcp add context7 -- npx -y @upstash/context7-mcp graff mcp add mobbin --url https://api.mobbin.com/mcp graff mcp login mobbin # OAuth discovery + browser PKCE flow
In the REPL: /mcp add mobbin --url https://api.mobbin.com/mcp
The equivalent `.mcp.json` URL entry is `{"mcpServers":{"mobbin":{"url":"https://api.mobbin.com/mcp"}}}`. Remote responses may use either `application/json` or `text/event-stream`; Graff keeps `Mcp-Session-Id` state and sends `MCP-Protocol-Version` on requests. For OAuth-protected endpoints, `graff mcp login <name>` performs protected resource and authorization-server discovery, dynamic client registration, and a browser PKCE flow. Tokens are stored outside the repository under `~/.simple-harness-mcp` with user-only permissions and refreshed automatically. Static HTTP headers can alternatively be added with `--header 'Authorization=Bearer TOKEN'` (they are stored in `.mcp.json`, so prefer a restricted token and do not commit that file).
The line editor supports ↑/↓ history (persisted to `~/.simple-harness-history`), Tab completion (commands, and model names after `/model`), and emacs-style editing (Ctrl-A/E/W/U/K, Option+Delete, word moves). The selected model is remembered in `~/.simple-harness-model` and resumed next launch (`--model <name>` overrides). If the remembered provider/model is absent from the current catalog or its credentials are missing, graff falls back for that session with a note, without overwriting the preference. If the preferred provider later returns a clear authentication, access, removed-model, quota, or credit failure before producing text or running tools, graff tries the next configured provider and keeps the saved preference for a future launch. The prompt is a small statusline: `[model · Fast · Extra high · Plan · cwd /repo · 12345/800k tok (1%) · ⚡cached]`. Fast stays immediately beside the model. Active reasoning/workflow modes are visible at a glance: Low is green, Medium/Extra high/Ultra/Ultracode use the Codegraff coral accent, High/Plan are yellow, and Max/Strict are red. YOLO is reported as an explicit warning when enabled instead of occupying the compact prompt. Badges for unsupported settings are hidden instead of implying they apply. The tail shows context used vs the compaction budget, last cache hit, and (for metered providers) session spend. Errors aim to be actionable: `/resume nope` says the session file wasn't found and points at `/sessions`; an unknown `/foo` points at `/help`.
- * *
Permission modes
[](https://github.com/justrach/codegraff#permission-modes) By default graff **asks before doing anything that can change your machine.** File writes (`write_file`/`edit_file`), MCP tool calls, and any bash command that isn't read-only stop at a permission gate:
``` ⚠ rm -rf build/ [y]es once · [a]lways allow "rm" (saved to .harness/settings.json) · [n]o › ```
- **y** runs it once · **a** runs it _and_ remembers the rule · **n** denies it (the model is told and picks another path).
- **Always** appends a prefix rule to `.harness/settings.json` under `"allow"`, so that command never prompts again, this session or a future one. Pre-seed that file by hand to allow commands up front (it lives next to your hooks; the harness preserves the rest of the file).
- Read-only commands are auto-allowed and never prompt: `ls cat head tail wc grep rg pwd which file`, `git status|diff|log|show`, `zig build|fmt`, but only while every path stays inside the working directory (`cat /etc/passwd` still asks), and only as a plain command. A pipe, redirect, `&&`, or `$(…)` always prompts, so a second command can't be smuggled past a prefix match.
Three session-wide modes change the gate. Set on the CLI, or flip them live in the REPL:
| mode | turn on | what it does | | --- | --- | --- |
| **yolo** | `--yolo` · `/yolo` | Skip **every** prompt: bash, edits, and MCP all run without asking. For sandboxes, CI, and `-p`/`--json` runs where there's no human to answer. `--yolo` starts the session in it; `/yolo` toggles mid-session. |
| **plan** | `/plan` | Read-only: the model explores and _proposes_ a plan; the gate hard-denies writes, edits, MCP, and any bash beyond the read-only seed (even your saved allow-list) until you `/plan` again to execute. The prompt shows a yellow `Plan` badge. | | **strict** | `/strict` | "Every message is a tool": the model must call exactly one tool per message and finish with `attempt_completion`. Useful for deterministic, scriptable agent loops. |
**One-shot mode** (`graff -p "…"` or `--json`) has no human to answer the prompt, so the gate denies anything not already allowed. Pre-approve commands in `.harness/settings.json` or pass `--yolo`.
Hooks
[](https://github.com/justrach/codegraff#hooks) Shell hooks in `.harness/settings.json` run at tool-call boundaries — your own policy layer next to the built-in gate:
{ "hooks": { "pre_tool": [{ "match": "write_file|edit_file", "command": "./scripts/guard.sh", "suggest": "the mcp edit tool", "timeout_ms": 5000 }], "post_tool": [{ "match": "write_file", "command": "zig fmt ." }], "turn_end": [{ "command": "./scripts/notify.sh" }] } }
- **`match`** — tool name, `|`-separated list, or `*` (default). **`command`** runs via `/bin/sh -c` with the event JSON on stdin: `{"event","tool","input"}` (post_tool adds `"is_error"`,`"output"`).
- **`pre_tool`**: exit **2 blocks the call** — the hook's stderr becomes the tool error the model sees. Add **`suggest`** to name the sanctioned replacement; the denial becomes `blocked by pre_tool hook: <stderr> — use instead: <suggest>`, turning a blocked call into a one-call recovery instead of a guess-and-retry spiral. Any other exit code, timeout, or spawn failure allows — a broken hook never bricks the loop.
- **`post_tool`** runs sequentially after the call (a formatter finishes before the next tool runs); exit codes are ignored. **`turn_end`** fires once per completed turn. Default timeout 10 s (`timeout_ms` to change); stderr is capped at 4 KiB.
- * *
SDKs: TypeScript & Python
[](https://github.com/justrach/codegraff#sdks-typescript--python)
graff is scriptable from your own code. `graff --json` is a structured stdio protocol (JSON requests in, JSONL events out; `ask_user` is answered with a structured `{"type":"answer","text":"...","cancelled":false}` line) and `graff --schema` prints the machine-readable interface, and the **TypeScript and Python SDKs in `sdk/` are auto-generated from that schema**, so they never drift from the binary. On every release tag a GitHub Action rebuilds, regenerates, fails if the committed SDKs are stale, and publishes to npm (`@codegraff/sdk`) and PyPI (`simple-harness-sdk`).
Python
from harness_sdk import Harness
with Harness(yolo=True, model="gpt-5.5") as h: print(h.ask("what is 2+2?")) for ev in h.chat("read foo.txt"): print(ev["type"], ev)
// TypeScript import { Harness, runAgent } from "@codegraff/sdk";
// one-shot, streamed for await (const ev of runAgent({ prompt: "summarize README.md", model: "gpt-5.5", yolo: true })) { if (ev.type === "text") process.stdout.write(ev.text); if (ev.type === "turn") console.log("\ncost $", ev.cost_usd); }
// long-lived, multi-turn const session = Harness.init({ model: "claude-opus-4-8", yolo: true }).session(); console.log(await session.ask("what files are here?")); session.close();
Can't spawn a local process (edge runtimes, browsers, other machines)? Run `graff serve` and both SDKs ship matching **remote clients** that drive it over HTTP: `@codegraff/sdk/remote` (fetch-only: Workers/Deno/Bun/browsers) and Python's `RemoteHarness` (stdlib only). Same method surface, same event stream. Both remote clients accept URL/base64 `images` on a turn and preserve them as native provider vision parts through `graff serve`; no encoded pixels are flattened into prompt text. See `sdk/README.md`. A host that wants ACP (thought / tool / text `session/update`s)