terminal coding agent · Go · agentkit + llmkit
A coding agent that asks first.
wright runs an agent loop against your repository with the tools a coding task actually needs — read, write, edit, glob, grep, list, shell, fetch — in a full-screen TUI or headless behind -p. Any model llmkit can open works by name: Anthropic, OpenAI, Vertex, xAI, DeepSeek, OpenRouter, Groq, or a model already running in your local Ollama.
What makes it different is what sits between the model and your machine: approval-first permissions with a hard-deny floor no mode can lift, an OS sandbox around every shell command with the network off by default, keys that never enter that sandbox, no telemetry, and a SHA-256-chained audit log you can verify afterwards.
MIT · single static binary, no cgo · built on agentkit + llmkit · reads AGENTS.md
$ wright doctor wright dev (none, unknown) Sandbox ! container not running inside a container ✓ bwrap bubblewrap 0.11.0 ✓ landlock available ! seatbelt not supported on this platform · landlock ABI v8 (network restriction needs v4+) ✓ selected bwrap Tools ✓ git git version 2.55.0 ✓ rg ripgrep 15.2.0 Credentials (names only) · ANTHROPIC_API_KEY absent · OPENAI_API_KEY absent
safe by default
Policy decides whether. The sandbox limits what.
Neither one is a setting you have to discover. Both are on the first time you run it, and turning them off takes a flag, a typed confirmation and a red status bar.
Approval first
Every call is evaluated as a lattice — hard-deny → deny rules → ask rules → allow rules and session grants → the mode table — and the prompt shows the exact rule that would grant it, at the scope you choose.
Real shell analysis
Scripts are parsed with a bash AST, and every command through pipes, &&, subshells, loops and sh -c '…' is classified. Anything opaque — eval, $(…), sh -c "$X" — can never match an allow rule, in any mode.
OS sandbox, network off
bubblewrap or Landlock on Linux, sandbox-exec on macOS, the container itself when you run the image: the workspace is writable, the system read-only, $HOME a tmpfs, and the network off unless a +net rule matched or you granted it at the prompt.
Secrets stay local
Credential files are refused to every tool; anything matching *_KEY, *_TOKEN, *_SECRET, AWS_* is stripped from the sandbox environment; and secret-looking strings in tool output become [redacted: github…3f9a] before the model sees them.
No telemetry
The only sockets wright opens are your model provider, a web_fetch you approved, MCP servers you configured, and one loopback probe for Ollama. No analytics, no crash reporting, no update check. A test enforces it at the import level.
Verifiable audit trail
Every call, verdict, file change, command and model call is appended to a SHA-256-chained JSONL log per session. wright audit verify proves nobody edited it — including wright.
permission modes
Four modes, one floor underneath all of them.
--mode default|plan|auto-edit, or shift+tab in the TUI. Bypass is not a settings value — only --bypass-permissions reaches it, and it still cannot lift the floor.
| Mode | Read | Edit | bash: read-only | bash: mutating | bash: network | bash: opaque | sudo / privilege | Outside the workspace |
|---|---|---|---|---|---|---|---|---|
| default | allow | ask | allow | ask | ask | ask | deny | ask; secret deny |
| plan | allow | deny | allow | deny | deny | deny | deny | reads ask, writes deny |
| auto-edit | allow | allow | allow | ask | ask | ask | deny | ask; secret deny |
| bypass | allow | allow | allow | allow | allow, network still off | allow | deny | allow except hard-deny |
The first interactive session in a directory asks whether you trust it; answering no exits without starting anything. In a directory you trusted, the default row's Edit becomes allow — and only that: shell commands, the network, paths outside the directory, sensitive files, ignored files and the floor below still ask or refuse. Headless (-p) never asks and never takes it.
The hard-deny floor
Checked before rules and before the mode table, in every mode. Three hard denials cancel the run.
- reading a secret file; writing to
~/.ssh,.git/**,.wright/**,/etc/**or a shell rc file sudo,su,doas,pkexeccurl … | shand decode-to-shellrm -rfresolving to/,$HOMEor a workspace root- force-pushing or deleting a protected branch
git config core.hooksPath;PATH=overrides viaenv -i
Rules you can write
# a bash rule that also grants the sandbox network --allow 'bash(curl -sS https://pkg.go.dev/*) +net' # path rules are doublestar, workspace-relative --allow 'edit_file($WORKSPACE/internal/**)' --deny '*(**/*.tfstate)'
A script is allowed by rules only when every command in it matches and nothing is opaque. * is refused in allow lists, and an “always allow” offer never widens beyond two argv words. A rule names the program rather than a wrapper in front of it, and one prompt can accept several rules at once — space marks, enter applies.
headless & CI
In a script, an approval is an exit code — never a guess.
There is nobody to ask behind -p, so every ask becomes a denial that names the rule which would have permitted it, and the run exits 3. No timeout-approves path, nothing granted because the model asked twice.
$ wright -p "please list the files here" --output stream-json {"type":"run_started","run_id":"f8c90ea1a4f6be77"} {"type":"step","run_id":"f8c90ea1a4f6be77","step":1} {"type":"tool_call","step":1,"tool":{"name":"list_dir","id":"call_1","args":{"path":"."}}} {"type":"tool_result","step":1,"result":{"text":"<untrusted source=\"list_dir\">\n./\n main.go\n</untrusted>"}} {"type":"text","step":2,"text":"The workspace contains the files listed above. Done."} {"type":"result","output":"…","stop_reason":"completed","steps":2,"tool_calls":1,"exit_code":0} $ wright -p "run curl please" not approved: bash requires interactive approval in headless mode (re-run with --allow 'bash(curl -sS *) +net' --allow 'bash(head -c *)') $ echo $? 3 $ wright audit verify ok: 8 event(s), chain intact (…/audit/20260919-204014-788e.jsonl)
Exit codes: 0 ok · 1 provider or tool error · 2 usage · 3 approval required · 4 budget exhausted · 130 interrupted. Every tool result reaches the model fenced as <untrusted source="…"> — tool, file, web and MCP content is data, not instructions.
install
Homebrew, Go, or a container.
brew install --cask richardwooding/tap/wright
go install github.com/richardwooding/wright/cmd/wright@latest
podman run --rm -it --userns=keep-id -v "$PWD:/workspace:Z" -e ANTHROPIC_API_KEY ghcr.io/richardwooding/wright
wright doctor && wright
A single static binary — no cgo, no runtime dependencies. git, rg and bwrap are used when present, and wright doctor says what it found. Prebuilt macOS and Linux binaries are on the releases page.
status
Working end to end; v0.6.0 is the current release.
The TUI, headless mode and its three output formats, the permission engine and shell classifier, the sandbox backends, the tool set (including multi_edit and background bash jobs), workspace trust, skills discovery, MCP servers behind an interactive consent prompt, the explore sub-agent and custom agents, sessions and resume, snapshots and /undo, redaction, the audit log, model detection and cost, web search behind a configured provider, and trust-gated project settings all work today.
Not planned: an LSP client — wright uses your repository's own build, test and lint tools instead.