Vectrum
Sign In

README: a front page, and the reference moved into docs/

The README is now what Codey is, how to install it and where the docs
are. Everything it used to say lives on under docs/user/, one page per
topic, with the wording unchanged and the anchors pointed at the page
each section moved to; the layout and evals went to docs/internals/.
Release archives carry docs/user/ so the README's links resolve there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Jjonicommitted 1 parent 686688acommitcb2e118

Showing 18 changed files with 1980 additions and 1902 deletions.

+1−0.goreleaser.yamlView File
@@ -43,6 +43,7 @@ archives:
4343 files:
4444 - README.md
4545 - LICENSE
46+ - docs/user/*.md
4647
4748 checksum:
4849 name_template: checksums.txt
+54−1902README.mdView File
@@ -1,1828 +1,90 @@
1- # codey
1+ # Codey
22
3- Codey is a coding agent harness in Go. Point it at a repository, tell it to
4- make the failing test pass, and measure how often it actually does.
3+ Codey is a coding agent harness in Go. Point it at a repository, tell it to make the failing test pass, and measure how often it actually does.
54
6- The code is the byproduct. The deliverable is [METRICS.md](METRICS.md).
7- [PLAN.md](PLAN.md) is the build plan this follows.
5+ It is one static binary: a full-screen TUI on a terminal, a drop-in for `claude -p` in scripts and CI, and an [Agent Client Protocol](https://agentclientprotocol.com) agent that other UIs can drive, [T3 Code](https://t3.codes) included.
86
9- ## Install
7+ It works with an Anthropic API key, or with any gateway, proxy or local server that speaks the Messages API. If you already use Claude Code, Codey picks up its MCP servers, its permission rules, its CLAUDE.md files and, if you ask, its skills.
108
11- Codey is one static binary with nothing to install alongside it, so there are
12- three ways to get it and they differ only in what you need to have already.
9+ ## "Wait, what is this for?"
1310
14- **A release.** The [releases page](https://github.com/johannSo/codey/releases)
15- has an archive for Linux and macOS, each on amd64 and arm64, with
16- `checksums.txt` beside them. Unpack the one for your machine and put `codey`
17- somewhere on your `PATH`. The archive names carry no
18- version, so the latest one is always at the same address:
11+ Measuring. The code is the byproduct. The deliverable is [METRICS.md](METRICS.md): pass rate, edit success, turns and cost across an eval suite, with one row for each change that could move them. [PLAN.md](PLAN.md) is the build plan this follows.
1912
20- ```bash
21- # linux or darwin, amd64 or arm64
22- curl -L https://github.com/johannSo/codey/releases/latest/download/codey_linux_amd64.tar.gz | tar xz codey
23- ```
13+ Codey is still a full agent, with subagents, tabs and git worktrees, skills, MCP, web search, language-server diagnostics on every edit, and hooks. It is built so you can measure it.
2414
25- **With Go.** If you have Go installed, this builds the latest release and puts
26- it in `$(go env GOPATH)/bin`:
15+ ## Installation
2716
28- ```bash
29- go install github.com/johannSo/codey/cmd/codey@latest
30- ```
17+ > [!WARNING]
18+ > Codey needs a credential. The usual one is an Anthropic API key from [console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys):
19+ >
20+ > ```bash
21+ > export ANTHROPIC_API_KEY=sk-ant-...
22+ > ```
23+ >
24+ > - A gateway or local server: `-base-url` with `-api-key` or `-auth-token`, or `ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN`
25+ > - A Claude Code login: off unless you opt in with `-claude-code-auth`. That credential was issued to Claude Code, and using it from another client is outside Anthropic's terms for it. [Read this first.](docs/user/install.md#quick-start)
3126
32- `@latest` can be any tag, and `codey --version` reports the one you asked for.
33- The Go it needs is the one `go.mod` names; an older `go` command fetches that
34- toolchain by itself unless `GOTOOLCHAIN` says not to.
27+ ### Release binary
3528
36- **From source.** For working on Codey, or running a commit that is not
37- released yet:
29+ Linux and macOS, amd64 and arm64, with `checksums.txt` on the [releases page](https://github.com/johannSo/codey/releases):
3830
3931 ```bash
40- git clone https://github.com/johannSo/codey
41- cd codey
42- go build -o codey ./cmd/codey
43- ```
44-
45- A build from a checkout reports the version the source was last released as,
46- not the commit; `git log` is the better answer to "which one is this". The
47- examples in this README run it as `./codey`, from the directory it was built
48- in. Installed on your `PATH` it is just `codey`.
49-
50- ### macOS
51-
52- Release binaries are not signed or notarized. A download through a
53- browser is quarantined and Gatekeeper refuses to open it; `xattr -d
54- com.apple.quarantine codey` lifts that. The `curl` above does not quarantine
55- what it fetches, and neither does `go install`.
56-
57- ## Quick start
58-
59- ```bash
60- ./codey -C /path/to/a/throwaway/clone # full-screen TUI
61- ./codey -C /path/to/repo -p "why does TestFoo fail?" # one prompt
62- ```
63-
64- Codey needs a credential, and the ordinary one is an Anthropic API key, which
65- you create at
66- [console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys):
67-
68- ```bash
69- export ANTHROPIC_API_KEY=sk-ant-...
32+ # linux or darwin, amd64 or arm64
33+ curl -L https://github.com/johannSo/codey/releases/latest/download/codey_linux_amd64.tar.gz | tar xz codey
7034 ```
7135
72- Started with nothing at all, Codey stops and says how to get one rather than
73- failing on the first request.
36+ Put `codey` somewhere on your `PATH`. Release binaries are not signed. On macOS, a copy downloaded with a browser needs `xattr -d com.apple.quarantine codey` before it will run.
7437
75- A different endpoint — a gateway, a proxy, a local server speaking the Messages
76- API:
38+ ### With Go
7739
7840 ```bash
79- ./codey -base-url http://127.0.0.1:4000 -api-key ... -C .
80- # or ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN
81- ```
82-
83- Flags beat the saved config, which beats the built-in defaults; the config is
84- what [the setup wizard](#setup) writes on a first run. `-auth-token` sends
85- `Authorization: Bearer`, which is what OAuth tokens and most gateways want;
86- `-api-key` sends `x-api-key`. A non-default endpoint announces itself on
87- stderr, because your prompt and every file the agent reads go wherever that URL
88- points.
89-
90- On your own machine, if you are logged into Claude Code, Codey can borrow the
91- OAuth credential Claude Code keeps in `~/.claude/.credentials.json` instead of
92- needing a key. It does this only when asked: `-claude-code-auth` for one run, or
93- `"claude_code_auth": "on"` in [the config](#setup) — which the setup wizard and
94- `/settings` both offer — for every run. Be aware of what that is: the
95- credential is issued to Claude Code, and using it from another client is
96- outside Anthropic's terms for it, which API keys and gateway tokens do not
97- carry. That is why it is off until you turn it on, and why it is not something
98- to set up on a machine other people use.
99-
100- Even when asked for, it is the last resort. It is tried only when no flag and
101- no environment variable supplied a credential, and only against Anthropic's own
102- endpoint — a credential found on disk is not one you chose to send, so it is
103- never forwarded to a `-base-url` you pointed Codey at. `-no-claude-code-auth`
104- refuses it for one run, whatever `-claude-code-auth` and the config say.
105-
106- Earlier builds borrowed the login silently whenever nothing else was set. If
107- you relied on that, the first start after upgrading asks once, on the terminal,
108- whether to keep doing so, and saves the answer as `claude_code_auth` — yes or
109- no, so it is not asked again. A `-p` run, a pipe, and `codey models` have
110- nobody to ask, so they just report that there is no credential and how to get
111- one.
112-
113- On macOS Claude Code keeps this in the Keychain rather than a file, so there is
114- nothing to borrow there. An expired access token is refreshed automatically; the
115- credentials file is left alone unless the server rotates the refresh token, in
116- which case the replacement is written back — leaving the dead one there is what
117- would break your Claude Code login.
118-
119- Useful flags: `-model`, `-effort low|medium|high|xhigh|max`, `-max-turns`,
120- `-resume latest`, `-no-stream`, `-no-cache`, `-no-auto-compact`, `-v`, `-yolo`,
121- `-no-tui`, `-no-skills`, `-no-agents`, `-no-web`, `-no-hooks`,
122- `-no-diagnostics`, `-no-mcp`, `-claude-code-auth`, `-no-claude-code-auth`,
123- `-trust-project`. And the ones a script written for Claude Code passes, with
124- Claude Code's meaning — see [using it in place of
125- `claude`](#using-it-in-place-of-claude): `--output-format
126- text|json|stream-json`, `-c`/`--continue`, `--allowedTools`,
127- `--disallowedTools`, `--permission-mode`, `--dangerously-skip-permissions`,
128- `--append-system-prompt`, `--system-prompt`, `--verbose`, `--print`. Every flag
129- takes one dash or two.
130-
131- ### Flags and thresholds
132-
133- `-compact-at` (default 0.7) is where the conversation is normally folded into a
134- summary, keeping the last six messages verbatim. Early and rare is deliberate:
135- compaction is itself a model call with the history in it, and it is the single
136- biggest thing that breaks prompt caching.
137-
138- `-compact-floor` (default 0.97) is the fraction past which compaction stops
139- being optional. The normal threshold declines a history it cannot split safely —
140- a handful of enormous tool results is too few messages to cut six off the end of
141- — and below the floor that restraint is right. Past it, a verbatim tail is worth
142- nothing if the next request has no room to be sent, so the floor folds anyway
143- and settles for a one-message tail. `/compact` follows the same rule past the
144- floor, since with auto-compaction off it is the only move left. When even that
145- finds nothing to fold, Codey says so, rather than letting the next
146- request fail as a bare API error.
147-
148- `-context-window` (default 1,000,000) is what both fractions are measured
149- against; lower it for a model with less room.
150-
151- `-max-turns` caps one prompt, and by default there is no cap: a turn count is a
152- bad proxy for the thing it was guarding against. A task is not finished at turn
153- thirty and unfinished at thirty-one, and a run that stops there has thrown away
154- a job that was going fine. What bounds an interactive session is the context
155- window, the bill, and a person who can press esc.
156-
157- An unattended run has none of those, which is what the flag is for: set it in
158- `-p` mode and in CI, where nobody is watching and a model stuck repeating one
159- failing tool call would otherwise repeat it until the money ran out. When a cap
160- you asked for does trip, the conversation is left intact and the last thing the
161- model said comes back with the error rather than being thrown away — say what
162- to do next, or raise it.
163-
164- `-no-auto-compact` turns the automatic fold off for one run, the same as
165- `"auto_compact": "off"` in the config. The floor then stops the turn instead of
166- folding: the session stays intact, nothing is summarized behind your back, and
167- `/compact` is what makes room. In `-p` mode the stop is a non-zero exit.
168-
169- `-no-agents` leaves out the subagent tools, so the agent works alone. `-no-web`
170- takes away web access for one run, whatever the config says; like `-yolo` in
171- reverse, it can only subtract.
172-
173- ## The TUI
174-
175- On a terminal, the interactive mode is a full-screen [Bubble Tea](https://github.com/charmbracelet/bubbletea)
176- app. It opens on a welcome block — Codey, then the model, the endpoint and the
177- root:
178-
179- ```
180- ▄██████▄ codey v0.6.0
181- █ ██ █ Opus 5 · anthropic
182- ███▀▀███ ~/Work/GO/coding-harness
183- ▀█▀▀█▀ /help · ↑ earlier prompts · ! shell · shift+tab approvals
184- ```
185-
186- The gopher is drawn in half-blocks, so it needs no patched font, and it is
187- dropped rather than sliced on a terminal too narrow to hold it beside a
188- readable amount of text: half a mascot reads as a rendering bug, where the
189- three lines alone read as a choice. The fourth line is the handful of things
190- there is no other way to find out, and it costs no height at all: the gopher is
191- four rows tall and the text was three. It is dropped whole rather than
192- truncated when the terminal is too narrow for it, because half a sentence of
193- hints is worse than none. From there it settles into a transcript above a
194- prompt:
195-
196- ```
197- ⏺ Let me see what the test expects before changing anything.
198-
199- ⏺ ReadFile(path=internal/agent/loop_test.go)
200- ⎿ 1: package agent
201- …
202-
203- ⏺ EditFile(path=internal/agent/compact.go)
204- 4 additions, 1 removal
205- @@ -50,7 +50,9 @@
206- + if window == 0 {
207-
208- ✢ Noodling… (14s · turn 3 · ↓ 1.3k tokens · esc to interrupt) ● high · /effort
209- ╭──────────────────────────────────────────────────────────────────────────────╮
210- │ › _ │
211- ╰──────────────────────────────────────────────────────────────────────────────╯
212- joni ✳ Opus 5 ◐ 62.2k (6%) ↑ 4.2k ⟳ 58.0k ↓ 1.3k $0.0835
213- ⏵ approvals on (shift+tab to cycle) · /help for commands
214- ```
215-
216- Every event hangs off a dot in the left gutter, and everything belonging to it
217- is indented under that dot, so a screen of tool output still reads as a
218- sequence of things that happened. Diffs are shown in full and in colour with a
219- count of what changed; other output is elided after six lines. A finished
220- answer closes with how long it took — `took 1m 30s · 3 turns` — because the
221- activity line counts those seconds only while the turn runs, and that number
222- is worth keeping in the scrollback beside the answer it paid for.
223-
224- What the model says is Markdown, and it is rendered as Markdown — headings,
225- weight, lists, block quotes, rules, and tables laid out in columns:
226-
227- ```
228- ⏺ Findings
229-
230- The refresh path drops the rotated token. Three things are going on:
231-
232- 1. refresh() returns the new pair but never writes it back
233- 2. the old refresh token is already dead by then
234-
235- File │ Line │ Change
236- ────────────────────────┼───────────────────────┼───────────────────────
237- refresh.go │ 84 │ write back
238-
239- func refresh(tok string) (Creds, error) {
240- // TODO: persist the rotation
241- }
242- ```
243-
244- Fenced code is syntax-highlighted by [chroma](https://github.com/alecthomas/chroma),
245- and every colour — prose, headings, keywords, strings — comes from the theme in
246- force, so `/setup` repaints Markdown along with everything else. Headings drop
247- their hashes: the levels are told apart by colour and weight instead. It
248- renders as it streams, so a table fills in row by row.
249-
250- Only what the model says goes through this. Tool results and the output of your
251- own `!` and `$` commands are program output and are shown as they came, and
252- `-no-tui` prints raw text so `-p` stays pipeable.
253-
254- The status bar reports the context gauge — the last turn's input size against
255- the window, which drops when the conversation is compacted — and separates
256- fresh input tokens from cache reads, because the gap between the two is what
257- [METRICS.md](METRICS.md) is about. The prompt box grows with what you type, up
258- to six lines.
259-
260- The conversation folds itself into a summary at 70% of the window, and
261- `/compact` does it on demand — before a long task, say, or when the gauge is
262- climbing faster than the work is. `/compact` waits for the current turn to
263- finish, because it rewrites the history that turn is building on. Afterwards
264- the gauge goes blank rather than keep reporting a number the compaction just
265- made untrue; the next turn measures the window again and it comes back. At 97%
266- compaction stops being optional, and it can be turned off altogether: see
267- [the flags](#flags-and-thresholds) below.
268-
269- | Key | Does |
270- | --- | --- |
271- | `enter` | send |
272- | `/` | open the command menu — see below |
273- | `!` | run a shell command yourself — see below |
274- | `$` | the same, through `python3` |
275- | `ctrl+j` | newline without sending |
276- | `ctrl+v` | paste a picture in — see [images](#images) below |
277- | `shift+tab` | cycle the approval mode |
278- | `ctrl+b` | open the agents sidebar — see below |
279- | `ctrl+t` | open a second session in a tab — see below |
280- | `alt+1`…`alt+9` | go to a tab |
281- | `ctrl+pgup`/`ctrl+pgdn` | walk the tabs |
282- | `alt+w` | close the tab on screen |
283- | `ctrl+r` | expand elided tool output |
284- | `pgup`/`pgdn` | scroll the transcript |
285- | `esc` | interrupt the turn |
286- | `ctrl+c` | interrupt the turn; again when idle to exit |
287-
288- Interrupting ends the turn, not the session. A tool still running when you
289- press `esc` is answered with an error saying it was interrupted, and one that
290- had already finished keeps its result, so the conversation is never left with
291- a `tool_use` nothing answered — which the API rejects on every request after
292- it, the next thing you type or a `-resume` weeks later. A session saved in
293- that state by an older build is repaired when it is reopened.
294-
295- Commands: `/allowed`, `/clear`, `/close`, `/compact`, `/effort`, `/exit`,
296- `/help`, `/image`, `/model`, `/mouse`, `/new`, `/resume`, `/settings`, `/setup`,
297- `/skills`, `/worktree`, and one per discovered skill.
298-
299- ### Images
300-
301- `ctrl+v` pastes a screenshot into the prompt. What appears in the box is a
302- marker:
303-
304- ```
305- ╭──────────────────────────────────────────────────────────────────────────────╮
306- │ › the button is in the wrong place [image 1] can you find the style for it │
307- ╰──────────────────────────────────────────────────────────────────────────────╯
308- ```
309-
310- The marker is ordinary text. It can be moved, and deleting it takes the paste
311- back — only the markers still standing when you press `enter` become pictures,
312- each one in the message exactly where it stood in the sentence. So a prompt
313- can point at one of two screenshots, or put a picture between two paragraphs,
314- without any of that being a separate mode.
315-
316- No terminal sends image bytes to the program running inside it, so the
317- keystroke is a signal rather than a delivery: Codey reads the system clipboard
318- itself, with the same helper you would run by hand — `wl-paste` on Wayland,
319- `xclip` on X11, `pngpaste` or AppleScript on macOS, PowerShell on Windows. On
320- Linux that means one of `wl-clipboard` or `xclip` has to be installed; when
321- neither is, the paste says so rather than failing quietly.
322-
323- Some terminals keep `ctrl+v` for themselves and paste the clipboard's text
324- form, which for a screenshot is nothing at all. An empty paste is therefore
325- taken as an attempt too, and answered by reading the clipboard directly — so
326- the key works on both kinds of terminal. Dropping an image file on the window
327- works as well, because that arrives as its path. With no picture on the
328- clipboard `ctrl+v` pastes the text on it instead, which is what the key always
329- meant — the textarea's own binding for it never reached the screen.
330-
331- A path to an image that is still in the prompt when you press `enter` becomes
332- that image too, wherever it came from. That is the one that always fires:
333- catching a paste as it happens needs the terminal to bracket it, and not all
334- of them do — a terminal that writes the picture to a file and pastes the path
335- as plain keystrokes is caught here instead. A path to something that is not a
336- readable image is left alone, so a prompt about a file still reads as one.
337-
338- `/image <path>` attaches a file by name and needs no clipboard helper at all,
339- which makes it the one that always works — over ssh, in a container, or on a
340- terminal that swallows the key. Bare, `/image` takes whatever the clipboard
341- has.
342-
343- PNG, JPEG, GIF and WebP go up; the format is decided by the bytes rather than
344- by the extension, and anything over about 3.7MB is refused with its size,
345- because the API will not take it.
346-
347- ### Tabs
348-
349- `ctrl+t` — or `/new` — opens a second conversation in a tab. A tab is a whole
350- session: its own agent, its own history, its own approvals, its own subagents
351- and its own file under `.codey/sessions`. Nothing crosses between them but the
352- model you are on and the terminal they share, so a turn running in one tab is
353- not interrupted, approved or compacted by anything you do in another.
354-
355- 1 ◍ fix the parser 2 ● read the spec 3 new session
356-
357- The strip appears when there is more than one tab and goes away when there is
358- not: a single session looks exactly as it did before, down to the row count. A
359- tab is named after the first thing asked of it. `◍` means something is running
360- in there; `●` means it said something while you were somewhere else.
361-
362- A new tab starts in the working root of the one you opened it from, so a
363- second conversation about a worktree is in that worktree. `alt+w` or `/close`
364- closes a tab — it refuses while something is still running there, and closing
365- the last one leaves Codey.
366-
367- `/worktree` branches the work off into a git worktree under
368- `~/.config/codey/worktrees`. It reads its argument three ways, and which one
369- you get is visible in what you typed:
370-
371- /worktree dark-mode a name — made and moved into, nothing else
372- /worktree add a dark mode toggle a task — named for you, then started on
373- /worktree resume a chooser of the trees that already exist
374-
375- One word is a name and is used as typed, so the quick case never waits on a
376- network call. A phrase is a description of the work: a small model turns it
377- into a name, the tree is made, and the agent is then set to work on it in the
378- new directory. Whatever the model answers is slugified and bounded before it
379- reaches the filesystem, and if the naming call fails the task's own opening
380- words are used instead — a name is not worth failing the command over.
381-
382- The tree is made by `~/.config/codey/worktree.sh`, which is written once with a
383- default and is yours to edit afterwards. Codey never overwrites it, so a script
384- taught to copy a `.env` or install dependencies keeps doing so. The contract is
385- two lines wide: the name arrives on stdin, and the absolute path of the tree is
386- the last line of stdout. The script decides where the tree lands, so moving
387- them somewhere else is an edit to that file and nothing more.
388-
389- What moves is the work — what the tools read and write, where `!` commands run,
390- and where subagents are spawned. What stays is the session: the conversation is
391- still filed in the checkout you started in, alongside the event log, so one
392- directory holds the whole history of what was done from it rather than a trail
393- scattered across worktrees. The tree is recorded in the session, so reopening
394- that conversation — with `/resume` or `-resume` — puts the work back in it. If
395- the tree has since been deleted, Codey says so and carries on in the main
396- checkout.
397-
398- `/worktree resume` goes the other way: into a tree that already exists, with a
399- new chat in it. Bare, it opens a chooser of every worktree this repository has
400- — git is asked, so a tree the script put somewhere unusual is still found —
401- most recently touched first, with the branch checked out in each. Typing
402- narrows the list: the match is fuzzy, so `dkmd` finds `dark-mode`, and a
403- branch name finds its tree too. `/worktree resume <name>` skips the chooser.
404-
405- The checkout you started Codey in is offered alongside the trees, marked as
406- such, because once the work has moved there has to be a way back. What you get
407- in the tree is a *new* conversation, not the old one: the chat on screen is
408- saved first and stays where it is, and the next turn is filed under a new
409- session that records the tree — so `/resume` can go back to either. Reach for
410- `/worktree resume` when you want to work in a tree again, and for `/resume`
411- when you want a particular conversation back.
412-
413- `/resume` switches to an earlier conversation without leaving Codey. Bare, it
414- opens a chooser: one row per saved session, named by the first thing you asked
415- it, newest first, with how long ago it was and what it ran on. `/resume <id>`
416- goes straight there. Either way the conversation on screen is written out
417- first, the chosen one is replayed into the transcript, and the next turn saves
418- into it.
419-
420- The list is scoped to the working root, and so is `-resume`: a session recorded
421- in `/home/joni/test` cannot be opened from `/home/joni/test2`. Sessions are
422- filed under the root they were made in to begin with, and the root each one
423- records is checked again on the way out, so a session file copied between
424- checkouts is refused rather than resumed into a tree where none of the paths it
425- discusses exist.
426-
427- ### Questions
428-
429- The agent can stop in the middle of a task and ask you something. It calls
430- `ask_user`, the turn parks, and a box comes up with the options it thought of:
431-
432- ```
433- ● the agent has a question
434- ╭──────────────────────────────────────────────────────────────────────────────╮
435- │ Storage · question 1 of 2 │
436- │ Sessions have to live somewhere once the process exits. Where should they go?│
437- │ │
438- │ ❯ 1. Postgres (recommended) │
439- │ what the rest of the stack already runs; no new service to operate │
440- │ 2. SQLite │
441- │ a file on disk, and one fewer thing to have running │
442- │ 3. Keep them in memory │
443- │ simplest, and they are gone when you quit │
444- │ 4. something else — type it yourself │
445- │ │
446- │ ↑↓ move · enter choose · esc skip the question │
447- ╰──────────────────────────────────────────────────────────────────────────────╯
448- ```
449-
450- | Key | Does |
451- | --- | --- |
452- | `↑`/`↓` | move the cursor — the wheel does the same |
453- | `1`–`9` | answer with that option outright |
454- | `space` | tick a box, where the question allows several answers |
455- | `enter` | take the row under the cursor, or confirm what is ticked |
456- | `←` | back to the previous question, to change an answer |
457- | `esc` | skip: the agent is told to decide for itself and say what it chose |
458-
459- The last row is always there and always free text, so you are never cornered
460- by three options that are all wrong; typing an answer is the same key as
461- picking one. A click does what enter would — on the description under a label
462- as well as on the label, because that is what it looks like you are clicking.
463-
464- This is the approval box's twin, and it works the same way underneath: the
465- agent blocks on a channel, the UI keeps running, and an interrupt releases it.
466- Where the two collide the approval goes first — a tool call cannot start until
467- someone says so, and the question is still there behind it. A question whose
468- turn is interrupted goes with the turn, since the tool call waiting for the
469- answer no longer exists.
470-
471- Only the session's own agent gets the tool. A subagent's report goes to its
472- parent, and the parent is not a person to ask.
473-
474- Outside the TUI the same questions are asked a line at a time, and a run with
475- nothing on stdin — a `-p` one-shot in a script — gets no answer, which the tool
476- reports as a dismissal so the model decides for itself and says so.
477-
478- ### The mouse
479-
480- | Does | How |
481- | --- | --- |
482- | scroll the transcript | the wheel, anywhere |
483- | select | drag; double-click a word, triple-click a line, shift+click to extend |
484- | copy | let go — the selection lands on the clipboard on release |
485- | choose | click a menu row, a model, a session, a worktree, the effort slider, an approval, the mode line |
486- | work with agents | click the agents pill or a row; click the prompt to type again |
487-
488- The prompt box is keyboard-driven: a click does not move the caret in it, and
489- what you have typed is the one thing the mouse cannot select. Dragging against
490- the top or bottom edge of the transcript scrolls, so a selection can be longer
491- than the screen; it keeps going while the pointer moves, because a terminal
492- reporting button events says nothing about a hand held still.
493-
494- Codey runs in the alternate screen, where the terminal has no scrollback
495- of its own to roll — so asking for mouse reporting is what makes the wheel work
496- at all. The cost is the terminal's own text selection, which stops being the
497- terminal's the moment drags are reported to the program. That is why the
498- selection above exists: it is the replacement, and copying goes out over both
499- OSC 52 (which works over ssh) and the desktop clipboard, because terminals
500- disagree about which of the two they allow.
501-
502- `/mouse` hands the mouse back. Selection and copy become the terminal's again,
503- exactly as before this existed, and the wheel goes quiet. `shift`+drag usually
504- reaches the terminal's own selection without turning reporting off at all.
505-
506- A slash at the start of an empty prompt opens the command menu, which filters
507- as you type — a name that starts with what you typed before one that merely
508- contains it:
509-
510- ```
511- ╭──────────────────────────────────────────────────────────────────────────────╮
512- │ › /s │
513- ╰──────────────────────────────────────────────────────────────────────────────╯
514- ❯ /skills the instruction sets found at startup
515- /systematic-debugging find the cause before changing code (project)
516- /brainstorming explore an idea before building it (personal)
517- ```
518-
519- `↑`/`↓` move, `tab` or `enter` completes the highlighted name into the prompt —
520- completing is not sending, so there is room to type arguments for a skill or a
521- level for `/effort` — and `esc` dismisses the menu without touching what you
522- typed. The menu offers exactly the commands the TUI answers, so nothing it
523- suggests can come back as "unknown command".
524-
525- ### Earlier prompts
526-
527- `↑` from the first line of the prompt box walks back through what has been
528- typed into this checkout before, `↓` walks forward again, and `esc` abandons
529- the walk. The empty box says so — its placeholder names the key, and sheds
530- clauses rather than characters as the terminal narrows, since a hint truncated
531- to `! shell · ↑ earli` advertises nothing while looking like a bug. `ctrl+p` and `ctrl+n` do the same, because they already do in every
532- other list Codey draws.
533-
534- The rule about the first line is what lets one key do two jobs without a mode:
535- anywhere below it, `↑` is moving the cursor through a paragraph you are still
536- writing, which is what it looks like it is doing. Walking all the way forward
537- again gives back the half-written prompt the walk started from — losing that is
538- the one thing a history feature can do that is worse than not existing — and
539- editing a recalled line ends the walk, because the line is yours now.
540-
541- A `!` or `$` line and a prompt are different kinds of thing, so they are
542- offered back separately: `↑` at the shell prompt walks shell commands, and the
543- prompt aimed at the model never hands you a `go test` to send as prose.
544-
545- The last few hundred entries are kept in `.codey/history` inside the checkout
546- Codey was started in — not per session, which would forget the thing you are
547- most likely to want back, and not one file per machine, which would offer you
548- the test command of a project you are not in. Two tabs append to it rather than
549- rewriting it, so neither loses the other's lines. It is inside the state
550- directory, which `.gitignore` already covers; delete the file and the prompt
551- box simply stops remembering.
552-
553- ### Running something yourself
554-
555- `!` on an empty prompt points the box at a shell instead of at the model; `$`
556- points it at `python3`. The marker replaces the `›`, the frame changes colour,
557- and `enter` runs the line where the agent's own `bash` tool would have run it —
558- from the working root, with the same timeout and the same output cap.
559-
560- ```
561- ╭──────────────────────────────────────────────────────────────────────────────╮
562- │ ! go test ./internal/agent/ │
563- ╰──────────────────────────────────────────────────────────────────────────────╯
564- ```
565-
566- ```
567- ⏺ ! go test ./internal/agent/
568- ⎿ exit code: 1 (2.3s)
569- --- stdout ---
570- --- FAIL: TestCompactKeepsTheLastExchange (0.00s)
571- compact_test.go:88: history has 2 messages, want 4
572- … +9 more lines (ctrl+r)
573- ```
574-
575- **The agent is shown what you saw.** The command and its output are appended to
576- the conversation as a user message, so the next thing you type can be "fix
577- that" and the model already has the failure. Nothing is sent at that moment —
578- it waits in the history and goes up with your next real turn, so several
579- commands can be run before saying anything about them.
580-
581- What reaches the model is the capped report, not the raw firehose: a noisy
582- command cannot cost more context from the prompt box than it would through the
583- tool. The transcript collapses it further to six lines, and `ctrl+r` expands to
584- exactly what the model was given, so the two never drift.
585-
586- `esc` leaves the mode, as does `backspace` over the marker. The mode is
587- per-command — after a run the prompt returns to normal, the way a shell prompt
588- comes back. One command runs at a time; while it does, `esc` interrupts it and
589- a message you type waits rather than jumping ahead of the output it is about.
590- A `!` typed mid-sentence is an exclamation mark.
591-
592- These bypass the approval policy by design: you typed the command, so there is
593- nobody left to ask. They are also the one part of the TUI that runs something
594- without the model's involvement — worth remembering when the root is not a
595- throwaway clone.
596-
597- `/model` asks the endpoint what it serves — `GET /v1/models`, paginated — and
598- opens a picker on the answer:
599-
600- ```
601- Select model
602- from anthropic · ↑↓ move · enter apply · esc cancel
603- 1. Claude Fable 5.1 claude-fable-5-1 · 1.0M in · $10 / $50 per Mtok
604- 2. Claude Opus 5 claude-opus-5 · 1.0M in · $5 / $25 per Mtok ✓
605- ❯ 3. Claude Sonnet 5 claude-sonnet-5 · 1.0M in · $2 / $10 per Mtok
606- 4. Claude Haiku 4.5 claude-haiku-4-5-20251001 · 200.0k in · $1 / $5 per Mtok
607- ```
608-
609- The cursor starts on the model in force, which is also the one marked. The id,
610- the context window and the list price come from the endpoint and from the
611- pricing table; a model neither knows about simply says less. The switch applies
612- from the next turn — the conversation so far is replayed to the new model — and
613- costs already incurred stay priced at the model that incurred them, because a
614- session that switched models was not all billed at the model it ended on.
615-
616- Not every endpoint serves that route. When the fetch fails the picker says so,
617- names the host and the reason, and closes; `/model <id>` and the `-model` flag
618- still switch without asking anyone's permission.
619-
620- `/effort` opens a slider in place of the prompt, on the level in force — an
621- unset level is the API's own default, `high`, so the marker starts there rather
622- than at an end:
623-
624- ```
625- Faster Smarter
626- ──────────────────────────────────────▲───────────────────────────────────
627- low medium high xhigh max
628- ←→ move · 1-5 jump · enter apply · esc cancel
629- ```
630-
631- Moving the marker changes nothing until `enter`; `esc` leaves the level alone.
632- `/effort xhigh` still applies a level directly for anyone who can type it, and
633- either way it is the `-effort` flag reached from the keyboard. The level in
634- force shows above the prompt on the right. A terminal too narrow for five
635- labels gets `◀ high ▶` and the same keys rather than a track with the words
636- sliced in half.
637-
638- `/model` and `/effort` change the session. `/settings` and `/setup` change what
639- the next session starts with — see [Setup](#setup).
640-
641- `shift+tab` cycles between asking before every mutating call and accepting them
642- all, which is `-yolo` reached from the keyboard. The mode line says which is in
643- force at all times, and the switch is recorded in the transcript, because a
644- mode that silently lets the agent write to disk is not one to keep in a corner.
645-
646- Approvals appear inline as a bordered prompt, answered by number or by the key
647- in brackets — `y`, `n`, `a` for this exact command, `t` for the whole tool. The
648- agent goroutine blocks until you answer, which is the point: the tool has not
649- run yet. Interrupting while that prompt is open denies the call and cancels the
650- turn rather than leaving the agent waiting on a channel nobody will read.
651-
652- The TUI is skipped automatically when stdin or stdout is not a terminal, and by
653- `-p` and `-no-tui`, which use the line-based renderer instead. Both front ends
654- consume the same `agent.Event` stream, so neither can show something the other
655- cannot.
656-
657- ## Subagents
658-
659- The agent can start other agents. `spawn_agent(preset, task)` returns
660- immediately with a short id — `a1`, `a2`, … — and that agent runs in the
661- background on its own goroutine while the main agent carries on with whatever
662- it was doing. `agent_wait(id)` blocks until that one finishes, for the times
663- the parent genuinely cannot proceed without the answer.
664-
665- A subagent is a whole agent, not a subroutine: its own conversation, its own
666- context window, its own model, its own turn loop. The parent never sees its
667- turns — only the report it ends with. That is the point. Work that would have
668- filled the parent's window with file listings fills the child's instead, and
669- comes back a paragraph long.
670-
671- Three presets, compiled in rather than configurable, because a preset the model
672- can invent is a blast radius the model can invent:
673-
674- | Preset | For | Turn budget |
675- | --- | --- | --- |
676- | `explore` | finding things, and reporting paths and line numbers | 60 |
677- | `plan` | working out how a change should be made, without making it | 80 |
678- | `general` | a self-contained piece of work, edits and tests included | 200 |
679-
680- The session itself runs uncapped; these are the one place a budget stays, and
681- they are the reason why. A subagent works alone, several run at once, and the
682- model that spawned it is not checking on it — so each one stops eventually, at
683- a number far past what the job takes. Hitting it means the agent is stuck, not
684- that it was busy, and the pane is the faster way out: `ctrl+b`, then `k`. A run
685- that did ask for `-max-turns` keeps it as a ceiling over these, because a child
686- free to sail past the cap would be a hole in exactly the guarantee the flag is
687- bought for.
688-
689- All three get the full tool set — read, glob, grep, edit, write, bash, skill —
690- and none of them gets `spawn_agent`, so subagents cannot spawn subagents.
691-
692- **A preset pins no model and no effort level.** Both are arguments to
693- `spawn_agent`, because what to spend is a judgement about the job and the agent
694- holding the task is the one making it: `model` picks what runs it — a small
695- fast model for mechanical search, a strong one for work that has to be right —
696- and `effort` picks how hard it thinks, `low` through `max`. Omit either and it
697- inherits the session's. A pinned id here would be a guess about someone else's
698- endpoint as well as about the job: point Codey at a gateway that serves
699- neither, and every spawn of that preset fails before its first turn.
700-
701- The models it may choose between are in its own system prompt: Codey asks
702- the endpoint what it serves at startup — `GET /v1/models`, the same route the
703- picker uses — and lists them newest first, with the rule that the newest is the
704- one to reach for unless you asked for another or it has already failed this
705- session. An endpoint that does not answer that route simply gets no listing,
706- and the agent is told that naming no model runs the subagent on whatever the
707- session is using. The list is read once, because it sits in the cached prefix
708- and a prompt that changes mid-session throws the cache away. Subagents do not
709- get this paragraph — they have no such choice to make.
710-
711- `output_config.effort` is a per-model capability, not a universal parameter.
712- Haiku 4.5 rejects a request carrying it, and `xhigh` did not exist before Opus
713- 4.7 — so a request for a level its model does not take goes out without it,
714- rather than failing with a 400 that reads like a bug in Codey. Where that
715- happens to a subagent the answer says so outright (`claude-haiku-4-5 takes no
716- effort level, so "low" was not applied`) instead of letting the parent believe
717- it got what it asked for, and the effort pill in the status line reads `not on
718- Haiku` rather than claiming a setting that is not in force.
719-
720- A model this build has never heard of — a gateway serving something else, a
721- family released after it — is treated the same way: the level is left off. The
722- two mistakes are not symmetric. Sending effort to a model that lacks it is a
723- 400 that ends the run before any work happens; omitting it costs the level and
724- says so in the pill. Gateway decoration is seen past first, so
725- `anthropic.claude-opus-5`, `openrouter/anthropic/claude-opus-5` and
726- `claude-opus-5@20260401` are all recognised as the model they name — for
727- pricing and for the thinking parameter too.
728-
729- Every spawn goes through the ordinary approval box, which shows the preset, the
730- model and the task. It is not rememberable per task — the task text differs
731- every time, so "always this exact one" would never match twice — but "always
732- allow this tool" still applies, for anyone who has decided to trust spawning as
733- such. A subagent's own tool calls go through the same policy, and the box says
734- which agent is asking.
735-
736- Four run at once; further spawns queue. Two agents editing one file is not a
737- special case: `edit_file` already refuses a file whose mtime moved since that
738- agent read it, so the second one is told to read it again rather than
739- clobbering the first.
740-
741- A report reaches the parent exactly once, whichever way it gets there. Both
742- paths — `agent_wait` returning it as a tool result, and the front end putting
743- it into the conversation as a message — claim it from the supervisor first, and
744- only one claim wins. Without that, an agent that finished before its
745- `agent_wait` call was even emitted was handed over twice: once as the result,
746- once as a message a turn later, which is the context cost the whole feature
747- exists to avoid. A wait that loses the claim answers with a line saying where
748- to find the report instead of repeating it.
749-
750- **The report comes back on its own.** When a subagent finishes, its report is
751- appended to the main conversation as a user message and the main agent starts a
752- turn on it — you do not have to type anything. It is held back only while a
753- turn is already running, while an approval is on screen, while a `!` or `$`
754- command is running, or while you have text in the prompt box: a turn starting
755- under your fingers would blur the input and eat what you were typing. It goes
756- up the moment that clears. A failed or killed agent is reported the same way,
757- so the parent is never left waiting on a result that is not coming.
758-
759- ### The agents sidebar
760-
761- `ctrl+b` opens a 30-column pane on the right and gives it the keyboard; the
762- transcript reflows into what is left rather than being covered by it.
763-
764- Open and focused are separate things. The pane answers every key while it has
765- focus — that is what lets `k` be a kill rather than a letter — so **click the
766- prompt to type again**, and the pane stays where it is. Click a row to hand the
767- keys back. `ctrl+b` always moves towards the pane: it opens a closed one,
768- focuses an open one, and only closes a pane that already has the keys. Which
769- one is live is visible in two places: the heading carries the accent and the
770- prompt frame goes dim, or the other way round.
771-
772- ```
773- ⏺ Two of those are independent, so I will farm │ Agents 2 ⏵
774- them out. │
775- │ ❯ ✳ a1 plan $0.23
776- ⏺ SpawnAgent(preset=explore, task=…) │ Opus · 2m04s · 6 turns
777- ⎿ started a2 (explore) on claude-haiku-4-5, s…│ ● a2 explore
778- │ Haiku · waiting · 1 turn
779- │
780- │ ↑↓ move · enter open
781- ✢ Deliberating… (8s · turn 5 · esc to interrup…│ p pause · k kill · esc back
782- ╭──────────────────────────────────────────────╮│
783- │ › ask for a change, /help, ! for a shell ││
784- ╰──────────────────────────────────────────────╯│
785- joni ✳ Opus 5 ⏵ 2 agents │
786- ⏵ approvals on (shift+tab to cycle) · ctrl+b a…│
787- ```
788-
789- Each agent is two lines: the status glyph, the id and the preset, with the cost
790- at the right, and under it the model, how long it has been going and how many
791- turns it has taken. The model is the family alone — thirty columns will not
792- hold `claude-haiku-4-5` as well as the numbers — and the full id is in the
793- header of the thread, one keystroke away.
794-
795- | Key | Does |
796- | --- | --- |
797- | `↑`/`↓` | move |
798- | `enter` | open that agent's thread in the main pane |
799- | `esc` | back to your own thread; again to close the pane |
800- | `p` | pause or resume |
801- | `k` | stop it |
802- | click the prompt | type again, leaving the pane open |
803-
804- The statuses are queued, running, waiting — blocked on an approval of yours —
805- paused, done, failed and killed. Pause is checked between turns: the model call
806- and the tool calls in flight finish, and it holds before the next request, so
807- the conversation is never left with a `tool_use` whose `tool_result` never
808- came. Kill cancels immediately, and what the agent already did to the tree
809- stays done — this ends an agent, it does not undo one.
810-
811- Mouse reporting is on, so clicking the agents pill in the status line toggles
812- the pane and clicking a row selects it. `/mouse` turns reporting off and on
813- again, because while it is on the terminal's own text selection is not.
814-
815- A child's usage is priced at the child's model and rolled into the session
816- total, so a haiku agent spawned from an opus session is not billed at opus
817- rates. Every metrics event a child writes carries its agent id, so
818- `.harness/events.jsonl` can be split per agent afterwards.
819-
820- Subagent transcripts last as long as the session does: `-resume` brings back
821- your conversation, not theirs.
822-
823- Subagents belong to the TUI: their reports come back as messages the front end
824- puts into the conversation, and the line-based REPL has nowhere to show four
825- agents at once. `-p` and `-no-tui` runs are not offered the tools at all, and
826- `-no-agents` turns them off for a TUI run too.
827-
828- ## Setup
829-
830- The first run on a terminal opens a full-screen wizard instead of the chat: the
831- mark assembles, the name lights up, and it asks five questions one at a time,
832- plus the three that are only sometimes there — the Claude Code login question
833- where there is no API key and Claude Code is logged in, the Claude skills
834- question on a machine that has some, and the herdr one on a machine that has
835- [herdr](#herdr).
836- "First run" means the config file does not say `"setup": "done"` — the marker
837- the wizard stamps when the last question is answered — rather than that no file
838- exists, so a config written by hand, or by a build with fewer questions in it,
839- still gets asked the ones it is missing.
840-
841- ```
842- codey setup step 1 of 5
843- ◆━━━━━━━━━━━━◇────────────◇────────────◇────────────◇
844-
845- Which model should the agent use?
846-
847- ▌ ◆ Claude Opus 5 $5 / $25 per Mtok
848- ◇ Claude Sonnet 5 $2 / $10 per Mtok
849- ◇ Claude Haiku 4.5 $1 / $5 per Mtok
850- ◇ custom…
851-
852- claude-opus-5 · 1.0M token context
853-
854- ↑↓ move enter next esc back ctrl+c cancel
855- ```
856-
857- | Question | Answers |
858- | --- | --- |
859- | Model | whatever `GET /v1/models` returns, plus `custom…` for any id |
860- | Effort | the API's own `llm.EffortLevels`: `low` `medium` `high` `xhigh` `max` |
861- | Approvals | ask before every mutating call, or accept them all |
862- | Web search | `none`, Claude's hosted search, or a command on this machine |
863- | Theme | `ember` `mint` `iris` `mono` |
864- | Claude Code login | no, or borrow it when no key is set — asked only where no key or token was given, the endpoint is Anthropic's, and Claude Code is logged in |
865- | Claude skills | leave them alone, use them in place, or copy them once — asked only where there are some |
866- | herdr skill | install it, or not — asked only where herdr is |
867-
868- The last three are the steps that are sometimes not there. The rail knows how
869- many questions this run is actually asking rather than how many the build has,
870- so a machine with none of them gets a five-node rail with no gaps in it, and
871- `step 7 of 8` on a machine with all three counts the questions you are being
872- asked. The Claude Code login question opens on no, and both answers carry the
873- same sentence about the terms: the login is issued to Claude Code, and using it
874- from another client is outside Anthropic's terms for it — so it is a choice
875- for your own machine, described [under Quick start](#quick-start). Someone with
876- an API key set is not asked at all, because there is nothing for them to
877- decide. The answer is `claude_code_auth`; changing it mid-session says it takes
878- effect on the next start, because the client was built, credential and all,
879- before the wizard opened.
880- The herdr answer is also the one that is not a preference: it is an act,
881- carried out once, so nothing about it is written to the config. It appears when
882- `HERDR_ENV=1` or `herdr` is on `PATH`. What it installs, and what happens when
883- the download fails, is [below](#the-herdr-skill). The Claude skills question is
884- described under [skills](#claude-codes-skills); "copy them once" is an act in
885- the same way, carried out as the wizard closes, and it is the mode rather than
886- the act that is saved.
887-
888- It is the same catalogue and the same pricing table `/model` uses, through the
889- same `GET /v1/models` call, so the two cannot disagree about what a model is
890- called or what it costs. Where they differ is failure: `/model` reports the
891- error and closes, because there is a session to go back to. The wizard cannot
892- do that on a first run, so it falls back to the models this build can price and
893- says so. `custom…` is there because a gateway may serve something no build has
894- heard of.
895-
896- The theme step previews by applying the palette for real — reading a swatch
897- tells you nothing about what a transcript looks like in it — and it recolours
898- the whole TUI, pickers and slider included. Cancelling puts the old one back.
899-
900- Answers land in `~/.config/codey/config.json` (`XDG_CONFIG_HOME` is
901- honoured):
902-
903- ```json
904- {
905- "version": 1,
906- "model": "claude-opus-5",
907- "effort": "high",
908- "approvals": "ask",
909- "web_search": "custom",
910- "search_command": "ddgs text -q {{query}} -m {{count}} -nc",
911- "claude_skills": "use",
912- "diagnostics": "on",
913- "claude_code_auth": "off",
914- "theme": "ember",
915- "auto_compact": "on",
916- "setup": "done"
917- }
918- ```
919-
920- `claude_code_auth` is `"on"` or `"off"`, and a missing key — or any other word
921- — means off: the Claude Code login is borrowed only on an explicit `"on"`, or
922- for one run with `-claude-code-auth`, and `-no-claude-code-auth` beats both.
923- A missing key is also how Codey knows it has never asked, which is what the
924- one-time question for someone upgrading reads.
925-
926- `auto_compact` is the one key neither the wizard nor `/settings` offers a row
927- for — set it by hand, or pass `-no-auto-compact` for a single run.
928- `diagnostics` is the one `/settings` has a row for and the wizard does not
929- ask about: it defaults to on, it runs a tool already installed on this machine,
930- and nothing it reads leaves the machine, which is the test every question in
931- that interview is really asking. Both screens
932- write the whole file but only touch what they asked about, so it survives them.
933- Set to `"off"`, the conversation is never folded on its own: the window becomes
934- a wall rather than a trigger, and at `-compact-floor` the turn stops before
935- sending a request that would not fit,
936- naming `/compact` and `/clear` as the ways on. The prompt is refused *before* it
937- joins the history, so compacting and asking again sends it once rather than
938- twice. `/compact` keeps working throughout — the setting governs the automatic
939- fold, not the manual one. Anything other than `"on"` or `"off"` is dropped, and
940- a missing key means on, so a config written before this existed does not
941- suddenly start stopping the agent.
942-
943- Nothing in that file is required, and none of it wins an argument with a flag:
944- precedence is **flag, then config, then the built-in default**, so
945- `-model claude-sonnet-5` is still a one-run override. `accept-all` saved as a
946- default is honoured, but `-yolo` can only turn approvals off, never on — a file
947- should not be able to quietly grant the agent more than the command line did.
948- `-no-auto-compact` runs the same way round: the flag can only turn the automatic
949- fold off, and a config that never mentions it leaves it on.
950- A value this build does not recognise is dropped rather than forwarded to the
951- API, and effort is checked against `llm.EffortLevels` rather than a second copy
952- of the same five strings.
953-
954- `/setup` reopens the wizard at any time. Finishing applies the answers through
955- the same setters `/model` and `/effort` use, so a setting chosen in the wizard
956- and the same setting chosen from the prompt cannot come out different — the
957- wizard just reports them in one line instead of three. It is refused mid-turn:
958- changing the model while a turn is in flight would finish it on a different
959- model than it started. A config file that cannot be parsed is reported and then
960- ignored, and is never overwritten until you finish the wizard.
961-
962- `/model` and `/effort` change the running session and say "from the next turn
963- on"; they do not write to the file. The two screens that do are the wizard and
964- `/settings`.
965-
966- ### Where Codey keeps things
967-
968- Two directories: one global, one per repository. The second sits in your
969- working tree and is git-ignored, because sessions and an event log are yours
970- rather than the project's.
971-
972- | Path | What |
973- | --- | --- |
974- | `~/.config/codey/config.json` | the answers, written by the wizard and `/settings`; [hooks](#hooks) go here by hand |
975- | `~/.config/codey/trust.json` | which checkouts' [Claude Code files](#trusting-a-project) you trusted, and which version of them |
976- | `<repo>/.codey/sessions/` | saved conversations, which is what `-resume` and `/resume` read |
977- | `<repo>/.codey/events.jsonl` | the event log METRICS.md is built from |
978- | `<repo>/.codey/history` | what has been typed at the prompt here, which `↑` walks back through |
979-
980- Codey was called `harness` until recently, and both of those used to live under
981- that name. Neither rename costs you anything. The config is read from
982- `~/.config/harness/config.json` when there is nothing at the new path, so a
983- setup you have already done still counts and the wizard stays shut; that is a
984- read, and the old file is left where it is for an older build to keep using.
985- The per-repository directory is instead moved once, on the first run in a clone
986- that still has a `.harness/`, and Codey says so when it does — the event log is
987- append-only, so leaving half a repository's history under the old name would
988- quietly corrupt the numbers rather than preserve them. A clone that somehow has
989- both keeps the newer one and leaves `.harness/` alone.
990-
991- ### /settings
992-
993- `/settings` is the same settings without the interview: one row each, changed
994- in place, applied as you move.
995-
996- ```
997- Settings ↑↓ row · ←→ change · esc done
998- ❯ Model Opus 5 $5 / $25 per Mtok
999- Effort high default
1000- Approvals Ask me first recommended
1001- Web search No web access default
1002- Diagnostics On default
1003- Claude Code login No, I'll use an API key default
1004- Claude skills Leave them alone default
1005- Theme ember
1006- claude-opus-5 · enter to pick from anthropic
1007- saved to ~/.config/codey/config.json
1008- ```
1009-
1010- `←→` cycles a row and applies it there and then — the theme repaints under the
1011- panel, the way it previews in the wizard — and `esc` closes and writes the
1012- file. There is no cancel, because there is nothing left to confirm: the session
1013- already has every change. A panel that was only read leaves the file alone, and
1014- so does a value cycled back to where it started.
1015-
1016- The rows come from the same option lists the wizard's steps are built from, so
1017- neither screen can invent a name for a theme or a sentence about an effort
1018- level the other does not use. The model row is the exception: its choices come
1019- from the endpoint rather than from this build, so `enter` hands over to the
1020- `/model` picker and lands back here once you have chosen.
1021-
1022- The Claude Code login row is there whether or not the wizard asked, so the
1023- opt-in [described under Quick start](#quick-start) can be turned on or back off
1024- without rerunning setup. Like web search, diagnostics and Claude's skills, it
1025- is decided when the session starts, so a change says it takes effect on the
1026- next start rather than pretending to swap the credential under a conversation
1027- already in progress.
1028-
1029- What the panel shows is the *session*, not the file — `/effort`, `shift+tab`
1030- and `-yolo` all change a setting without the file hearing about it, and a panel
1031- that disagreed with the status bar two lines below it would be worse than
1032- useless. Only the rows actually touched are written back, which is what keeps a
1033- `-yolo` run from quietly saving "accept everything" as the default for every
1034- run after it. And the setup marker is *not* stamped here: changing a theme is
1035- not answering the setup questions, so someone who has only ever used this panel
1036- is still offered the wizard.
1037-
1038- The whole thing is `↑↓` and `enter`, `esc` to go back. Animations collapse to
1039- their final frame under `NO_COLOR`, a `dumb` terminal, or a window under 60×20,
1040- and any key skips the one playing.
1041-
1042- ### Claude Code's settings
1043-
1044- If you already use Claude Code, a project set up for it should not need setting
1045- up a second time. Codey reads three kinds of thing from Claude Code's files —
1046- [its MCP servers](#claude-codes-servers), its permission rules, and its
1047- CLAUDE.md files — and none of it is copied anywhere or written back: change it
1048- in Claude Code and the next start here sees the change.
1049-
1050- **Permissions.** The `permissions` block of three settings files, in the order
1051- Claude Code reads them:
1052-
1053- | File | Whose | `deny` / `ask` | `allow` | `defaultMode` |
1054- | --- | --- | --- | --- | --- |
1055- | `~/.claude/settings.json` | yours, every project | honoured | honoured | `acceptEdits` only |
1056- | `.claude/settings.json` | the repository's, committed | honoured | only once [trusted](#trusting-a-project) | ignored |
1057- | `.claude/settings.local.json` | yours, this project, git-ignored | honoured | honoured | `acceptEdits` only |
1058-
1059- The rule underneath that table is who wrote the file. A deny or an ask rule can
1060- only make the agent do less or ask more, and nobody comes to harm from that, so
1061- a repository may add as many as it likes. An allow rule is the opposite: it is
1062- a decision that something runs without you being asked, and a decision written
1063- into a repository was made by whoever wrote the repository. The shared file's
1064- allow rules therefore wait for the same trust question as its `.mcp.json`, and
1065- until you answer it they are listed and ignored. A `settings.local.json` that
1066- git turns out to be tracking arrived with the clone, whatever its name says,
1067- and is treated as shared.
1068-
1069- The rules act before anything else in the approval policy. A deny holds in
1070- every mode, `-yolo` included — that is what writing it down is for — and it
1071- holds for the read-only tools too, since `Read(.env)` is a rule about reading.
1072- An ask rule puts the call to you even when the mode would have let it through,
1073- and even when you said "always this tool" earlier in the session. An allow rule
1074- lets a call through without a prompt. Between them deny beats ask beats allow,
1075- as in Claude Code, so no allow rule anywhere argues a call past a deny. The
1076- denial the model reads names the rule and the file, so it can tell you which
1077- line to change instead of retrying. `/allowed` lists every rule under the file
1078- it came from, and says which are in force and why the others are not.
1079-
1080- Claude Code's names map onto Codey's: `Bash` is `bash`, `Read` is `read_file`
1081- (and, as best it can, `glob` and `grep`), `Edit` is both `edit_file` and
1082- `write_file`, `MultiEdit` is `edit_file`, `Write` is `write_file`, `Glob`,
1083- `Grep`, `WebFetch`, `WebSearch` and `TodoWrite` are the obvious ones, and
1084- `mcp__server` and `mcp__server__tool` are unchanged. A rule naming a tool Codey
1085- does not have, or written in a form it does not understand, is reported at
1086- startup and takes no part. Paths follow Claude Code's conventions: `//path` is
1087- absolute, `~/path` is under your home, `/path` is under the project root, and
1088- anything else is relative to it, with a bare name such as `.env` matching at
1089- any depth the way `.gitignore` would. Links are followed, so a rule about
1090- `.env` also catches a symlink to it.
1091-
1092- `Bash(npm run test:*)` is matched one simple command at a time, using the same
1093- parser as the approval prompt. An allow rule covers `npm run test && rm -rf ~`
1094- only if something also allows `rm`; a deny rule catches the line if it catches
1095- any part of it. A command the parser cannot see through — `$(...)`, backticks,
1096- `bash -c`, a variable in command position — never matches an allow rule, and
1097- where a deny rule cannot be ruled out for it, the call is asked about rather
1098- than run. So is anything that does more than its words say: `PATH=. git
1099- status`, or `echo hi > ~/.bashrc`, is not covered by an allow rule for `git` or
1100- `echo`. None of this makes shell allowlisting a security boundary — see
1101- [Safety](#safety) — but it does stop the commonest way a prefix rule says yes to
1102- something nobody wrote down.
1103-
1104- `defaultMode: "acceptEdits"` in a file of yours lets file edits through, as
1105- Claude Code's mode of that name does. `default` is the default. The other modes
1106- are ignored with a notice, from every file: `bypassPermissions` in particular,
1107- because in Codey the way to stop being asked is `-yolo`, typed on the command
1108- line for one run, and neither a file of yours nor a repository's gets to say it
1109- for you.
1110-
1111- **CLAUDE.md.** The files Claude Code loads into every conversation are loaded
1112- into this one's system prompt, at startup, in Claude Code's order:
1113- `~/.claude/CLAUDE.md`, then — from the top of the project down to the working
1114- root — each directory's `CLAUDE.md`, `.claude/CLAUDE.md` and `CLAUDE.local.md`.
1115- The top is the nearest directory with a `.git` in it, or your home directory if
1116- there is none on the way; a root that is in neither is a project of one
1117- directory, because walking up to `/` would read whatever happened to be lying
1118- in `/tmp`. Later files are more specific and the prompt says they win.
1119- `@path/to/file` imports are followed, relative to the file they appear in and up
1120- to five deep, skipping anything in a code block. A project's files may import
1121- only from the project, and that is checked where symlinks lead: a repository's
1122- CLAUDE.md that could import `@~/.aws/credentials` would be a repository reading
1123- your credentials into a conversation that is then sent to the model. Your own
1124- `~/.claude/CLAUDE.md` and `CLAUDE.local.md` may import from your home
1125- directory. The whole thing is read once per session, so the cached prompt
1126- prefix stays the same from request to request; restart to pick up an edit.
1127- `AGENTS.md` is not loaded this way, but the agent is still told to read one if
1128- the repository has it, as before, and a CLAUDE.md that says `@AGENTS.md` brings
1129- it in.
1130-
1131- **What is not carried over**, so nobody has to find out the hard way:
1132-
1133- - Hooks in Claude Code's format (`PreToolUse`, `PostToolUse` and the rest).
1134- Codey's [hooks](#hooks) are a different thing — they watch, they cannot
1135- veto — and a script written to block a tool call would silently block
1136- nothing here.
1137- - `env`, `model`, `apiKeyHelper`, `statusLine`, `outputStyle`, `cleanupPeriodDays`
1138- and the other non-permission settings. Codey has its own config for the ones
1139- it has an equivalent of.
1140- - `permissions.additionalDirectories`: the file tools keep the working root as
1141- their only root.
1142- - Claude Code's own record of which `.mcp.json` servers you approved
1143- (`enabledMcpjsonServers`, `enableAllProjectMcpServers`). It approves by name,
1144- so a server whose command changed under the same name would still count;
1145- Codey asks its own question about the exact definitions instead.
1146- - Managed (enterprise) settings, subdirectory CLAUDE.md files loaded on demand,
1147- and `.claude/rules/`.
1148- - Remote MCP servers, which Codey's client does not speak.
1149-
1150- ## Tools
1151-
1152- | Tool | Approval | Notes |
1153- | --- | --- | --- |
1154- | `read_file` | automatic | Numbered lines, `offset`/`limit` for paging; a PNG, JPEG, GIF or WebP comes back as a picture the model can look at |
1155- | `glob` | automatic | Newest first; `*.go` matches at any depth |
1156- | `grep` | automatic | ripgrep when present, pure Go otherwise, identical output either way |
1157- | `edit_file` | prompts | Requires a prior read; refuses ambiguous matches; returns a diff and any [diagnostics](#diagnostics) |
1158- | `write_file` | prompts | Whole files only; separate from `edit_file` so the metrics stay honest |
1159- | `bash` | prompts | Own process group, killed whole on timeout; output capped head and tail. `background: true` returns a handle instead of waiting |
1160- | `process_output` | never | New output from a background process since you last asked, and whether it is still running |
1161- | `process_kill` | never | Stops a background process and its children; everything is killed when the session ends anyway |
1162- | `todo_write` | never | The working plan for a multi-step task; the whole list is sent each time |
1163- | `context_status` | never | How full the window is, how many turns and what they cost |
1164- | `ask_user` | never | Puts a multiple-choice question to you and waits — see [questions](#questions) |
1165- | `web_search` | prompts | Only with `web_search: custom`; runs `ddgs` on this machine |
1166- | `web_fetch` | prompts | With either web search mode; refuses private and loopback addresses |
1167- | `mcp__<server>__<tool>` | prompts | One per tool of each [MCP server](#mcp-servers) in the config |
1168-
1169- ## Diagnostics
1170-
1171- The agent writes a file and finds out it broke the build three tool calls
1172- later, if at all. The cheapest fix is to put the compiler's own opinion in the
1173- result the model is already reading:
1174-
1175- ```
1176- ⏺ EditFile(path=internal/agent/loop.go)
1177- ⎿ 4 additions, 1 removal
1178- …
1179-
1180- diagnostics (gopls): 2 problems
1181- loop.go:14:2: undefined: fmt.Printl
1182- loop.go:19:6: declared and not used: n
1183- ```
1184-
1185- After a successful `edit_file` or `write_file`, the changed file goes to
1186- whatever this machine already has for its language — `gopls`,
1187- `pyright-langserver`, `typescript-language-server` or `rust-analyzer` if one is
1188- on `PATH`, and otherwise the compiler or linter: `go build` and `go vet`, `ruff`
1189- or `pyflakes`, `eslint` or `tsc`, `shellcheck`. Nothing is installed and
1190- nothing leaves the machine.
1191-
1192- **A clean file adds nothing at all.** Silence is the good news, and a "0
1193- problems" line on every edit would be a paragraph of tokens per edit saying
1194- that nothing happened. Ten problems are reported and the rest counted, and a
1195- diagnostic about a *neighbouring* file — the helper you have just renamed out
1196- from under — is kept and labelled, because that is the one the agent has no
1197- other way to learn about.
1198-
1199- Language servers are started once and kept warm between edits, keyed by the
1200- workspace around the file rather than by the working root, so `/worktree` does
1201- not strand a warm `gopls` behind it. A check that has not finished in two
1202- seconds is abandoned rather than waited for: a tool call that hangs because a
1203- language server is confused is far worse than one that reports nothing. Every
1204- other way this can go wrong lands in the same place — a server that will not
1205- start, that dies mid-session, that answers with garbage, or one on `PATH` that
1206- turns out to be a broken shim — and the answer is always no diagnostics and a
1207- fall through to the linter, never a hung tool call. A server that fails to
1208- start is put on a five-minute cooldown rather than retried on every edit.
1209-
1210- It is on by default, which is the one setting here that leans that way: it runs
1211- a tool you already have, against a file you already told the agent to write,
1212- and nothing it reads goes anywhere. `"diagnostics": "off"` in the config, the
1213- `/settings` row, or `-no-diagnostics` for a single run turns it off.
1214-
1215- ## Skills
1216-
1217- A skill is a directory of instructions the agent pulls in when it is relevant:
1218-
1219- ```
1220- ~/.agents/skills/commit-helper/SKILL.md every repo
1221- ./.agents/skills/commit-helper/SKILL.md this one, checked in
1222- ```
1223-
1224- ```markdown
1225- ---
1226- name: commit-helper
1227- description: Use when writing commit messages for this repo
1228- ---
1229-
1230- Read the last twenty commits for tone before writing anything.
1231- Conventions are in references/conventions.md.
1232- ```
1233-
1234- The directory name is the skill's name, and a `name:` that disagrees with it is
1235- a load error. Only the description reaches the model until the skill is
1236- invoked, so it is what decides whether the skill fires at all; the body can be
1237- as long as you like.
1238-
1239- Two ways in, one way to load. The agent calls the `skill` tool when a
1240- description matches what it is doing; you can force one with `/commit-helper
1241- fix the flaky test`, which the model answers by making the same call. `/skills`
1242- lists what was found, project shadowing personal on a name collision, grouped
1243- by where each one came from:
1244-
1245- ```
1246- skills: 3
1247- personal
1248- /commit-helper Use when writing commit messages
1249- claude
1250- /pdf Extract text and tables from PDFs
1251- claude:superpowers
1252- /brainstorming Use when exploring an idea before writing a plan
1253- ```
1254-
1255- One line each, truncated to the width. A description written for another
1256- harness can be three sentences long, and this list is for recognising a skill
1257- you already know about rather than for reading it.
1258-
1259- Skill directories are readable by `read_file`, `glob` and `grep` even though
1260- they sit outside the working root, so a skill can ship reference files and
1261- point at them. They are not writable by anything: `edit_file`, `write_file` and
1262- `bash` keep the working root as their only root. A skill is input, not a work
1263- product.
1264-
1265- Relative paths still anchor to the working root, so a skill pointing at its own
1266- files is read with the directory the `skill` tool prints joined on. If the model
1267- forgets, the "no such file" it gets back names the skill directories, which is
1268- usually enough to recover on the next turn. A shadowed skill's directory is not
1269- readable — only the skills actually in play widen anything.
1270-
1271- Discovery happens at startup. The name-and-description listing goes in the
1272- cached prompt prefix, so re-reading the directory each turn would cost a cache
1273- miss on every turn for a file that never changes. Restart to pick up a new
1274- skill. `-no-skills` turns the whole thing off; the eval runner never discovers
1275- skills at all, so METRICS.md is unaffected by what is in your home directory.
1276-
1277- ### Claude Code's skills
1278-
1279- Codey can also use the skills Claude Code already has on this machine — the
1280- ones under `~/.claude/skills`, the ones its installed plugins ship, and a
1281- project's `.claude/skills`. The setup wizard asks about them, and only when
1282- there are some to offer, because a question about nothing is a question worth
1283- not asking:
1284-
1285- | `claude_skills` | What happens |
1286- | --- | --- |
1287- | `off` | they are left alone; Codey reads only its own roots. This is the default |
1288- | `use` | they are read where they lie, on every start, so updating a plugin in Claude Code updates the skill here with nothing to re-run |
1289- | `copy` | they are copied once into `~/.agents/skills`, where they are yours to edit and unaffected by the next plugin update |
1290-
1291- Off is the default for the same reason `web_search` defaults to none: a skill
1292- is a page of instructions the model will follow, and it should be loaded
1293- because you said so rather than because a directory happened to exist in your
1294- home directory.
1295-
1296- Plugins come from `~/.claude/plugins/installed_plugins.json`, newest install
1297- per plugin, so the stale versions left in the cache are not loaded three times
1298- over. A skill of your own in `~/.agents/skills` wins when it shares a name with
1299- one of Claude's — what you put there is a decision, what arrived with a plugin
1300- is not — and `copy` never overwrites a skill already there. Frontmatter Codey
1301- would otherwise reject is accommodated rather than skipped: a name with
1302- capitals, underscores or spaces in it, or one that disagrees with its own
1303- directory, is normalised into a slug instead of becoming a load error, because
1304- these files were written for another harness and are not yours to fix.
1305- `/skills` lists each one with where it came from — `claude`, or
1306- `claude:superpowers` for a plugin's.
1307-
1308- ## Web search
1309-
1310- The agent's knowledge of a library ends where its training did, and the two
1311- questions it cannot otherwise answer — what does this error message mean, and
1312- what is this API called now — are the ones a search box answers in seconds. The
1313- wizard's fourth question decides whether it gets one, and the honest default is
1314- no: every other capability here stays inside the working tree, and a search
1315- query is written by the model out of whatever the model has read.
1316-
1317- | `web_search` | What it does | What it costs you |
1318- | --- | --- | --- |
1319- | `none` | nothing; the tools are not registered and the model is never told they exist | — |
1320- | `claude` | declares Anthropic's hosted `web_search_20250305` tool, which runs on their side of the wire | billed per search by Anthropic; needs an endpoint that offers it |
1321- | `custom` | registers `web_search` and `web_fetch`, which run here | a search command on your PATH |
1322-
1323- **`claude`** is a line in the request's tool array and nothing else — no
1324- install, no key, no HTML parsing. The searches come back as `server_tool_use`
1325- and `web_search_tool_result` blocks, which the loop turns into ordinary
1326- `WebSearch(...)` transcript entries so a search is something you can watch
1327- rather than a pause you have to guess at. A hosted search cannot be refused at
1328- the prompt, because it happens inside a request you already approved; the
1329- setting itself is the consent. It also needs the endpoint to implement it, so a
1330- gateway or a local server speaking the Messages API will generally refuse the
1331- whole request — if that happens, switch to `custom`.
1332-
1333- **`custom`** runs a command. The default is
1334-
1335- ```
1336- ddgs text -q {{query}} -m {{count}} -nc
1337- ```
1338-
1339- which needs the DuckDuckGo CLI (`pipx install ddgs`) and no account at all.
1340- `{{query}}` and `{{count}}` are substituted as whole argv elements and the
1341- command is run without a shell, so a query containing `;` or backticks is a
1342- query rather than a second command.
1343-
1344- Note the absence of `-o json`, which is the flag you would reach for and the
1345- wrong one: it does not print JSON, it *writes a file* — into whatever directory
1346- the agent happens to be working in, once per search. The plain listing goes to
1347- stdout and is parsed, and `-nc` keeps the colour codes out of the titles.
1348-
1349- Any tool will do, as long as it prints to stdout: a JSON array, one JSON object
1350- per line, a `{"results": […]}` wrapper, or ddgs's own aligned listing are all
1351- read. Titles may be `title`; links `href`, `url` or `link`; snippets `body`,
1352- `snippet`, `description` or `abstract`. Edit the command in the wizard, or set
1353- `search_command` in the config.
1354-
1355- `web_fetch` is the other half, because a snippet is enough to choose a link and
1356- never enough to write code against. It resolves the host and refuses loopback,
1357- private, link-local and unique-local addresses, on the original URL and on
1358- every redirect hop — the model invents these URLs, and `169.254.169.254` is a
1359- cloud metadata endpoint. Pages come back as text, capped head and tail like
1360- `bash` output, wrapped in markers that say to treat what is between them as
1361- data rather than instructions. That does not make prompt injection impossible;
1362- it makes it visible in the transcript, which is the most a fetch tool can
1363- honestly offer.
1364-
1365- Both tools prompt, and neither is in the read-only set: they read nothing local
1366- and send the model's words off the machine, which is the opposite of what that
1367- set is for. The query and the URL are the detail on the prompt, so what is
1368- leaving is on screen before it leaves. "Always this tool" is the answer most
1369- people give once.
1370-
1371- ## MCP servers
1372-
1373- Codey is an MCP client over stdio. Servers go in the config by hand, in the
1374- same shape Claude Code and Codex use, so an entry can be carried across from
1375- either unchanged:
1376-
1377- ```json
1378- {
1379- "mcp_servers": {
1380- "fs": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] },
1381- "db": { "command": "my-db-mcp", "env": { "DB_URL": "postgres://localhost/dev" } }
1382- }
1383- }
1384- ```
1385-
1386- Each server is started once, when the session opens, in the working root, and
1387- every tool it lists is offered as `mcp__<name>__<tool>` — the name being the
1388- key you gave it, not whatever the server calls itself. Subagents get the same
1389- tools from the same processes. Every call prompts like `bash` does, with
1390- "always this tool" to stop asking. A server that is not installed, does not
1391- answer within thirty seconds or speaks a protocol version Codey does not is
1392- reported at startup and left out; the session goes on without it. A server
1393- that dies later is not restarted, and its tools say so when called.
1394-
1395- Only tools are used: not resources, prompts or sampling, and no HTTP
1396- transport. Pictures a tool returns are described to the model rather than
1397- shown. A server's directory is fixed when it starts, so after `/worktree` it
1398- is still serving the root the session began in. `-no-mcp` starts none of them
1399- for one run — Codey's own and Claude Code's alike.
1400-
1401- ### Claude Code's servers
1402-
1403- The servers Claude Code already knows about are started as well, read from
1404- where Claude Code keeps them rather than copied into Codey's config:
1405-
1406- | Where | Whose | Started |
1407- | --- | --- | --- |
1408- | `~/.claude.json`, under `projects["<root>"].mcpServers` | yours, for this project — what `claude mcp add` writes | always |
1409- | `.mcp.json` at the working root | the repository's, committed | once you [trust it](#trusting-a-project) |
1410- | `~/.claude.json`, top-level `mcpServers` | yours, for every project — `claude mcp add -s user` | always |
1411-
1412- The shape is the one already shown above, `{"mcpServers": {name: {command,
1413- args, env}}}`, with `"type": "stdio"` accepted and meaning the same thing. A
1414- server with `"type": "http"` or `"sse"`, or with a `url`, is a remote server;
1415- Codey's client speaks only stdio, so it is reported at startup and skipped.
1416- `${VAR}` and `${VAR:-default}` are expanded in the command, the arguments and
1417- the env values, as Claude Code expands them — so a `.mcp.json` can say
1418- `"${DB_URL}"` and leave the secret in your environment. A variable that is
1419- unset and has no default skips that server with a notice naming it, rather
1420- than starting it with a hole in its command line.
1421-
1422- When two places define a server of the same name, Codey's own `mcp_servers`
1423- win, and after that the table is read top to bottom: your per-project servers,
1424- then the repository's, then your global ones. That is Claude Code's own order,
1425- so a name means the same program in both. Each clash is reported once, naming
1426- the one that won, because a server that quietly turned out to be a different
1427- program is not something anyone thinks to debug.
1428-
1429- #### Trusting a project
1430-
1431- A `.mcp.json` comes from the repository. Starting a server listed there means
1432- running a command — with your environment, your credentials, your network —
1433- that whoever wrote the repository chose, before the agent has done anything
1434- and outside every approval prompt. So, like Claude Code, Codey asks first. The
1435- first interactive start in a checkout whose `.mcp.json` or shared
1436- `.claude/settings.json` [allow rules](#claude-codes-settings) would grant
1437- something prints, before the TUI opens:
1438-
1439- ```
1440- Claude Code's configuration for /home/you/src/thing would:
1441-
1442- start these MCP servers, from .mcp.json:
1443- db: npx -y @acme/db-mcp --url ${DB_URL} (env: DB_TOKEN)
1444- run these without asking, from .claude/settings.json:
1445- Bash(make:*)
1446-
1447- These files come from the repository, not from you: trusting them lets it
1448- choose commands this machine runs. Your answer is remembered until they change.
1449- Trust them for this checkout? [y/N]
1450- ```
1451-
1452- One question covers both files, because it is one question: do you trust what
1453- this repository says about itself. The full command line is shown because that
1454- is the thing being agreed to. The answer — yes or no — is remembered for this
1455- checkout against a fingerprint of exactly those definitions, as written, before
1456- any `${VAR}` is expanded. A new server, a changed argument or one more allow
1457- rule is a different fingerprint, and the question comes back; nothing else
1458- does. A refusal is remembered too, so saying no once is not a question at every
1459- start.
1460-
1461- The record is kept in `~/.config/codey/trust.json`, not in the project's
1462- `.codey/` directory with the rest of the per-repository state. That is the
1463- point of it: anything inside the working tree can arrive with a clone, and a
1464- repository that shipped its own record of being trusted would be approving its
1465- own servers. The fingerprint would be no defence there — whoever wrote the
1466- `.mcp.json` can compute it. Only a file the repository cannot reach can say what
1467- you decided.
1468-
1469- Where nobody can be asked — `-p`, `codey agent stdio`, a piped stdin — an
1470- undecided project's servers are not started and its allow rules are ignored,
1471- with a line on stderr saying so. A checkout you have already trusted needs
1472- nothing more. `-trust-project` says yes for one run without asking and without
1473- recording anything, which is what a CI job against a repository you control
1474- wants. Servers in `~/.claude.json` never ask: you put them there, as you put
1475- Codey's own in its config. Once started, every server's tool calls go through
1476- the approval prompts like any other, unless an allow rule such as `mcp__db`
1477- says otherwise.
1478-
1479- ## Hooks
1480-
1481- Codey publishes its lifecycle — a session opened, a run started, a tool ran, an
1482- agent stopped to ask you something — so that things outside the process can
1483- watch it. That is a different job from the event stream the TUI draws from:
1484- this one carries no conversation content, no text deltas and no tool output,
1485- because its audience is a sidebar, a notifier, or a script, and what the model
1486- wrote is not theirs to have.
1487-
1488- | Event | When |
1489- | --- | --- |
1490- | `session_start` / `session_end` | one conversation, from open to close; a second tab is a second session |
1491- | `run_start` / `run_end` | one prompt, from your message to the model's final answer, tool calls and all |
1492- | `turn_start` | one model call inside a run |
1493- | `tool_start` / `tool_result` | one tool call; the result says whether it failed, not what it returned |
1494- | `blocked` / `unblocked` | waiting on a human: an approval prompt, or a question from `ask_user` |
1495- | `paused` / `resumed` | the loop held at a turn boundary, and let go again |
1496-
1497- `blocked` is the one you cannot get any other way. From outside the process an
1498- agent thinking hard and an agent that has been sitting on an approval prompt
1499- for ten minutes look exactly alike: the run has not returned and nothing is
1500- being printed. Only one of them will finish without you.
1501-
1502- Hooks in the config run shell commands:
1503-
1504- ```json
1505- {
1506- "hooks": {
1507- "run_end": ["notify-send 'codey' \"$CODEY_SESSION finished\""],
1508- "blocked": ["notify-send -u critical 'codey needs you' \"$CODEY_MESSAGE\""]
1509- }
1510- }
1511- ```
1512-
1513- Each command gets the event twice over: as `CODEY_HOOK_EVENT`, `CODEY_SESSION`,
1514- `CODEY_AGENT`, `CODEY_TURN`, `CODEY_TOOL`, `CODEY_REASON`, `CODEY_MESSAGE` and
1515- `CODEY_ERROR` — always exported, sometimes empty, so a one-liner never has to
1516- test first — and as the same fields in JSON on stdin, for anything that wants a
1517- parser rather than string surgery. They run in the working root, one after
1518- another, on the agent's own goroutine, which is why each is given ten seconds
1519- and then abandoned: a hook that hangs would otherwise be a session that hangs.
1520- Output is swallowed unless the command fails, since the TUI owns the screen; a
1521- failure is written to the event log as a `hook_error` and changes nothing about
1522- the run. An unknown event name is refused at startup rather than ignored,
1523- because a hook that silently never fires is the one bug with nothing to notice.
1524-
1525- This is the single setting the wizard never writes. A hook is a command your
1526- machine runs on your behalf, so it gets into the file because you typed it
1527- there. `-no-hooks` turns the lot off for one run.
1528-
1529- ### herdr
1530-
1531- [herdr](https://herdr.dev) is a terminal workspace manager for coding agents: a
1532- background server owns the terminals, panes survive a detach, and a sidebar
1533- shows across every project which agent is working, which is blocked, and which
1534- is idle. Codey reports itself to herdr, automatically — there is nothing to
1535- install and nothing to configure. Run it in a pane and it says so at startup:
1536-
1537- ```
1538- codey: herdr detected — reporting agent state to pane w1:p1
1539- ```
1540-
1541- Herdr reads most agents' states off the screen with pattern rules. Codey is
1542- told instead, through the contract herdr documents for agents it does not ship
1543- support for: a process inside a pane inherits `HERDR_ENV=1`, `HERDR_PANE_ID`
1544- and `HERDR_BIN_PATH`, and reports semantic state by running `herdr pane
1545- report-agent`. Being told beats guessing in both directions — herdr never has
1546- to infer anything from what the TUI happened to draw, and the state it could
1547- not see at all, that the agent is sitting on an approval prompt behind a
1548- full-screen view, is stated outright along with what it is waiting for:
1549-
1550- ```
1551- bash: rm -rf build
1552- asked about Storage, Error handling
1553- ```
1554-
1555- A subagent's prompt counts. Spawning does not block, so a background agent can
1556- ask for approval long after the agent that spawned it went quiet, and a pane
1557- that called that idle would leave you waiting on an agent that is waiting on
1558- you. Two agents can be waiting at once, and answering one does not clear the
1559- other: the pane goes back to what it was doing only when the last prompt is
1560- answered.
1561-
1562- One pane holds one Codey and one Codey can hold several tabs, so the tabs are
1563- reduced to the single state the pane is in: blocked beats working beats idle.
1564- What a person scanning a sidebar needs to find is the agent that stopped and
1565- wants them, and after that the one still going. A paused agent reports idle
1566- rather than blocked — nobody is being waited on, and it has no business in the
1567- list of agents that need attention.
1568-
1569- Every report carries Codey's session id, so herdr's pane and agent APIs show
1570- which conversation is running in a pane and a restored one can be pointed back
1571- at it with `-resume`. Herdr restarts an agent by itself only where it knows how
1572- to launch it, which for now means the agents in its own registry; the reference
1573- is reported regardless, because it costs one flag and is the half of the
1574- problem that is ours.
1575-
1576- Reporting is a no-op anywhere else. Detection is those three environment
1577- variables and nothing else — no probing, no socket, no cost to anyone who has
1578- never heard of herdr — and all three are required, because a stale `HERDR_ENV`
1579- exported into a shell that outlived its pane is exactly the case where
1580- reporting would go nowhere, slowly, forever. `-no-hooks` disables it too.
1581-
1582- When a state looks wrong, `herdr agent list` shows what herdr sees, and the
1583- `hook_error` lines in `.codey/events.jsonl` show what Codey could not tell it.
1584-
1585- #### The herdr skill
1586-
1587- Reporting is one direction. The other is the agent driving herdr itself —
1588- splitting a pane, starting a server in it without stealing your focus, reading
1589- what another pane printed, waiting until another agent is genuinely blocked
1590- rather than merely slow. Herdr publishes a skill that teaches an agent to do
1591- that, and [the setup wizard](#setup) offers to install it when it finds herdr
1592- on the machine:
1593-
1594- ```
1595- Install herdr's skill, so the agent can drive herdr?
1596-
1597- ▌ ◆ yes, install it ~/.agents/skills/herdr
1598- ◇ no
41+ go install github.com/johannSo/codey/cmd/codey@latest
159942 ```
160043
1601- Yes downloads [herdr's `SKILL.md`](https://github.com/herdrdev/herdr/blob/master/skills/herdr/SKILL.md)
1602- and writes it to `~/.agents/skills/herdr/SKILL.md`, which is where Codey looks
1603- for personal skills — the same layout herdr publishes and the same one
1604- `npx skills add` writes, so a skill installed by either is found by both. From
1605- the next start it is listed to the agent like any other skill, and invoked the
1606- same way.
1607-
1608- It is fetched rather than shipped inside the binary. The file is a manual for a
1609- CLI that is still moving, and a copy frozen into a Codey release would go on
1610- teaching last year's flags long after they stopped working. The cost is that
1611- the install needs the network: when it fails, nothing is written, the wizard
1612- says why, and the answers you just gave are saved regardless — only the
1613- download is lost, and `/setup` offers it again.
1614-
1615- Nothing is written unread. What comes back is parsed by the same loader that
1616- will read it at startup, and rejected if it is not a skill Codey can load —
1617- a login page, an error page, or a file whose shape has changed. The write goes
1618- through a temporary file in the same directory, so a failed update leaves the
1619- copy you already had rather than half of a new one.
1620-
1621- This is the only question in the wizard that acts rather than records, so it is
1622- the only one where pressing enter through the screens does something: the row
1623- names the exact path before you answer, and the notice afterwards names the
1624- file that was written. Uninstalling is `rm -r ~/.agents/skills/herdr`.
1625-
1626- ## Using it in place of `claude`
1627-
1628- A lot of CI jobs and shell scripts run an agent as `claude -p`. Codey reads the
1629- same flags with the same meaning, so pointing one of them at Codey is meant to
1630- be a matter of changing the binary's name:
44+ ### From source
163145
163246 ```bash
1633- codey -p --output-format json "why does TestFoo fail?" | jq -r .result
1634- echo "summarise the last commit" | codey -p --allowedTools "Bash(git log:*)" "Bash(git show:*)"
1635- codey -c -p "now fix it" # same session
47+ git clone https://github.com/johannSo/codey
48+ cd codey
49+ go build -o codey ./cmd/codey
163650 ```
163751
1638- | Claude Code flag | In Codey |
1639- | --- | --- |
1640- | `-p`, `--print` | One prompt, then exit. The prompt may follow `-p` — Codey's own spelling — or be a positional argument anywhere on the line, as `claude` takes it, or arrive on stdin when there is no other |
1641- | `--output-format text` | The final answer and nothing else on stdout |
1642- | `--output-format json` | One result object at the end |
1643- | `--output-format stream-json` | One JSON line per message as the run goes, then the result object |
1644- | `--verbose` | The same as `-v`. With `json` it prints every line `stream-json` would have, as one array, which is what Claude Code does; `stream-json` does not need it |
1645- | `-c`, `--continue` | `-resume latest`: the most recent session in this working root |
1646- | `--resume <id>` | Unchanged; the id is a Codey session id, which is what `session_id` in the output holds |
1647- | `--model`, `--max-turns` | Codey's own flags, already spelled the same |
1648- | `--allowedTools`, `--allowed-tools` | Calls to approve without asking, as rules |
1649- | `--disallowedTools`, `--disallowed-tools` | Tools to take away, or calls to refuse, as rules |
1650- | `--permission-mode` | `default`; `acceptEdits`, which lets edits and writes through and still asks before a command — the mode the ACP front end has by the same name; `bypassPermissions`, which is `-yolo`; `dontAsk`, which refuses whatever no rule allows |
1651- | `--dangerously-skip-permissions` | `-yolo` |
1652- | `--append-system-prompt`, `--system-prompt` | Add to the end of Codey's system prompt, or replace it. The session's own agent only: a subagent's prompt is its preset's |
1653- | `--include-partial-messages` | Accepted, with a warning, and ignored |
1654-
1655- Without `--output-format`, `-p` prints what it always has: the transcript as it
1656- streams, the tool calls, a usage line — for a person watching, not a parser.
1657- Asking for a format is asking for Claude Code's behaviour, and then stdout holds
1658- the format and nothing else; notices, warnings and any approval prompt go to
1659- stderr.
1660-
1661- The shapes are Claude Code's, because the programs reading them were written
1662- against it. The result object carries `type`, `subtype` (`success`,
1663- `error_max_turns` or `error_during_execution`), `is_error`, `result`,
1664- `session_id`, `num_turns`, `duration_ms`, `duration_api_ms`, `total_cost_usd`
1665- and `usage` with the four token counts. In `stream-json` it is preceded by a
1666- `system`/`init` line — working directory, model, tools, MCP servers, permission
1667- mode — then an `assistant` line per response, holding the message as the
1668- Messages API returned it, and a `user` line per batch of tool results. Numbers
1669- are this invocation's: a resumed session reports what the resume cost, not
1670- everything before it.
1671-
1672- A run that fails exits 1, whatever the format, and the JSON formats still print
1673- their result object — with `is_error: true`, the error in `errors`, and in
1674- `result` whatever the model last said, or the error itself when it said
1675- nothing. That includes failing before the model was ever asked: no credentials
1676- gives a result object, with an empty `session_id` because there was no session
1677- yet. Check `is_error` rather than `subtype`: it is the field that means
1678- failure in both, where the subtypes for a failure are not guaranteed to agree
1679- between the two. A flag
1680- Codey does not know stops the run before any of this, with exit 2, and so does
1681- a value it cannot use — `--permission-mode plan`, say — with exit 1 and a
1682- message on stderr.
1683-
1684- **Who gets asked.** Claude Code never prompts in `-p` mode, and with
1685- `--output-format` neither does Codey when stdin is not a terminal: what no rule
1686- allows is refused, and the model is told so and carries on. That is the case in
1687- CI. At a terminal the prompt still comes up, on stderr. Codey's own `-p`
1688- without a format keeps reading answers from stdin, so `yes | codey -p ...`
1689- works as it always has — except when the prompt itself came from stdin, which
1690- leaves nothing there to answer with.
1691-
1692- **Rules.** `--allowedTools` and `--disallowedTools` take Claude Code's rule
1693- syntax, comma- or space-separated, as many as you like after one flag:
1694- `Bash`, `Bash(git status)`, `Bash(npm run test:*)`, `Read`, `Read(./src/**)`,
1695- `Edit(./docs/**)`, `Write`, `Glob`, `Grep`, `WebFetch(domain:example.com)`,
1696- `WebSearch`, `TodoWrite`, `Task`, `mcp__github`, `mcp__github__create_issue`.
1697- Claude Code's names map onto Codey's tools — `Bash` is `bash`, `Read`
1698- `read_file`, `Write` `write_file`, and `Edit` both `edit_file` and
1699- `write_file`, because Claude Code applies its Edit rules to every tool that
1700- edits files. Codey's own names work as well. A rule naming a tool Codey does
1701- not have, `NotebookEdit` say, is a warning, and the rest of the list still
1702- applies.
1703-
1704- A rule for a whole tool on the disallowed list takes the tool away: the model
1705- is never shown it. A narrower one — `Bash(git push:*)`, `Read(./.env)` — is
1706- checked at approval time, before anything else, `-yolo` included, and a call it
1707- matches is refused. On the allowed list a rule stands in for the "yes" you
1708- would otherwise have been asked for.
1709-
1710- `Bash(prefix:*)` matches word by word, so `Bash(ls:*)` covers `ls -la` and not
1711- `lsof`. A compound command is allowed only when every simple command in it is
1712- covered, so `git status; rm -rf ~` does not ride in on `Bash(git status:*)`, and
1713- anything the [shell analysis](#safety) cannot see through — command
1714- substitution, a variable in command position, an inline `bash -c` script, an
1715- assignment like `PATH=...`, output redirected into a file — is never allowed by
1716- a narrower rule than `Bash`. A deny rule is the other way round: one matching
1717- part is enough, and it looks inside `bash -c` and `eval`, past `sudo`, `env`
1718- and `timeout`, and at `/bin/rm` as `rm`. That catches the command a model
1719- would ordinarily write. It is not a sandbox, for the reason the Safety section
1720- gives.
1721-
1722- Path rules read like gitignore, as Claude Code's do: `./` and `/` are the
1723- working root, `//` an absolute path, `~/` your home, and a pattern with no
1724- slash in it — `.env`, `*.pem` — matches a name at any depth. Path rules on
1725- `Read` apply to `glob` and `grep` too, as far as their `path` argument goes.
1726-
1727- What does not work:
1728-
1729- - `--permission-mode plan`. Codey has no read-only planning mode, and says so
1730- rather than run a mode it does not have. Ask for a plan in the prompt, and
1731- `--disallowedTools Edit,Write,Bash` holds the run to it.
1732- - `--input-format stream-json`, and so a conversation fed in over stdin. The
1733- prompt is text.
1734- - `--include-partial-messages`: there are no `stream_event` lines, only whole
1735- messages.
1736- - `--mcp-config` and `--strict-mcp-config`. MCP servers come from
1737- [Codey's config](#mcp-servers).
1738- - `--settings`, `--setting-sources`, `--add-dir`, `--fallback-model`,
1739- `--session-id`, `--fork-session`, and `--resume` with no id, which in Claude
1740- Code opens a picker.
1741- - Piping a file in alongside a prompt. `cat log | claude -p "explain"` hands
1742- Claude Code both; Codey reads stdin only when there is no prompt otherwise,
1743- because a CI runner can leave stdin as a pipe nobody ever closes, and a run
1744- that waited on it would hang there for good. Put it in the prompt:
1745- `codey -p "explain: $(cat log)"`.
1746- - A prompt without `-p`, which in Claude Code starts an interactive session
1747- with that prompt. Codey ignores it and says so.
1748- - Tool names in the output are Codey's — `bash`, `read_file` — not Claude
1749- Code's. A script that looks for `"name":"Bash"` in the stream needs to look
1750- for `bash`.
1751- - The result object has no `permission_denials` or `modelUsage`, and lines
1752- carry no `parent_tool_use_id` other than `null`: subagents report through a
1753- tool result rather than in the parent's stream.
1754-
1755- ## Inside another editor
1756-
1757- Codey can be the agent behind somebody else's UI. It speaks the
1758- [Agent Client Protocol](https://agentclientprotocol.com) — JSON-RPC over stdin
1759- and stdout — so a control plane that drives Codex, Claude Code or Cursor can
1760- drive this instead:
52+ ### First run
176153
176254 ```bash
1763- codey agent stdio
55+ codey -C /path/to/a/throwaway/clone # full-screen TUI
56+ codey -C /path/to/repo -p "why does TestFoo fail?" # one prompt, then exit
176457 ```
176558
1766- Nothing but protocol frames goes to stdout in that mode; diagnostics go to
1767- stderr. The connection ends when stdin does.
1768-
1769- ### T3 Code
1770-
1771- [T3 Code](https://t3.codes) compiles its providers in, so there is no Codey
1772- entry to select. There is, however, a binary path on every provider, and its
1773- Grok provider is a plain ACP agent — which means Codey can take that slot with
1774- no change to their source. In **Settings → Providers**, enable Grok, set
1775- **Binary path** to your `codey`, and rename the instance:
59+ The first run on a terminal opens a short setup wizard that asks for the model, effort, approvals, web search and theme. `/setup` opens it again later, and `/settings` changes one setting at a time.
177660
1777- ```json
1778- "providerInstances": {
1779- "codey": {
1780- "driver": "grok",
1781- "displayName": "Codey",
1782- "enabled": true,
1783- "config": { "binaryPath": "/path/to/codey" },
1784- "environment": [
1785- { "name": "ANTHROPIC_API_KEY", "value": "sk-ant-...", "sensitive": true }
1786- ]
1787- }
1788- }
1789- ```
1790-
1791- That file is `~/.t3/userdata/settings.json`; the settings panel writes the same
1792- thing. The per-instance environment is how Codey gets a credential without
1793- inheriting whatever the desktop app was launched with — and without it, the
1794- provider card will say Codey is installed and not logged in, which is true.
61+ ## Some notes
179562
1796- Threads stream text and reasoning, fill a tool row in as the command talks,
1797- raise an approval prompt you answer in T3 Code's own panel, and stop when you
1798- press stop. The model picker is filled from the endpoint Codey is pointed at.
63+ Codey is early. Expect rough edges.
179964
1800- [Subagents](#subagents) work too. Each one gets a row of its own that opens
1801- when the child starts, fills in as it works, and closes with its report, and
1802- the reports reach the parent as messages exactly as they do in the TUI. A turn
1803- stays open while its children are still working — their output belongs inside
1804- the turn that started them, and a thread that looked finished while four agents
1805- were still running would be lying. `-no-agents` in the launch arguments turns
1806- them off.
65+ Approval prompts are not a sandbox. `-yolo` belongs inside a container or against a throwaway clone. Read [Safety](docs/user/safety.md) before you point Codey at anything you care about.
180766
1808- What does not work is resuming a thread after a restart: Codey does not
1809- advertise session loading, so T3 Code will not offer it.
67+ ## Documentation
181068
1811- The card will say Grok and show the xAI glyph. The display name and accent
1812- colour are yours to set; the icon is not. That is the price of occupying a slot
1813- instead of adding one.
69+ Full docs live in [docs/](docs). There's no docs site.
181470
1815- Two invocations exist only for that provider card: `codey --version`, and
1816- `codey models`, which prints whether there is a credential and what the
1817- endpoint serves. Both are shaped for the regular expressions on the other end
1818- rather than for a person. The probe follows the same opt-in as everything else:
1819- a Claude Code login counts as logged in only when the config says
1820- `"claude_code_auth": "on"` — the probe is run with no flags, so
1821- `-claude-code-auth` in the launch arguments reaches the agent but not the card.
71+ - [Install and first run](docs/user/install.md): credentials, gateways, and the Claude Code login
72+ - [Flags](docs/user/flags.md): including compaction thresholds and turn caps
73+ - [The TUI](docs/user/tui.md): keys, images, questions, the mouse, `!` shell, `/model`, `/effort`
74+ - [Tabs, worktrees and sessions](docs/user/tabs.md)
75+ - [Subagents](docs/user/subagents.md) and the agents sidebar
76+ - [Setup, `/settings` and where Codey keeps things](docs/user/setup.md)
77+ - [Tools](docs/user/tools.md) and diagnostics after every edit
78+ - [Skills](docs/user/skills.md), Claude Code's included
79+ - [Web search](docs/user/web-search.md)
80+ - [MCP servers](docs/user/mcp.md), and trusting a project's `.mcp.json`
81+ - [Claude Code's settings](docs/user/claude-code-settings.md): permissions and CLAUDE.md
82+ - [Hooks](docs/user/hooks.md) and [herdr](docs/user/hooks.md#herdr)
83+ - [Using it in place of `claude`](docs/user/in-place-of-claude.md): `-p`, `--output-format`, `--allowedTools`
84+ - [Inside another editor](docs/user/editors.md): ACP, and Codey as a T3 Code provider
85+ - [Safety](docs/user/safety.md)
182286
1823- `docs/superpowers/specs/2026-09-19-t3code-provider-design.md` is the research
1824- this is built on: what every T3 Code provider requires of a binary, and why the
1825- Grok slot is the one that fits.
87+ Working on Codey itself? Start at [docs/internals/overview.md](docs/internals/overview.md).
182688
182789 ## Evals
182890
@@ -1831,117 +93,7 @@ Grok slot is the one that fits.
183193 go run ./evals -n 3 # the real thing
183294 ```
183395
1834- Ten tasks, a fresh temp directory per run, full transcripts under
1835- `evals/runs/<timestamp>/`. See [METRICS.md](METRICS.md).
1836-
1837- ## Safety
1838-
1839- Three layers, in increasing order of how much they actually protect you.
1840-
1841- **Path jail.** Every path a tool is given is resolved with
1842- `filepath.EvalSymlinks` and refused if it lands outside the working root,
1843- including through a symlink that points out of the tree. Cheap, and it catches
1844- a lot.
1845-
1846- **Approval prompts.** Read-only tools run unattended. `bash`, `edit_file`,
1847- `write_file`, `web_search` and `web_fetch` ask, with "yes / no / always this exact one / always this tool" and
1848- session memory. `/allowed` lists what you have agreed to. `--allowedTools` and
1849- `--disallowedTools` write the same decisions down ahead of time, in Claude
1850- Code's rule syntax — see [using it in place of
1851- `claude`](#using-it-in-place-of-claude) — and a deny rule there is checked
1852- before everything else, `-yolo` included.
1853-
1854- Shell commands are parsed with `mvdan.cc/sh/syntax` so the prompt can tell you
1855- what a command will actually run — `git status; rm -rf ~` is shown as running
1856- both `git` and `rm`. **This is not a security boundary**, and Codey does
1857- not pretend otherwise: a command containing `$(...)`, backticks, or an
1858- interpreter with `-c` cannot be analysed at all, so it is marked opaque and
1859- "always allow this exact command" is refused for it. String-matching
1860- allowlists for shell commands do not work; this exists to make the prompt
1861- specific, not to make the decision for you.
1862-
1863- [Claude Code's permission rules](#claude-codes-settings) sit in front of all
1864- of that. Its deny rules hold in every mode, `-yolo` included, which makes them
1865- the one part of this layer you can lean on: a call they refuse does not run.
1866- Its allow rules are matched with the same parser, one simple command at a
1867- time, and an opaque command matches none of them — but the warning above
1868- applies to them with full force. `Bash(find:*)` allows `find . -exec rm {} +`,
1869- and no parser will tell you otherwise.
1870-
1871- Hooks sit outside all three layers, deliberately: a command in `"hooks"` runs
1872- on every event it is attached to, without a prompt, because you put it in your
1873- own config rather than the agent asking for it. The path jail is what keeps the
1874- agent from adding one — the config lives in `~/.config/codey/`, outside any
1875- working root you would sensibly point Codey at — but the flip side is that
1876- anything able to write that file has arranged to run commands here. `-no-hooks`
1877- is the switch that says no for one run. MCP servers are the same bargain: each
1878- is a program started with your environment when the session opens, and only
1879- its tool calls go through the prompts. `-no-mcp` is theirs. The one kind of
1880- server that does not come from a file you wrote — a repository's `.mcp.json` —
1881- waits for you to [trust it](#trusting-a-project) first.
1882-
1883- **A sandbox.** This is the layer that actually contains a mistake, and the
1884- Codey cannot provide it for you. The honest recipe on Linux:
1885-
1886- ```bash
1887- podman run --rm -it \
1888- -v "$PWD":/work:Z -w /work \
1889- -e ANTHROPIC_API_KEY \
1890- docker.io/library/golang:1.26 \
1891- /work/codey -C /work -yolo
1892- ```
1893-
1894- **Decide about egress explicitly.** The container above has network access,
1895- because Codey needs to reach the API — which means the agent's `bash`
1896- has it too. That is the line between "broke my repo" and "read my `.env` and
1897- made an HTTP request". If that matters for the tree you are pointing it at:
1898-
1899- - Treat every secret reachable from inside the container as exposed.
1900- - Use a token scoped to nothing else, and a clone rather than your working tree.
1901- - For real isolation, run Codey on the host and put only `bash` in a
1902- network-less namespace. That is not implemented here.
1903-
1904- `-yolo` skips every prompt. It is correct inside a container or against a
1905- throwaway clone, and wrong everywhere else. The eval runner sets it, because it
1906- is headless and operates on a temp directory it created.
1907-
1908- ## Layout
1909-
1910- ```
1911- cmd/codey flag parsing, wiring, REPL
1912- internal/llm Messages API: types, HTTP, SSE, pricing
1913- internal/agent turn loop, conversation state, compaction, sessions
1914- internal/tools the tool registry and the tools
1915- internal/diag language servers and linters, and what they say about an edit
1916- internal/skills discovery and parsing of SKILL.md, Codey's own and Claude Code's
1917- internal/websearch the search command, the URL jail, HTML to text
1918- internal/mcp MCP client: starts the configured servers, offers their tools
1919- internal/config saved defaults: model, effort, approvals, web search, theme, diagnostics
1920- internal/approval allow / deny / ask, with shell analysis and Claude Code's permission rules
1921- internal/ccsettings Claude Code's MCP servers, permission rules and CLAUDE.md, and who may grant what
1922- internal/ask questions the agent puts to you mid-task, and the tool for it
1923- internal/hooks the lifecycle, published: fan-out, and the config's commands
1924- internal/herdr reporting agent state to the herdr pane Codey is running in
1925- internal/attach pictures pasted into a prompt: clipboard, files, markers
1926- internal/metrics JSONL event log
1927- internal/ui line-based terminal rendering
1928- internal/acp Agent Client Protocol: the wire, and its vocabulary
1929- internal/serve Codey as an agent another program drives, over ACP
1930- internal/tui full-screen Bubble Tea front end, and the setup wizard
1931- evals task suite and runner
1932- ```
1933-
1934- Two rules hold the shape: `agent` never imports `ui` or `tui`, so the eval
1935- runner and the ACP front end can drive the loop headless; and every tool returns `(string, error)`
1936- where the error is text the model reads, not an exception Codey swallows.
1937- A failed edit is a normal event in the transcript.
1938-
1939- The TUI obeys the first rule through two one-way hatches. Events reach it via
1940- `agent.Observe`, forwarded into the Bubble Tea loop with `Program.Send`, which
1941- is safe from the agent's goroutine. Approvals go out the same way and then
1942- block the agent on a reply channel until a key arrives, and questions take the
1943- same hatch with an answer on the way back. Those channels are buffered and the
1944- wait honours the turn's context, so neither side can strand the other.
96+ Ten tasks, a fresh temp directory per run, full transcripts under `evals/runs/<timestamp>/`. The results are in [METRICS.md](METRICS.md).
194597
194698 ## License
194799
+49−0docs/internals/overview.mdaddedView File
@@ -0,0 +1,49 @@
1+ # Internals
2+
3+ ## Layout
4+
5+ ```
6+ cmd/codey flag parsing, wiring, REPL
7+ internal/llm Messages API: types, HTTP, SSE, pricing
8+ internal/agent turn loop, conversation state, compaction, sessions
9+ internal/tools the tool registry and the tools
10+ internal/diag language servers and linters, and what they say about an edit
11+ internal/skills discovery and parsing of SKILL.md, Codey's own and Claude Code's
12+ internal/websearch the search command, the URL jail, HTML to text
13+ internal/mcp MCP client: starts the configured servers, offers their tools
14+ internal/config saved defaults: model, effort, approvals, web search, theme, diagnostics
15+ internal/approval allow / deny / ask, with shell analysis and Claude Code's permission rules
16+ internal/ccsettings Claude Code's MCP servers, permission rules and CLAUDE.md, and who may grant what
17+ internal/ask questions the agent puts to you mid-task, and the tool for it
18+ internal/hooks the lifecycle, published: fan-out, and the config's commands
19+ internal/herdr reporting agent state to the herdr pane Codey is running in
20+ internal/attach pictures pasted into a prompt: clipboard, files, markers
21+ internal/metrics JSONL event log
22+ internal/ui line-based terminal rendering
23+ internal/acp Agent Client Protocol: the wire, and its vocabulary
24+ internal/serve Codey as an agent another program drives, over ACP
25+ internal/tui full-screen Bubble Tea front end, and the setup wizard
26+ evals task suite and runner
27+ ```
28+
29+ Two rules hold the shape: `agent` never imports `ui` or `tui`, so the eval
30+ runner and the ACP front end can drive the loop headless; and every tool returns `(string, error)`
31+ where the error is text the model reads, not an exception Codey swallows.
32+ A failed edit is a normal event in the transcript.
33+
34+ The TUI obeys the first rule through two one-way hatches. Events reach it via
35+ `agent.Observe`, forwarded into the Bubble Tea loop with `Program.Send`, which
36+ is safe from the agent's goroutine. Approvals go out the same way and then
37+ block the agent on a reply channel until a key arrives, and questions take the
38+ same hatch with an answer on the way back. Those channels are buffered and the
39+ wait honours the turn's context, so neither side can strand the other.
40+
41+ ## Evals
42+
43+ ```bash
44+ ./evals/selftest.sh # no API calls: checks the tasks measure something
45+ go run ./evals -n 3 # the real thing
46+ ```
47+
48+ Ten tasks, a fresh temp directory per run, full transcripts under
49+ `evals/runs/<timestamp>/`. See [METRICS.md](../../METRICS.md).
+107−0docs/user/claude-code-settings.mdaddedView File
@@ -0,0 +1,107 @@
1+ # Claude Code's settings
2+
3+ If you already use Claude Code, a project set up for it should not need setting
4+ up a second time. Codey reads three kinds of thing from Claude Code's files —
5+ [its MCP servers](mcp.md#claude-codes-servers), its permission rules, and its
6+ CLAUDE.md files — and none of it is copied anywhere or written back: change it
7+ in Claude Code and the next start here sees the change.
8+
9+ **Permissions.** The `permissions` block of three settings files, in the order
10+ Claude Code reads them:
11+
12+ | File | Whose | `deny` / `ask` | `allow` | `defaultMode` |
13+ | --- | --- | --- | --- | --- |
14+ | `~/.claude/settings.json` | yours, every project | honoured | honoured | `acceptEdits` only |
15+ | `.claude/settings.json` | the repository's, committed | honoured | only once [trusted](mcp.md#trusting-a-project) | ignored |
16+ | `.claude/settings.local.json` | yours, this project, git-ignored | honoured | honoured | `acceptEdits` only |
17+
18+ The rule underneath that table is who wrote the file. A deny or an ask rule can
19+ only make the agent do less or ask more, and nobody comes to harm from that, so
20+ a repository may add as many as it likes. An allow rule is the opposite: it is
21+ a decision that something runs without you being asked, and a decision written
22+ into a repository was made by whoever wrote the repository. The shared file's
23+ allow rules therefore wait for the same trust question as its `.mcp.json`, and
24+ until you answer it they are listed and ignored. A `settings.local.json` that
25+ git turns out to be tracking arrived with the clone, whatever its name says,
26+ and is treated as shared.
27+
28+ The rules act before anything else in the approval policy. A deny holds in
29+ every mode, `-yolo` included — that is what writing it down is for — and it
30+ holds for the read-only tools too, since `Read(.env)` is a rule about reading.
31+ An ask rule puts the call to you even when the mode would have let it through,
32+ and even when you said "always this tool" earlier in the session. An allow rule
33+ lets a call through without a prompt. Between them deny beats ask beats allow,
34+ as in Claude Code, so no allow rule anywhere argues a call past a deny. The
35+ denial the model reads names the rule and the file, so it can tell you which
36+ line to change instead of retrying. `/allowed` lists every rule under the file
37+ it came from, and says which are in force and why the others are not.
38+
39+ Claude Code's names map onto Codey's: `Bash` is `bash`, `Read` is `read_file`
40+ (and, as best it can, `glob` and `grep`), `Edit` is both `edit_file` and
41+ `write_file`, `MultiEdit` is `edit_file`, `Write` is `write_file`, `Glob`,
42+ `Grep`, `WebFetch`, `WebSearch` and `TodoWrite` are the obvious ones, and
43+ `mcp__server` and `mcp__server__tool` are unchanged. A rule naming a tool Codey
44+ does not have, or written in a form it does not understand, is reported at
45+ startup and takes no part. Paths follow Claude Code's conventions: `//path` is
46+ absolute, `~/path` is under your home, `/path` is under the project root, and
47+ anything else is relative to it, with a bare name such as `.env` matching at
48+ any depth the way `.gitignore` would. Links are followed, so a rule about
49+ `.env` also catches a symlink to it.
50+
51+ `Bash(npm run test:*)` is matched one simple command at a time, using the same
52+ parser as the approval prompt. An allow rule covers `npm run test && rm -rf ~`
53+ only if something also allows `rm`; a deny rule catches the line if it catches
54+ any part of it. A command the parser cannot see through — `$(...)`, backticks,
55+ `bash -c`, a variable in command position — never matches an allow rule, and
56+ where a deny rule cannot be ruled out for it, the call is asked about rather
57+ than run. So is anything that does more than its words say: `PATH=. git
58+ status`, or `echo hi > ~/.bashrc`, is not covered by an allow rule for `git` or
59+ `echo`. None of this makes shell allowlisting a security boundary — see
60+ [Safety](safety.md) — but it does stop the commonest way a prefix rule says yes to
61+ something nobody wrote down.
62+
63+ `defaultMode: "acceptEdits"` in a file of yours lets file edits through, as
64+ Claude Code's mode of that name does. `default` is the default. The other modes
65+ are ignored with a notice, from every file: `bypassPermissions` in particular,
66+ because in Codey the way to stop being asked is `-yolo`, typed on the command
67+ line for one run, and neither a file of yours nor a repository's gets to say it
68+ for you.
69+
70+ **CLAUDE.md.** The files Claude Code loads into every conversation are loaded
71+ into this one's system prompt, at startup, in Claude Code's order:
72+ `~/.claude/CLAUDE.md`, then — from the top of the project down to the working
73+ root — each directory's `CLAUDE.md`, `.claude/CLAUDE.md` and `CLAUDE.local.md`.
74+ The top is the nearest directory with a `.git` in it, or your home directory if
75+ there is none on the way; a root that is in neither is a project of one
76+ directory, because walking up to `/` would read whatever happened to be lying
77+ in `/tmp`. Later files are more specific and the prompt says they win.
78+ `@path/to/file` imports are followed, relative to the file they appear in and up
79+ to five deep, skipping anything in a code block. A project's files may import
80+ only from the project, and that is checked where symlinks lead: a repository's
81+ CLAUDE.md that could import `@~/.aws/credentials` would be a repository reading
82+ your credentials into a conversation that is then sent to the model. Your own
83+ `~/.claude/CLAUDE.md` and `CLAUDE.local.md` may import from your home
84+ directory. The whole thing is read once per session, so the cached prompt
85+ prefix stays the same from request to request; restart to pick up an edit.
86+ `AGENTS.md` is not loaded this way, but the agent is still told to read one if
87+ the repository has it, as before, and a CLAUDE.md that says `@AGENTS.md` brings
88+ it in.
89+
90+ **What is not carried over**, so nobody has to find out the hard way:
91+
92+ - Hooks in Claude Code's format (`PreToolUse`, `PostToolUse` and the rest).
93+ Codey's [hooks](hooks.md) are a different thing — they watch, they cannot
94+ veto — and a script written to block a tool call would silently block
95+ nothing here.
96+ - `env`, `model`, `apiKeyHelper`, `statusLine`, `outputStyle`, `cleanupPeriodDays`
97+ and the other non-permission settings. Codey has its own config for the ones
98+ it has an equivalent of.
99+ - `permissions.additionalDirectories`: the file tools keep the working root as
100+ their only root.
101+ - Claude Code's own record of which `.mcp.json` servers you approved
102+ (`enabledMcpjsonServers`, `enableAllProjectMcpServers`). It approves by name,
103+ so a server whose command changed under the same name would still count;
104+ Codey asks its own question about the exact definitions instead.
105+ - Managed (enterprise) settings, subdirectory CLAUDE.md files loaded on demand,
106+ and `.claude/rules/`.
107+ - Remote MCP servers, which Codey's client does not speak.
+71−0docs/user/editors.mdaddedView File
@@ -0,0 +1,71 @@
1+ # Inside another editor
2+
3+ Codey can be the agent behind somebody else's UI. It speaks the
4+ [Agent Client Protocol](https://agentclientprotocol.com) — JSON-RPC over stdin
5+ and stdout — so a control plane that drives Codex, Claude Code or Cursor can
6+ drive this instead:
7+
8+ ```bash
9+ codey agent stdio
10+ ```
11+
12+ Nothing but protocol frames goes to stdout in that mode; diagnostics go to
13+ stderr. The connection ends when stdin does.
14+
15+ ## T3 Code
16+
17+ [T3 Code](https://t3.codes) compiles its providers in, so there is no Codey
18+ entry to select. There is, however, a binary path on every provider, and its
19+ Grok provider is a plain ACP agent — which means Codey can take that slot with
20+ no change to their source. In **Settings → Providers**, enable Grok, set
21+ **Binary path** to your `codey`, and rename the instance:
22+
23+ ```json
24+ "providerInstances": {
25+ "codey": {
26+ "driver": "grok",
27+ "displayName": "Codey",
28+ "enabled": true,
29+ "config": { "binaryPath": "/path/to/codey" },
30+ "environment": [
31+ { "name": "ANTHROPIC_API_KEY", "value": "sk-ant-...", "sensitive": true }
32+ ]
33+ }
34+ }
35+ ```
36+
37+ That file is `~/.t3/userdata/settings.json`; the settings panel writes the same
38+ thing. The per-instance environment is how Codey gets a credential without
39+ inheriting whatever the desktop app was launched with — and without it, the
40+ provider card will say Codey is installed and not logged in, which is true.
41+
42+ Threads stream text and reasoning, fill a tool row in as the command talks,
43+ raise an approval prompt you answer in T3 Code's own panel, and stop when you
44+ press stop. The model picker is filled from the endpoint Codey is pointed at.
45+
46+ [Subagents](subagents.md) work too. Each one gets a row of its own that opens
47+ when the child starts, fills in as it works, and closes with its report, and
48+ the reports reach the parent as messages exactly as they do in the TUI. A turn
49+ stays open while its children are still working — their output belongs inside
50+ the turn that started them, and a thread that looked finished while four agents
51+ were still running would be lying. `-no-agents` in the launch arguments turns
52+ them off.
53+
54+ What does not work is resuming a thread after a restart: Codey does not
55+ advertise session loading, so T3 Code will not offer it.
56+
57+ The card will say Grok and show the xAI glyph. The display name and accent
58+ colour are yours to set; the icon is not. That is the price of occupying a slot
59+ instead of adding one.
60+
61+ Two invocations exist only for that provider card: `codey --version`, and
62+ `codey models`, which prints whether there is a credential and what the
63+ endpoint serves. Both are shaped for the regular expressions on the other end
64+ rather than for a person. The probe follows the same opt-in as everything else:
65+ a Claude Code login counts as logged in only when the config says
66+ `"claude_code_auth": "on"` — the probe is run with no flags, so
67+ `-claude-code-auth` in the launch arguments reaches the agent but not the card.
68+
69+ [The T3 Code provider design](../superpowers/specs/2026-09-19-t3code-provider-design.md) is the research
70+ this is built on: what every T3 Code provider requires of a binary, and why the
71+ Grok slot is the one that fits.
+55−0docs/user/flags.mdaddedView File
@@ -0,0 +1,55 @@
1+ # Flags
2+
3+ The flags worth knowing: `-model`, `-effort low|medium|high|xhigh|max`, `-max-turns`,
4+ `-resume latest`, `-no-stream`, `-no-cache`, `-no-auto-compact`, `-v`, `-yolo`,
5+ `-no-tui`, `-no-skills`, `-no-agents`, `-no-web`, `-no-hooks`,
6+ `-no-diagnostics`, `-no-mcp`, `-claude-code-auth`, `-no-claude-code-auth`,
7+ `-trust-project`. And the ones a script written for Claude Code passes, with
8+ Claude Code's meaning — see [using it in place of
9+ `claude`](in-place-of-claude.md): `--output-format
10+ text|json|stream-json`, `-c`/`--continue`, `--allowedTools`,
11+ `--disallowedTools`, `--permission-mode`, `--dangerously-skip-permissions`,
12+ `--append-system-prompt`, `--system-prompt`, `--verbose`, `--print`. Every flag
13+ takes one dash or two.
14+
15+ ## Flags and thresholds
16+
17+ `-compact-at` (default 0.7) is where the conversation is normally folded into a
18+ summary, keeping the last six messages verbatim. Early and rare is deliberate:
19+ compaction is itself a model call with the history in it, and it is the single
20+ biggest thing that breaks prompt caching.
21+
22+ `-compact-floor` (default 0.97) is the fraction past which compaction stops
23+ being optional. The normal threshold declines a history it cannot split safely —
24+ a handful of enormous tool results is too few messages to cut six off the end of
25+ — and below the floor that restraint is right. Past it, a verbatim tail is worth
26+ nothing if the next request has no room to be sent, so the floor folds anyway
27+ and settles for a one-message tail. `/compact` follows the same rule past the
28+ floor, since with auto-compaction off it is the only move left. When even that
29+ finds nothing to fold, Codey says so, rather than letting the next
30+ request fail as a bare API error.
31+
32+ `-context-window` (default 1,000,000) is what both fractions are measured
33+ against; lower it for a model with less room.
34+
35+ `-max-turns` caps one prompt, and by default there is no cap: a turn count is a
36+ bad proxy for the thing it was guarding against. A task is not finished at turn
37+ thirty and unfinished at thirty-one, and a run that stops there has thrown away
38+ a job that was going fine. What bounds an interactive session is the context
39+ window, the bill, and a person who can press esc.
40+
41+ An unattended run has none of those, which is what the flag is for: set it in
42+ `-p` mode and in CI, where nobody is watching and a model stuck repeating one
43+ failing tool call would otherwise repeat it until the money ran out. When a cap
44+ you asked for does trip, the conversation is left intact and the last thing the
45+ model said comes back with the error rather than being thrown away — say what
46+ to do next, or raise it.
47+
48+ `-no-auto-compact` turns the automatic fold off for one run, the same as
49+ `"auto_compact": "off"` in the config. The floor then stops the turn instead of
50+ folding: the session stays intact, nothing is summarized behind your back, and
51+ `/compact` is what makes room. In `-p` mode the stop is a non-zero exit.
52+
53+ `-no-agents` leaves out the subagent tools, so the agent works alone. `-no-web`
54+ takes away web access for one run, whatever the config says; like `-yolo` in
55+ reverse, it can only subtract.
+146−0docs/user/hooks.mdaddedView File
@@ -0,0 +1,146 @@
1+ # Hooks
2+
3+ Codey publishes its lifecycle — a session opened, a run started, a tool ran, an
4+ agent stopped to ask you something — so that things outside the process can
5+ watch it. That is a different job from the event stream the TUI draws from:
6+ this one carries no conversation content, no text deltas and no tool output,
7+ because its audience is a sidebar, a notifier, or a script, and what the model
8+ wrote is not theirs to have.
9+
10+ | Event | When |
11+ | --- | --- |
12+ | `session_start` / `session_end` | one conversation, from open to close; a second tab is a second session |
13+ | `run_start` / `run_end` | one prompt, from your message to the model's final answer, tool calls and all |
14+ | `turn_start` | one model call inside a run |
15+ | `tool_start` / `tool_result` | one tool call; the result says whether it failed, not what it returned |
16+ | `blocked` / `unblocked` | waiting on a human: an approval prompt, or a question from `ask_user` |
17+ | `paused` / `resumed` | the loop held at a turn boundary, and let go again |
18+
19+ `blocked` is the one you cannot get any other way. From outside the process an
20+ agent thinking hard and an agent that has been sitting on an approval prompt
21+ for ten minutes look exactly alike: the run has not returned and nothing is
22+ being printed. Only one of them will finish without you.
23+
24+ Hooks in the config run shell commands:
25+
26+ ```json
27+ {
28+ "hooks": {
29+ "run_end": ["notify-send 'codey' \"$CODEY_SESSION finished\""],
30+ "blocked": ["notify-send -u critical 'codey needs you' \"$CODEY_MESSAGE\""]
31+ }
32+ }
33+ ```
34+
35+ Each command gets the event twice over: as `CODEY_HOOK_EVENT`, `CODEY_SESSION`,
36+ `CODEY_AGENT`, `CODEY_TURN`, `CODEY_TOOL`, `CODEY_REASON`, `CODEY_MESSAGE` and
37+ `CODEY_ERROR` — always exported, sometimes empty, so a one-liner never has to
38+ test first — and as the same fields in JSON on stdin, for anything that wants a
39+ parser rather than string surgery. They run in the working root, one after
40+ another, on the agent's own goroutine, which is why each is given ten seconds
41+ and then abandoned: a hook that hangs would otherwise be a session that hangs.
42+ Output is swallowed unless the command fails, since the TUI owns the screen; a
43+ failure is written to the event log as a `hook_error` and changes nothing about
44+ the run. An unknown event name is refused at startup rather than ignored,
45+ because a hook that silently never fires is the one bug with nothing to notice.
46+
47+ This is the single setting the wizard never writes. A hook is a command your
48+ machine runs on your behalf, so it gets into the file because you typed it
49+ there. `-no-hooks` turns the lot off for one run.
50+
51+ ## herdr
52+
53+ [herdr](https://herdr.dev) is a terminal workspace manager for coding agents: a
54+ background server owns the terminals, panes survive a detach, and a sidebar
55+ shows across every project which agent is working, which is blocked, and which
56+ is idle. Codey reports itself to herdr, automatically — there is nothing to
57+ install and nothing to configure. Run it in a pane and it says so at startup:
58+
59+ ```
60+ codey: herdr detected — reporting agent state to pane w1:p1
61+ ```
62+
63+ Herdr reads most agents' states off the screen with pattern rules. Codey is
64+ told instead, through the contract herdr documents for agents it does not ship
65+ support for: a process inside a pane inherits `HERDR_ENV=1`, `HERDR_PANE_ID`
66+ and `HERDR_BIN_PATH`, and reports semantic state by running `herdr pane
67+ report-agent`. Being told beats guessing in both directions — herdr never has
68+ to infer anything from what the TUI happened to draw, and the state it could
69+ not see at all, that the agent is sitting on an approval prompt behind a
70+ full-screen view, is stated outright along with what it is waiting for:
71+
72+ ```
73+ bash: rm -rf build
74+ asked about Storage, Error handling
75+ ```
76+
77+ A subagent's prompt counts. Spawning does not block, so a background agent can
78+ ask for approval long after the agent that spawned it went quiet, and a pane
79+ that called that idle would leave you waiting on an agent that is waiting on
80+ you. Two agents can be waiting at once, and answering one does not clear the
81+ other: the pane goes back to what it was doing only when the last prompt is
82+ answered.
83+
84+ One pane holds one Codey and one Codey can hold several tabs, so the tabs are
85+ reduced to the single state the pane is in: blocked beats working beats idle.
86+ What a person scanning a sidebar needs to find is the agent that stopped and
87+ wants them, and after that the one still going. A paused agent reports idle
88+ rather than blocked — nobody is being waited on, and it has no business in the
89+ list of agents that need attention.
90+
91+ Every report carries Codey's session id, so herdr's pane and agent APIs show
92+ which conversation is running in a pane and a restored one can be pointed back
93+ at it with `-resume`. Herdr restarts an agent by itself only where it knows how
94+ to launch it, which for now means the agents in its own registry; the reference
95+ is reported regardless, because it costs one flag and is the half of the
96+ problem that is ours.
97+
98+ Reporting is a no-op anywhere else. Detection is those three environment
99+ variables and nothing else — no probing, no socket, no cost to anyone who has
100+ never heard of herdr — and all three are required, because a stale `HERDR_ENV`
101+ exported into a shell that outlived its pane is exactly the case where
102+ reporting would go nowhere, slowly, forever. `-no-hooks` disables it too.
103+
104+ When a state looks wrong, `herdr agent list` shows what herdr sees, and the
105+ `hook_error` lines in `.codey/events.jsonl` show what Codey could not tell it.
106+
107+ ### The herdr skill
108+
109+ Reporting is one direction. The other is the agent driving herdr itself —
110+ splitting a pane, starting a server in it without stealing your focus, reading
111+ what another pane printed, waiting until another agent is genuinely blocked
112+ rather than merely slow. Herdr publishes a skill that teaches an agent to do
113+ that, and [the setup wizard](setup.md) offers to install it when it finds herdr
114+ on the machine:
115+
116+ ```
117+ Install herdr's skill, so the agent can drive herdr?
118+
119+ ▌ ◆ yes, install it ~/.agents/skills/herdr
120+ ◇ no
121+ ```
122+
123+ Yes downloads [herdr's `SKILL.md`](https://github.com/herdrdev/herdr/blob/master/skills/herdr/SKILL.md)
124+ and writes it to `~/.agents/skills/herdr/SKILL.md`, which is where Codey looks
125+ for personal skills — the same layout herdr publishes and the same one
126+ `npx skills add` writes, so a skill installed by either is found by both. From
127+ the next start it is listed to the agent like any other skill, and invoked the
128+ same way.
129+
130+ It is fetched rather than shipped inside the binary. The file is a manual for a
131+ CLI that is still moving, and a copy frozen into a Codey release would go on
132+ teaching last year's flags long after they stopped working. The cost is that
133+ the install needs the network: when it fails, nothing is written, the wizard
134+ says why, and the answers you just gave are saved regardless — only the
135+ download is lost, and `/setup` offers it again.
136+
137+ Nothing is written unread. What comes back is parsed by the same loader that
138+ will read it at startup, and rejected if it is not a skill Codey can load —
139+ a login page, an error page, or a file whose shape has changed. The write goes
140+ through a temporary file in the same directory, so a failed update leaves the
141+ copy you already had rather than half of a new one.
142+
143+ This is the only question in the wizard that acts rather than records, so it is
144+ the only one where pressing enter through the screens does something: the row
145+ names the exact path before you answer, and the notice afterwards names the
146+ file that was written. Uninstalling is `rm -r ~/.agents/skills/herdr`.
+128−0docs/user/in-place-of-claude.mdaddedView File
@@ -0,0 +1,128 @@
1+ # Using it in place of `claude`
2+
3+ A lot of CI jobs and shell scripts run an agent as `claude -p`. Codey reads the
4+ same flags with the same meaning, so pointing one of them at Codey is meant to
5+ be a matter of changing the binary's name:
6+
7+ ```bash
8+ codey -p --output-format json "why does TestFoo fail?" | jq -r .result
9+ echo "summarise the last commit" | codey -p --allowedTools "Bash(git log:*)" "Bash(git show:*)"
10+ codey -c -p "now fix it" # same session
11+ ```
12+
13+ | Claude Code flag | In Codey |
14+ | --- | --- |
15+ | `-p`, `--print` | One prompt, then exit. The prompt may follow `-p` — Codey's own spelling — or be a positional argument anywhere on the line, as `claude` takes it, or arrive on stdin when there is no other |
16+ | `--output-format text` | The final answer and nothing else on stdout |
17+ | `--output-format json` | One result object at the end |
18+ | `--output-format stream-json` | One JSON line per message as the run goes, then the result object |
19+ | `--verbose` | The same as `-v`. With `json` it prints every line `stream-json` would have, as one array, which is what Claude Code does; `stream-json` does not need it |
20+ | `-c`, `--continue` | `-resume latest`: the most recent session in this working root |
21+ | `--resume <id>` | Unchanged; the id is a Codey session id, which is what `session_id` in the output holds |
22+ | `--model`, `--max-turns` | Codey's own flags, already spelled the same |
23+ | `--allowedTools`, `--allowed-tools` | Calls to approve without asking, as rules |
24+ | `--disallowedTools`, `--disallowed-tools` | Tools to take away, or calls to refuse, as rules |
25+ | `--permission-mode` | `default`; `acceptEdits`, which lets edits and writes through and still asks before a command — the mode the ACP front end has by the same name; `bypassPermissions`, which is `-yolo`; `dontAsk`, which refuses whatever no rule allows |
26+ | `--dangerously-skip-permissions` | `-yolo` |
27+ | `--append-system-prompt`, `--system-prompt` | Add to the end of Codey's system prompt, or replace it. The session's own agent only: a subagent's prompt is its preset's |
28+ | `--include-partial-messages` | Accepted, with a warning, and ignored |
29+
30+ Without `--output-format`, `-p` prints what it always has: the transcript as it
31+ streams, the tool calls, a usage line — for a person watching, not a parser.
32+ Asking for a format is asking for Claude Code's behaviour, and then stdout holds
33+ the format and nothing else; notices, warnings and any approval prompt go to
34+ stderr.
35+
36+ The shapes are Claude Code's, because the programs reading them were written
37+ against it. The result object carries `type`, `subtype` (`success`,
38+ `error_max_turns` or `error_during_execution`), `is_error`, `result`,
39+ `session_id`, `num_turns`, `duration_ms`, `duration_api_ms`, `total_cost_usd`
40+ and `usage` with the four token counts. In `stream-json` it is preceded by a
41+ `system`/`init` line — working directory, model, tools, MCP servers, permission
42+ mode — then an `assistant` line per response, holding the message as the
43+ Messages API returned it, and a `user` line per batch of tool results. Numbers
44+ are this invocation's: a resumed session reports what the resume cost, not
45+ everything before it.
46+
47+ A run that fails exits 1, whatever the format, and the JSON formats still print
48+ their result object — with `is_error: true`, the error in `errors`, and in
49+ `result` whatever the model last said, or the error itself when it said
50+ nothing. That includes failing before the model was ever asked: no credentials
51+ gives a result object, with an empty `session_id` because there was no session
52+ yet. Check `is_error` rather than `subtype`: it is the field that means
53+ failure in both, where the subtypes for a failure are not guaranteed to agree
54+ between the two. A flag
55+ Codey does not know stops the run before any of this, with exit 2, and so does
56+ a value it cannot use — `--permission-mode plan`, say — with exit 1 and a
57+ message on stderr.
58+
59+ **Who gets asked.** Claude Code never prompts in `-p` mode, and with
60+ `--output-format` neither does Codey when stdin is not a terminal: what no rule
61+ allows is refused, and the model is told so and carries on. That is the case in
62+ CI. At a terminal the prompt still comes up, on stderr. Codey's own `-p`
63+ without a format keeps reading answers from stdin, so `yes | codey -p ...`
64+ works as it always has — except when the prompt itself came from stdin, which
65+ leaves nothing there to answer with.
66+
67+ **Rules.** `--allowedTools` and `--disallowedTools` take Claude Code's rule
68+ syntax, comma- or space-separated, as many as you like after one flag:
69+ `Bash`, `Bash(git status)`, `Bash(npm run test:*)`, `Read`, `Read(./src/**)`,
70+ `Edit(./docs/**)`, `Write`, `Glob`, `Grep`, `WebFetch(domain:example.com)`,
71+ `WebSearch`, `TodoWrite`, `Task`, `mcp__github`, `mcp__github__create_issue`.
72+ Claude Code's names map onto Codey's tools — `Bash` is `bash`, `Read`
73+ `read_file`, `Write` `write_file`, and `Edit` both `edit_file` and
74+ `write_file`, because Claude Code applies its Edit rules to every tool that
75+ edits files. Codey's own names work as well. A rule naming a tool Codey does
76+ not have, `NotebookEdit` say, is a warning, and the rest of the list still
77+ applies.
78+
79+ A rule for a whole tool on the disallowed list takes the tool away: the model
80+ is never shown it. A narrower one — `Bash(git push:*)`, `Read(./.env)` — is
81+ checked at approval time, before anything else, `-yolo` included, and a call it
82+ matches is refused. On the allowed list a rule stands in for the "yes" you
83+ would otherwise have been asked for.
84+
85+ `Bash(prefix:*)` matches word by word, so `Bash(ls:*)` covers `ls -la` and not
86+ `lsof`. A compound command is allowed only when every simple command in it is
87+ covered, so `git status; rm -rf ~` does not ride in on `Bash(git status:*)`, and
88+ anything the [shell analysis](safety.md) cannot see through — command
89+ substitution, a variable in command position, an inline `bash -c` script, an
90+ assignment like `PATH=...`, output redirected into a file — is never allowed by
91+ a narrower rule than `Bash`. A deny rule is the other way round: one matching
92+ part is enough, and it looks inside `bash -c` and `eval`, past `sudo`, `env`
93+ and `timeout`, and at `/bin/rm` as `rm`. That catches the command a model
94+ would ordinarily write. It is not a sandbox, for the reason the Safety section
95+ gives.
96+
97+ Path rules read like gitignore, as Claude Code's do: `./` and `/` are the
98+ working root, `//` an absolute path, `~/` your home, and a pattern with no
99+ slash in it — `.env`, `*.pem` — matches a name at any depth. Path rules on
100+ `Read` apply to `glob` and `grep` too, as far as their `path` argument goes.
101+
102+ What does not work:
103+
104+ - `--permission-mode plan`. Codey has no read-only planning mode, and says so
105+ rather than run a mode it does not have. Ask for a plan in the prompt, and
106+ `--disallowedTools Edit,Write,Bash` holds the run to it.
107+ - `--input-format stream-json`, and so a conversation fed in over stdin. The
108+ prompt is text.
109+ - `--include-partial-messages`: there are no `stream_event` lines, only whole
110+ messages.
111+ - `--mcp-config` and `--strict-mcp-config`. MCP servers come from
112+ [Codey's config](mcp.md).
113+ - `--settings`, `--setting-sources`, `--add-dir`, `--fallback-model`,
114+ `--session-id`, `--fork-session`, and `--resume` with no id, which in Claude
115+ Code opens a picker.
116+ - Piping a file in alongside a prompt. `cat log | claude -p "explain"` hands
117+ Claude Code both; Codey reads stdin only when there is no prompt otherwise,
118+ because a CI runner can leave stdin as a pipe nobody ever closes, and a run
119+ that waited on it would hang there for good. Put it in the prompt:
120+ `codey -p "explain: $(cat log)"`.
121+ - A prompt without `-p`, which in Claude Code starts an interactive session
122+ with that prompt. Codey ignores it and says so.
123+ - Tool names in the output are Codey's — `bash`, `read_file` — not Claude
124+ Code's. A script that looks for `"name":"Bash"` in the stream needs to look
125+ for `bash`.
126+ - The result object has no `permission_denials` or `modelUsage`, and lines
127+ carry no `parent_tool_use_id` other than `null`: subagents report through a
128+ tool result rather than in the parent's stream.
+109−0docs/user/install.mdaddedView File
@@ -0,0 +1,109 @@
1+ # Install and first run
2+
3+ Codey is one static binary with nothing to install alongside it, so there are
4+ three ways to get it and they differ only in what you need to have already.
5+
6+ **A release.** The [releases page](https://github.com/johannSo/codey/releases)
7+ has an archive for Linux and macOS, each on amd64 and arm64, with
8+ `checksums.txt` beside them. Unpack the one for your machine and put `codey`
9+ somewhere on your `PATH`. The archive names carry no
10+ version, so the latest one is always at the same address:
11+
12+ ```bash
13+ # linux or darwin, amd64 or arm64
14+ curl -L https://github.com/johannSo/codey/releases/latest/download/codey_linux_amd64.tar.gz | tar xz codey
15+ ```
16+
17+ **With Go.** If you have Go installed, this builds the latest release and puts
18+ it in `$(go env GOPATH)/bin`:
19+
20+ ```bash
21+ go install github.com/johannSo/codey/cmd/codey@latest
22+ ```
23+
24+ `@latest` can be any tag, and `codey --version` reports the one you asked for.
25+ The Go it needs is the one `go.mod` names; an older `go` command fetches that
26+ toolchain by itself unless `GOTOOLCHAIN` says not to.
27+
28+ **From source.** For working on Codey, or running a commit that is not
29+ released yet:
30+
31+ ```bash
32+ git clone https://github.com/johannSo/codey
33+ cd codey
34+ go build -o codey ./cmd/codey
35+ ```
36+
37+ A build from a checkout reports the version the source was last released as,
38+ not the commit; `git log` is the better answer to "which one is this". The
39+ examples in these docs run it as `./codey`, from the directory it was built
40+ in. Installed on your `PATH` it is just `codey`.
41+
42+ ## macOS
43+
44+ Release binaries are not signed or notarized. A download through a
45+ browser is quarantined and Gatekeeper refuses to open it; `xattr -d
46+ com.apple.quarantine codey` lifts that. The `curl` above does not quarantine
47+ what it fetches, and neither does `go install`.
48+
49+ ## Quick start
50+
51+ ```bash
52+ ./codey -C /path/to/a/throwaway/clone # full-screen TUI
53+ ./codey -C /path/to/repo -p "why does TestFoo fail?" # one prompt
54+ ```
55+
56+ Codey needs a credential, and the ordinary one is an Anthropic API key, which
57+ you create at
58+ [console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys):
59+
60+ ```bash
61+ export ANTHROPIC_API_KEY=sk-ant-...
62+ ```
63+
64+ Started with nothing at all, Codey stops and says how to get one rather than
65+ failing on the first request.
66+
67+ A different endpoint — a gateway, a proxy, a local server speaking the Messages
68+ API:
69+
70+ ```bash
71+ ./codey -base-url http://127.0.0.1:4000 -api-key ... -C .
72+ # or ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN
73+ ```
74+
75+ Flags beat the saved config, which beats the built-in defaults; the config is
76+ what [the setup wizard](setup.md) writes on a first run. `-auth-token` sends
77+ `Authorization: Bearer`, which is what OAuth tokens and most gateways want;
78+ `-api-key` sends `x-api-key`. A non-default endpoint announces itself on
79+ stderr, because your prompt and every file the agent reads go wherever that URL
80+ points.
81+
82+ On your own machine, if you are logged into Claude Code, Codey can borrow the
83+ OAuth credential Claude Code keeps in `~/.claude/.credentials.json` instead of
84+ needing a key. It does this only when asked: `-claude-code-auth` for one run, or
85+ `"claude_code_auth": "on"` in [the config](setup.md) — which the setup wizard and
86+ `/settings` both offer — for every run. Be aware of what that is: the
87+ credential is issued to Claude Code, and using it from another client is
88+ outside Anthropic's terms for it, which API keys and gateway tokens do not
89+ carry. That is why it is off until you turn it on, and why it is not something
90+ to set up on a machine other people use.
91+
92+ Even when asked for, it is the last resort. It is tried only when no flag and
93+ no environment variable supplied a credential, and only against Anthropic's own
94+ endpoint — a credential found on disk is not one you chose to send, so it is
95+ never forwarded to a `-base-url` you pointed Codey at. `-no-claude-code-auth`
96+ refuses it for one run, whatever `-claude-code-auth` and the config say.
97+
98+ Earlier builds borrowed the login silently whenever nothing else was set. If
99+ you relied on that, the first start after upgrading asks once, on the terminal,
100+ whether to keep doing so, and saves the answer as `claude_code_auth` — yes or
101+ no, so it is not asked again. A `-p` run, a pipe, and `codey models` have
102+ nobody to ask, so they just report that there is no credential and how to get
103+ one.
104+
105+ On macOS Claude Code keeps this in the Keychain rather than a file, so there is
106+ nothing to borrow there. An expired access token is refreshed automatically; the
107+ credentials file is left alone unless the server rotates the refresh token, in
108+ which case the replacement is written back — leaving the dead one there is what
109+ would break your Claude Code login.
+107−0docs/user/mcp.mdaddedView File
@@ -0,0 +1,107 @@
1+ # MCP servers
2+
3+ Codey is an MCP client over stdio. Servers go in the config by hand, in the
4+ same shape Claude Code and Codex use, so an entry can be carried across from
5+ either unchanged:
6+
7+ ```json
8+ {
9+ "mcp_servers": {
10+ "fs": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] },
11+ "db": { "command": "my-db-mcp", "env": { "DB_URL": "postgres://localhost/dev" } }
12+ }
13+ }
14+ ```
15+
16+ Each server is started once, when the session opens, in the working root, and
17+ every tool it lists is offered as `mcp__<name>__<tool>` — the name being the
18+ key you gave it, not whatever the server calls itself. Subagents get the same
19+ tools from the same processes. Every call prompts like `bash` does, with
20+ "always this tool" to stop asking. A server that is not installed, does not
21+ answer within thirty seconds or speaks a protocol version Codey does not is
22+ reported at startup and left out; the session goes on without it. A server
23+ that dies later is not restarted, and its tools say so when called.
24+
25+ Only tools are used: not resources, prompts or sampling, and no HTTP
26+ transport. Pictures a tool returns are described to the model rather than
27+ shown. A server's directory is fixed when it starts, so after `/worktree` it
28+ is still serving the root the session began in. `-no-mcp` starts none of them
29+ for one run — Codey's own and Claude Code's alike.
30+
31+ ## Claude Code's servers
32+
33+ The servers Claude Code already knows about are started as well, read from
34+ where Claude Code keeps them rather than copied into Codey's config:
35+
36+ | Where | Whose | Started |
37+ | --- | --- | --- |
38+ | `~/.claude.json`, under `projects["<root>"].mcpServers` | yours, for this project — what `claude mcp add` writes | always |
39+ | `.mcp.json` at the working root | the repository's, committed | once you [trust it](#trusting-a-project) |
40+ | `~/.claude.json`, top-level `mcpServers` | yours, for every project — `claude mcp add -s user` | always |
41+
42+ The shape is the one already shown above, `{"mcpServers": {name: {command,
43+ args, env}}}`, with `"type": "stdio"` accepted and meaning the same thing. A
44+ server with `"type": "http"` or `"sse"`, or with a `url`, is a remote server;
45+ Codey's client speaks only stdio, so it is reported at startup and skipped.
46+ `${VAR}` and `${VAR:-default}` are expanded in the command, the arguments and
47+ the env values, as Claude Code expands them — so a `.mcp.json` can say
48+ `"${DB_URL}"` and leave the secret in your environment. A variable that is
49+ unset and has no default skips that server with a notice naming it, rather
50+ than starting it with a hole in its command line.
51+
52+ When two places define a server of the same name, Codey's own `mcp_servers`
53+ win, and after that the table is read top to bottom: your per-project servers,
54+ then the repository's, then your global ones. That is Claude Code's own order,
55+ so a name means the same program in both. Each clash is reported once, naming
56+ the one that won, because a server that quietly turned out to be a different
57+ program is not something anyone thinks to debug.
58+
59+ ### Trusting a project
60+
61+ A `.mcp.json` comes from the repository. Starting a server listed there means
62+ running a command — with your environment, your credentials, your network —
63+ that whoever wrote the repository chose, before the agent has done anything
64+ and outside every approval prompt. So, like Claude Code, Codey asks first. The
65+ first interactive start in a checkout whose `.mcp.json` or shared
66+ `.claude/settings.json` [allow rules](claude-code-settings.md) would grant
67+ something prints, before the TUI opens:
68+
69+ ```
70+ Claude Code's configuration for /home/you/src/thing would:
71+
72+ start these MCP servers, from .mcp.json:
73+ db: npx -y @acme/db-mcp --url ${DB_URL} (env: DB_TOKEN)
74+ run these without asking, from .claude/settings.json:
75+ Bash(make:*)
76+
77+ These files come from the repository, not from you: trusting them lets it
78+ choose commands this machine runs. Your answer is remembered until they change.
79+ Trust them for this checkout? [y/N]
80+ ```
81+
82+ One question covers both files, because it is one question: do you trust what
83+ this repository says about itself. The full command line is shown because that
84+ is the thing being agreed to. The answer — yes or no — is remembered for this
85+ checkout against a fingerprint of exactly those definitions, as written, before
86+ any `${VAR}` is expanded. A new server, a changed argument or one more allow
87+ rule is a different fingerprint, and the question comes back; nothing else
88+ does. A refusal is remembered too, so saying no once is not a question at every
89+ start.
90+
91+ The record is kept in `~/.config/codey/trust.json`, not in the project's
92+ `.codey/` directory with the rest of the per-repository state. That is the
93+ point of it: anything inside the working tree can arrive with a clone, and a
94+ repository that shipped its own record of being trusted would be approving its
95+ own servers. The fingerprint would be no defence there — whoever wrote the
96+ `.mcp.json` can compute it. Only a file the repository cannot reach can say what
97+ you decided.
98+
99+ Where nobody can be asked — `-p`, `codey agent stdio`, a piped stdin — an
100+ undecided project's servers are not started and its allow rules are ignored,
101+ with a line on stderr saying so. A checkout you have already trusted needs
102+ nothing more. `-trust-project` says yes for one run without asking and without
103+ recording anything, which is what a CI job against a repository you control
104+ wants. Servers in `~/.claude.json` never ask: you put them there, as you put
105+ Codey's own in its config. Once started, every server's tool calls go through
106+ the approval prompts like any other, unless an allow rule such as `mcp__db`
107+ says otherwise.
+70−0docs/user/safety.mdaddedView File
@@ -0,0 +1,70 @@
1+ # Safety
2+
3+ Three layers, in increasing order of how much they actually protect you.
4+
5+ **Path jail.** Every path a tool is given is resolved with
6+ `filepath.EvalSymlinks` and refused if it lands outside the working root,
7+ including through a symlink that points out of the tree. Cheap, and it catches
8+ a lot.
9+
10+ **Approval prompts.** Read-only tools run unattended. `bash`, `edit_file`,
11+ `write_file`, `web_search` and `web_fetch` ask, with "yes / no / always this exact one / always this tool" and
12+ session memory. `/allowed` lists what you have agreed to. `--allowedTools` and
13+ `--disallowedTools` write the same decisions down ahead of time, in Claude
14+ Code's rule syntax — see [using it in place of
15+ `claude`](in-place-of-claude.md) — and a deny rule there is checked
16+ before everything else, `-yolo` included.
17+
18+ Shell commands are parsed with `mvdan.cc/sh/syntax` so the prompt can tell you
19+ what a command will actually run — `git status; rm -rf ~` is shown as running
20+ both `git` and `rm`. **This is not a security boundary**, and Codey does
21+ not pretend otherwise: a command containing `$(...)`, backticks, or an
22+ interpreter with `-c` cannot be analysed at all, so it is marked opaque and
23+ "always allow this exact command" is refused for it. String-matching
24+ allowlists for shell commands do not work; this exists to make the prompt
25+ specific, not to make the decision for you.
26+
27+ [Claude Code's permission rules](claude-code-settings.md) sit in front of all
28+ of that. Its deny rules hold in every mode, `-yolo` included, which makes them
29+ the one part of this layer you can lean on: a call they refuse does not run.
30+ Its allow rules are matched with the same parser, one simple command at a
31+ time, and an opaque command matches none of them — but the warning above
32+ applies to them with full force. `Bash(find:*)` allows `find . -exec rm {} +`,
33+ and no parser will tell you otherwise.
34+
35+ Hooks sit outside all three layers, deliberately: a command in `"hooks"` runs
36+ on every event it is attached to, without a prompt, because you put it in your
37+ own config rather than the agent asking for it. The path jail is what keeps the
38+ agent from adding one — the config lives in `~/.config/codey/`, outside any
39+ working root you would sensibly point Codey at — but the flip side is that
40+ anything able to write that file has arranged to run commands here. `-no-hooks`
41+ is the switch that says no for one run. MCP servers are the same bargain: each
42+ is a program started with your environment when the session opens, and only
43+ its tool calls go through the prompts. `-no-mcp` is theirs. The one kind of
44+ server that does not come from a file you wrote — a repository's `.mcp.json` —
45+ waits for you to [trust it](mcp.md#trusting-a-project) first.
46+
47+ **A sandbox.** This is the layer that actually contains a mistake, and the
48+ Codey cannot provide it for you. The honest recipe on Linux:
49+
50+ ```bash
51+ podman run --rm -it \
52+ -v "$PWD":/work:Z -w /work \
53+ -e ANTHROPIC_API_KEY \
54+ docker.io/library/golang:1.26 \
55+ /work/codey -C /work -yolo
56+ ```
57+
58+ **Decide about egress explicitly.** The container above has network access,
59+ because Codey needs to reach the API — which means the agent's `bash`
60+ has it too. That is the line between "broke my repo" and "read my `.env` and
61+ made an HTTP request". If that matters for the tree you are pointing it at:
62+
63+ - Treat every secret reachable from inside the container as exposed.
64+ - Use a token scoped to nothing else, and a clone rather than your working tree.
65+ - For real isolation, run Codey on the host and put only `bash` in a
66+ network-less namespace. That is not implemented here.
67+
68+ `-yolo` skips every prompt. It is correct inside a container or against a
69+ throwaway clone, and wrong everywhere else. The eval runner sets it, because it
70+ is headless and operates on a temp directory it created.
+213−0docs/user/setup.mdaddedView File

Large diffs are not rendered by default.

+92−0docs/user/skills.mdaddedView File

Large diffs are not rendered by default.

+170−0docs/user/subagents.mdaddedView File

Large diffs are not rendered by default.

+79−0docs/user/tabs.mdaddedView File

Large diffs are not rendered by default.

+64−0docs/user/tools.mdaddedView File

Large diffs are not rendered by default.

+403−0docs/user/tui.mdaddedView File

Large diffs are not rendered by default.

+62−0docs/user/web-search.mdaddedView File

Large diffs are not rendered by default.