返回 CodeWhale
AGENT_RUNTIME.md
根目录 / docs / AGENT_RUNTIME.md
1 # The CodeWhale Agent Runtime — one durable substrate, familiar launchers
2
3 This document explains how sub-agents, the headless `exec` path, and Agent Fleet
4 relate. It exists because these had drifted into *two* parallel "worker"
5 systems, and the fix is to make the **fleet-backed worker run** the durable
6 primitive. "Sub-agent" remains useful product vocabulary for a nested role, but
7 it must not imply a separate execution substrate with weaker lifecycle
8 semantics. It also answers the open direction question in #2972 ("how much
9 Claude Code convergence is right?").
10
11 ## The core idea
12
13 There is exactly **one** thing that runs detached agent work: a **headless agent
14 runtime** wrapped in a durable worker lifecycle. It is a model loop with the
15 full (policy-gated) tool surface that can, in turn, delegate child work through
16 the same lifecycle. Everything else is just a different way to *launch* that one
17 runtime, or a different way to *observe* it.
18
19 ```
20 ┌───────────────────────────────┐
21 │ headless agent runtime │
22 │ (full tools + can sub-spawn) │
23 └───────────────────────────────┘
24 ▲ ▲ ▲
25 launches │ │ │ launches
26 │ │ │
27 ┌────────────┴───┐ ┌───────┴────────┐ ┌──┴───────────────────┐
28 │ TUI turn │ │ `codewhale │ │ Agent Fleet │
29 │ (interactive, │ │ exec` │ │ (durable: ledger, │
30 │ in-process) │ │ (headless CLI,│ │ scheduler, SSH, │
31 │ │ │ anyone/any- │ │ alerts) — launches │
32 │ │ │ time) │ │ `codewhale exec` │
33 └────────────────┘ └────────────────┘ │ per worker │
34 └───────────────────────┘
35 ```
36
37 - A **sub-agent** is the user-facing name for a *nested assignment* with a role
38 (`explore`, `review`, `implementer`, `verifier`, ...). It should be backed by
39 the same worker run lifecycle as fleet. `agent` is the model-facing launcher,
40 not a second runtime.
41 - **`codewhale exec`** is the headless front door: usable by anyone at any time
42 (CI, scripts, another agent), full tools, emits a `stream-json` event stream,
43 and can spawn sub-agents. It is *the* runtime with a CLI on it.
44 - A **fleet worker** *is* a `codewhale exec` run that the fleet launches and
45 tracks durably — locally as a subprocess, or remotely as
46 `ssh host … codewhale exec …`. The fleet does not re-implement execution; it
47 adds **orchestration** (durable ledger, scheduling/leasing/retry, host
48 transport, alert escalation) *over* the one runtime.
49
50 So "fleet vs sub-agent" is not two categories. It is **the same headless run**:
51 Fleet is the durable control plane, while sub-agent is the role/UX vocabulary
52 for a nested worker.
53
54 ## The cutover rule
55
56 If a detached `agent` child can fail on a one-off provider timeout with no
57 retry while an equivalent fleet worker would retry and preserve ledger evidence,
58 then the cutover is incomplete. Treat that as a CodeWhale runtime gap, not as
59 normal "sub-agent behavior".
60
61 The compatibility `agent` runtime now retries transient provider header,
62 stream, and timeout failures with backoff before marking a worker interrupted;
63 when retries are exhausted it preserves a checkpoint and returns a continuation
64 handle. The remaining convergence work is to keep that lifecycle durable across
65 process restarts, remote execution, and full fleet-ledger scheduling.
66
67 The target rule is:
68
69 - durable or long-running work goes through the fleet worker lifecycle;
70 - `agent` should enqueue
71 or observe a fleet-backed worker run instead of owning an independent
72 lifecycle;
73 - in-process children are allowed only as a small compatibility/latency
74 optimization, and they must expose the same terminal states, retry semantics,
75 receipts, and inspection handles as the fleet path.
76
77 In product language it is fine to say "open a sub-agent". In architecture
78 language that means "start a nested fleet worker with this role".
79
80 ## Why this shape (and why it fixes the lag)
81
82 The motivating problem: spawning many in-process sub-agents made the TUI lag,
83 because each child cloned a heavy runtime and rebuilt the whole tool registry,
84 *and* the TUI rendered a full card/transcript per child.
85
86 Surveying Claude Code, Codex, and Kimi, the thing that keeps an orchestrator
87 light at high fanout is **not** a process boundary — all three run sub-agents
88 in-process. It is **isolation + a compact event stream**:
89
90 - a child's transcript **never** flows back into the parent — the parent gets a
91 result summary and a small lifecycle event stream;
92 - the UI renders **counts** (`2 running / 3 done`), not a child session per
93 worker;
94 - each worker's tool surface is built directly from a **role/capability
95 profile**, not "build everything then filter".
96
97 "Headless" therefore means *the execution is not shaped like the UI* — it does
98 **not** mean fewer abilities. A headless worker keeps the full toolset and can
99 spawn sub-agents.
100
101 When the work also needs to be **durable** (survive the TUI closing, a laptop
102 sleeping) or **remote** (SSH), the fleet runs the worker out-of-process as
103 `codewhale exec`. The heavy construction then lives in another process entirely,
104 so the orchestrator stays smooth regardless of fanout, and the run survives
105 restarts — the day-scale autonomy goal of #3154.
106
107 ## One recursion axis
108
109 A worker runs at `spawn_depth = 0` and may spawn children while
110 `spawn_depth + 1 ≤ max_spawn_depth`, so a budget of `N` affords `N` nested
111 delegation levels. Sub-agents and fleet workers share **one** axis, sourced from
112 `codewhale_config`:
113
114 - `DEFAULT_SPAWN_DEPTH = 3` — the default budget for both standalone sub-agents
115 and fleet workers (so they cannot drift into "two moving targets");
116 - `MAX_SPAWN_DEPTH_CEILING = 8` — the opt-in cap that every configured value
117 (fleet `max_spawn_depth`, `agent`'s `max_depth`) clamps to.
118
119 Note the parser and the advertised schema do not agree on `agent`'s `max_depth`:
120 the parser clamps to 8 (`tools/subagent/mod.rs:10601-10617`) while the JSON
121 schema the model is shown declares `"maximum": 3` (`mod.rs:6845-6848`). A model
122 therefore cannot request a depth the runtime would honour. Tracked here as a
123 code discrepancy, not a doc one.
124
125 The root worker always runs even at budget 0; the budget gates *child*
126 delegation. The default affords at least three nested levels.
127
128 ## Event vocabulary
129
130 The fleet ledger persists the worker's own event stream rather than a separate,
131 simulated taxonomy. `codewhale exec --output-format stream-json` emits
132 `{"type": "content" | "tool_use" | "tool_result" | "sandbox_denied" |
133 "workflow_event" | "session_capture" | "turn_usage" | "metadata" | "done" |
134 "error"}` lines, which map onto the fleet ledger's
135 `FleetWorkerEventPayload` (`RunningTool`, `WorkflowEvent`, `Running`,
136 `Completed`, `Failed`, …). `workflow_event` carries the typed
137 run/phase/task/gate receipt while a Workflow is in flight and is retained as a
138 typed `WorkflowEvent` in the Fleet ledger; the enclosing worker still owns the
139 terminal `done` or `error`. One vocabulary, two surfaces.
140
141 `turn_usage` is the per-model-call usage receipt, emitted once per model
142 request (turn-step) when the provider reported usage for that call:
143
144 ```json
145 {"type": "turn_usage", "schema": "codewhale.exec-stream", "schema_version": 1,
146 "turn": 1, "input_tokens": 1200, "output_tokens": 180,
147 "reasoning_tokens": 90, "prompt_cache_hit_tokens": 900,
148 "prompt_cache_miss_tokens": 300, "prompt_cache_write_tokens": 0,
149 "reasoning_replay_tokens": 40, "duration_ms": 1834}
150 ```
151
152 - `turn` is the 1-based index of the model call within the exec run;
153 `input_tokens`, `output_tokens`, and `duration_ms` are always present.
154 - Optional token fields are **omitted** when the provider does not report
155 them — never emitted as null and never backfilled with zeros. Field names
156 mirror the terminal `metadata` receipt: `prompt_cache_hit_tokens` is the
157 provider's cache-read count (Anthropic `cache_read_input_tokens`),
158 `prompt_cache_write_tokens` the cache-creation count
159 (`cache_creation_input_tokens`). `reasoning_tokens` appears only for
160 provider paths that report it (OpenAI-compatible
161 `completion_tokens_details` / Responses `output_tokens_details`; Anthropic
162 does not report a thinking-token count). `reasoning_replay_tokens` is a
163 client-side estimate for DeepSeek V4 interleaved-thinking replays.
164 - When a provider reports no usage at all for a call, the whole event is
165 skipped for that call. Latency/convergence analysis should sum
166 `turn_usage` events instead of inferring per-step tokens from wall time;
167 the terminal `metadata` receipt still carries the cumulative totals.
168
169 ## Convergence with Claude Code (#2972)
170
171 CodeWhale should converge with Claude Code on **shape**, not on branding:
172
173 - **Adopt**: a headless runtime with a real CLI/SDK front door; sub-agents as
174 isolated runs that return summaries (not transcripts); a compact, event-driven
175 fanout projection; capability/role tool profiles; the skills ecosystem
176 (#2743); structured run receipts.
177 - **Keep distinct**: CodeWhale branding and first-class DeepSeek/GLM/MiniMax/
178 multi-provider support; the local-first **Agent Fleet** (durable, SSH-capable
179 orchestration) as CodeWhale's own layer above the shared runtime; Workflow as
180 the orchestration overlay.
181 - **Do not** fork execution semantics per surface. The TUI, `agent`,
182 `exec`, the Runtime API, and the fleet must all drive the *same* runtime and
183 observe the *same* event stream — divergence there is what produced the "two
184 moving targets" this document exists to prevent.
185
186 The litmus test for any new agent surface: *does it launch and observe the one
187 runtime, or does it invent a second one?* Only the former is allowed.
188
189 ## What remains after v0.9.0
190
191 Refreshed 2026-07-15 from a full audit of the older 0.9-era documents. Those
192 plans are evidence, not a second source of truth. v0.9.0 consolidates the
193 underwater shell, message-first Operate, permission postures, the wired
194 Workflow engine and durable run journal, Lane CLI/runtime, setup with
195 `operate_ready`, constitution rebalance, and ProviderLake/Models.dev. The
196 remaining work belongs to later releases:
197
198 1. **Rebrand completion** — the only hard-dated obligations: remove the
199 `deepseek`/`deepseek-tui` binary shims and shim release assets; finish the
200 Homebrew `codewhale` formula rollout (`docs/REBRAND.md`).
201 2. **Operate as a value stream** — a control-board surface over the underwater
202 shell (WIP, queue age, bottleneck); model-visible Work state (#3983); phase
203 ledger (#4039); Workrooms Phase 2 (#3209/#3210) as the inbox substrate;
204 receipt reconciliation.
205 3. **Flow control** — real WIP limits and visible queues (#4015, #4016),
206 reconciled with the shipped 16-concurrent/1k-run access model (#4292).
207 4. **Fleet/Workflow convergence residuals** — live tmux/verifier-gate dogfood
208 closing #4175/#4177/#4178/#4179; Fleet consuming canonical AgentProfiles;
209 Conductor/topology (#4010, #4012) as stretch.
210 5. **TTC_DESIGN implementation** — approved and now unblocked after v0.9.0.
211 6. **HarnessProfile completion** — the status/UX display lane
212 (`docs/rfcs/HARNESS_PROFILE_CUTLINE.md`).
213 7. **File decomposition, re-scoped** — `ui.rs` (~19.1k lines) and `main.rs`
214 (~17.6k) are the current offenders (`docs/rfcs/FILE_DECOMPOSITION_0_9_0.md`,
215 whose own ~13.6k/~12.1k figures are the 0.9.0-era snapshot). Both have grown
216 since; measure before quoting.
217
218 Explicitly deferred by their own documents: external workflow memory (boundary
219 only), automatic harness evolution, hosted workrooms, `constitution_modules`
220 (needs sign-off), permission profiles (#3211, needs design), and plan-ceiling
221 probing (needs a product decision).
222
223 ## Public launch contract for an external harness (#4641)
224
225 An external evaluation harness (for example a future Verifiers v1 built-in
226 harness) embeds CodeWhale by launching the public `codewhale exec` front door
227 against an interception endpoint it owns. CodeWhale owns only its **launch
228 contract**; the harness owns interception, traces, model-call timing, token
229 accounting, retries, rollout limits, and runtime orchestration. Do not add a
230 harness runtime, trace parser, or receipt schema to CodeWhale.
231
232 A reproducible headless launch uses only existing generic surfaces:
233
234 - an explicit temporary config that names the route and the credential
235 **environment variable**, never the secret itself:
236
237 ```toml
238 provider = "openai"
239
240 [providers.openai]
241 base_url = "" # the harness fills in its interception endpoint
242 model = "" # the harness fills in the target model
243 api_key_env = "VF_CODEWHALE_API_KEY"
244 ```
245
246 - `CODEWHALE_HOME` set to a fresh per-run directory;
247 - `CODEWHALE_SECRET_BACKEND=file`;
248 - `CODEWHALE_MCP_CONFIG` pointing to a generated per-run MCP JSON file that
249 contains only the task servers the harness supplies
250 (`{"mcpServers":{"task-tools":{"url":""}}}`; the `mcpServers` alias and
251 URL-based Streamable HTTP / SSE transports already exist);
252 - `CODEWHALE_MEMORY=false` and `CODEWHALE_TELEMETRY=false`. Product telemetry
253 is already opt-in and off by default, and a fresh `CODEWHALE_HOME` carries no
254 first-run notice decision, so a sealed harness run collects nothing either
255 way; setting the variable makes that explicit and survives a home the caller
256 reuses — which is the case that now matters, because a reused home whose owner
257 answered the first-run notice with Enable sends to a live endpoint
258 (`https://telemetry.codewhale.net/v1/telemetry`, the shipped default) rather
259 than to a local file. It is a hard floor — an explicit "off" in the
260 environment beats `--telemetry true` and `telemetry = true` in config. Set
261 `CODEWHALE_TELEMETRY_ENDPOINT=` (empty) instead if a harness wants an enabled
262 home to keep buffering locally without contacting anything. See
263 [`docs/TELEMETRY.md`](TELEMETRY.md);
264 - `CODEWHALE_ALLOW_INSECURE_HTTP=1` **only** when the harness supplies a
265 trusted `http://` interception endpoint (container/tunnel endpoints are not
266 always loopback);
267 - `--append-system-prompt` and `--disallowed-tools` when the caller supplies
268 them.
269
270 The interception secret stays in the child environment (resolved through the
271 route's `api_key_env`); it is never written into argv, the route config, logs,
272 the `stream-json` stream, or any generated file.
273
274 The exact argument order is:
275
276 ```sh
277 codewhale \
278 --config .vf-codewhale/config.toml \
279 --workspace . \
280 --no-project-config \
281 --skip-onboarding \
282 exec \
283 --auto \
284 --sandbox danger-full-access \
285 --output-format stream-json \
286 -- "<task prompt>"
287 ```
288
289 `--no-project-config` must appear **before** the subcommand (like
290 `--skip-onboarding`). The public dispatcher parses it and forwards it ahead of
291 the TUI subcommand; `Exec` then skips the workspace-specific
292 `[workspace]`/`[projects]` user-config overlay so the config surface depends
293 only on the explicit `--config`. `crates/tui/tests/verifiers_harness_contract.rs`
294 is the provider-free acceptance lock for this contract.
295
296 ### Future upstream checklist (out of scope here — do not run)
297
298 Actually adding CodeWhale as a built-in harness lives in the external Verifiers
299 repository, **after** a public, immutable CodeWhale `v0.9.1` GitHub Release and
300 checksum manifest exist. That upstream change is expected to be limited to a new
301 `verifiers/v1/harnesses/codewhale/` package plus its test-matrix and docs
302 registration, with `CodewhaleHarnessConfig` pinning `0.9.1`, `setup()`
303 downloading and verifying the released archive, and `launch()` writing the
304 temporary route/MCP files above and calling `runtime.run_program(...)`.
305
306 Holdouts, explicitly **not** performed by this contract work: tagging,
307 publishing, or creating a CodeWhale release; opening or submitting the upstream
308 Verifiers PR; running its credentialed E2E matrix; or claiming
309 runtime/architecture support before the exact released archive has run in that
310 upstream runtime.
311
311 lines MARKDOWN