Vectrum
Sign In
170 lines · 9.8 KB

Subagents

The agent can start other agents. spawn_agent(preset, task) returns immediately with a short id — a1, a2, … — and that agent runs in the background on its own goroutine while the main agent carries on with whatever it was doing. agent_wait(id) blocks until that one finishes, for the times the parent genuinely cannot proceed without the answer.

A subagent is a whole agent, not a subroutine: its own conversation, its own context window, its own model, its own turn loop. The parent never sees its turns — only the report it ends with. That is the point. Work that would have filled the parent's window with file listings fills the child's instead, and comes back a paragraph long.

Three presets, compiled in rather than configurable, because a preset the model can invent is a blast radius the model can invent:

PresetForTurn budget
explorefinding things, and reporting paths and line numbers60
planworking out how a change should be made, without making it80
generala self-contained piece of work, edits and tests included200

The session itself runs uncapped; these are the one place a budget stays, and they are the reason why. A subagent works alone, several run at once, and the model that spawned it is not checking on it — so each one stops eventually, at a number far past what the job takes. Hitting it means the agent is stuck, not that it was busy, and the pane is the faster way out: ctrl+b, then k. A run that did ask for -max-turns keeps it as a ceiling over these, because a child free to sail past the cap would be a hole in exactly the guarantee the flag is bought for.

All three get the full tool set — read, glob, grep, edit, write, bash, skill — and none of them gets spawn_agent, so subagents cannot spawn subagents.

A preset pins no model and no effort level. Both are arguments to spawn_agent, because what to spend is a judgement about the job and the agent holding the task is the one making it: model picks what runs it — a small fast model for mechanical search, a strong one for work that has to be right — and effort picks how hard it thinks, low through max. Omit either and it inherits the session's. A pinned id here would be a guess about someone else's endpoint as well as about the job: point Codey at a gateway that serves neither, and every spawn of that preset fails before its first turn.

The models it may choose between are in its own system prompt: Codey asks the endpoint what it serves at startup — GET /v1/models, the same route the picker uses — and lists them newest first, with the rule that the newest is the one to reach for unless you asked for another or it has already failed this session. An endpoint that does not answer that route simply gets no listing, and the agent is told that naming no model runs the subagent on whatever the session is using. The list is read once, because it sits in the cached prefix and a prompt that changes mid-session throws the cache away. Subagents do not get this paragraph — they have no such choice to make.

output_config.effort is a per-model capability, not a universal parameter. Haiku 4.5 rejects a request carrying it, and xhigh did not exist before Opus 4.7 — so a request for a level its model does not take goes out without it, rather than failing with a 400 that reads like a bug in Codey. Where that happens to a subagent the answer says so outright (claude-haiku-4-5 takes no effort level, so "low" was not applied) instead of letting the parent believe it got what it asked for, and the effort pill in the status line reads not on Haiku rather than claiming a setting that is not in force.

A model this build has never heard of — a gateway serving something else, a family released after it — is treated the same way: the level is left off. The two mistakes are not symmetric. Sending effort to a model that lacks it is a 400 that ends the run before any work happens; omitting it costs the level and says so in the pill. Gateway decoration is seen past first, so anthropic.claude-opus-5, openrouter/anthropic/claude-opus-5 and claude-opus-5@20260401 are all recognised as the model they name — for pricing and for the thinking parameter too.

Every spawn goes through the ordinary approval box, which shows the preset, the model and the task. It is not rememberable per task — the task text differs every time, so "always this exact one" would never match twice — but "always allow this tool" still applies, for anyone who has decided to trust spawning as such. A subagent's own tool calls go through the same policy, and the box says which agent is asking.

Four run at once; further spawns queue. Two agents editing one file is not a special case: edit_file already refuses a file whose mtime moved since that agent read it, so the second one is told to read it again rather than clobbering the first.

A report reaches the parent exactly once, whichever way it gets there. Both paths — agent_wait returning it as a tool result, and the front end putting it into the conversation as a message — claim it from the supervisor first, and only one claim wins. Without that, an agent that finished before its agent_wait call was even emitted was handed over twice: once as the result, once as a message a turn later, which is the context cost the whole feature exists to avoid. A wait that loses the claim answers with a line saying where to find the report instead of repeating it.

The report comes back on its own. When a subagent finishes, its report is appended to the main conversation as a user message and the main agent starts a turn on it — you do not have to type anything. It is held back only while a turn is already running, while an approval is on screen, while a ! or $ command is running, or while you have text in the prompt box: a turn starting under your fingers would blur the input and eat what you were typing. It goes up the moment that clears. A failed or killed agent is reported the same way, so the parent is never left waiting on a result that is not coming.

The agents sidebar

ctrl+b opens a 30-column pane on the right and gives it the keyboard; the transcript reflows into what is left rather than being covered by it.

Open and focused are separate things. The pane answers every key while it has focus — that is what lets k be a kill rather than a letter — so click the prompt to type again, and the pane stays where it is. Click a row to hand the keys back. ctrl+b always moves towards the pane: it opens a closed one, focuses an open one, and only closes a pane that already has the keys. Which one is live is visible in two places: the heading carries the accent and the prompt frame goes dim, or the other way round.

⏺ Two of those are independent, so I will farm  │  Agents                 2 ⏵
  them out.                                     │
                                                │  ❯ ✳ a1  plan         $0.23
⏺ SpawnAgent(preset=explore, task=…)            │   Opus · 2m04s · 6 turns
  ⎿ started a2 (explore) on claude-haiku-4-5, s…│   ● a2  explore
                                                │   Haiku · waiting · 1 turn
                                                │
                                                │ ↑↓ move · enter open
 ✢ Deliberating… (8s · turn 5 · esc to interrup…│ p pause · k kill · esc back
╭──────────────────────────────────────────────╮│
│ › ask for a change, /help, ! for a shell     ││
╰──────────────────────────────────────────────╯│
 joni   ✳ Opus 5   ⏵ 2 agents                   │
 ⏵ approvals on (shift+tab to cycle) · ctrl+b a…│

Each agent is two lines: the status glyph, the id and the preset, with the cost at the right, and under it the model, how long it has been going and how many turns it has taken. The model is the family alone — thirty columns will not hold claude-haiku-4-5 as well as the numbers — and the full id is in the header of the thread, one keystroke away.

KeyDoes
↑/↓move
enteropen that agent's thread in the main pane
escback to your own thread; again to close the pane
ppause or resume
kstop it
click the prompttype again, leaving the pane open

The statuses are queued, running, waiting — blocked on an approval of yours — paused, done, failed and killed. Pause is checked between turns: the model call and the tool calls in flight finish, and it holds before the next request, so the conversation is never left with a tool_use whose tool_result never came. Kill cancels immediately, and what the agent already did to the tree stays done — this ends an agent, it does not undo one.

Mouse reporting is on, so clicking the agents pill in the status line toggles the pane and clicking a row selects it. /mouse turns reporting off and on again, because while it is on the terminal's own text selection is not.

A child's usage is priced at the child's model and rolled into the session total, so a haiku agent spawned from an opus session is not billed at opus rates. Every metrics event a child writes carries its agent id, so .harness/events.jsonl can be split per agent afterwards.

Subagent transcripts last as long as the session does: -resume brings back your conversation, not theirs.

Subagents belong to the TUI: their reports come back as messages the front end puts into the conversation, and the line-based REPL has nowhere to show four agents at once. -p and -no-tui runs are not offered the tools at all, and -no-agents turns them off for a TUI run too.