| 1 | # Fleet Workers and Sub-Agent Compatibility |
| 2 | |
| 3 | Fleet roles are the user-facing vocabulary for delegated work: a parent |
| 4 | launches a focused `worker`, `scout`, `planner`, `reviewer`, `builder`, |
| 5 | `verifier`, or `consultant` through `agent` and gets back an `agent_id` plus transcript handle |
| 6 | while the worker runs. The internal runtime type is `FleetRole` (formerly |
| 7 | `SubAgentType`); the older role spellings (`general`, `explore`, `plan`, |
| 8 | `review`, `implementer`, `oracle`, …) remain accepted only as a persisted/deserialize |
| 9 | compatibility adapter during v0.9.x. New prompts and config should use Fleet |
| 10 | names. |
| 11 | |
| 12 | Architecturally, sub-agents should not be a second execution substrate. The |
| 13 | durable primitive is the fleet-backed worker run described in |
| 14 | [`AGENT_RUNTIME.md`](AGENT_RUNTIME.md): retries, terminal status, receipts, |
| 15 | artifact refs, inspection, and restart behavior belong there. The |
| 16 | model-facing launcher is the single `agent` tool and detached work should |
| 17 | converge on the same lifecycle as Agent Fleet. |
| 18 | |
| 19 | The current `agent` implementation delegates to the durable sub-agent runtime |
| 20 | while that cutover completes. It can still be useful for short in-session |
| 21 | delegation. Transient provider header/stream/time-out failures are retried with |
| 22 | backoff inside the child runtime before the worker is marked interrupted; if the |
| 23 | retry budget is exhausted, Codewhale preserves a checkpoint and returns a |
| 24 | continuation handle instead of leaving the parent to infer what happened. For |
| 25 | work that must survive process restarts, sleep, or remote execution, prefer |
| 26 | Fleet or a Workflow-backed fleet run. |
| 27 | |
| 28 | Sub-agents inherit the parent's tool registry by default, and that includes |
| 29 | `agent` itself: children are built with `with_full_agent_surface_options` |
| 30 | (`crates/tui/src/tools/subagent/mod.rs:12164`) so they can recurse. `agent` is |
| 31 | filtered out of a child's catalog only when the depth budget is spent — |
| 32 | `can_spawn_child = !runtime.would_exceed_depth()` (`mod.rs:12145`), enforced at |
| 33 | `mod.rs:12324` and `:12469`. With the default depth of 3 |
| 34 | (`DEFAULT_SPAWN_DEPTH`, `crates/config/src/lib.rs:1671`) a child can spawn |
| 35 | grandchildren. The removed `agent_open`/`agent_eval`/`agent_close` lifecycle |
| 36 | tools are gone from every registry, parent and child alike. |
| 37 | |
| 38 | `agent` launches detached background work: cancelling the parent turn stops the |
| 39 | parent wait path, but it does not kill already-opened child runs. |
| 40 | |
| 41 | This doc covers the role taxonomy and current compatibility controls. The active |
| 42 | orchestration surface is `agent`; see the sub-agent guidance in |
| 43 | `crates/tui/src/prompts/text.rs` (`AGENT_MODE`) and the in-line |
| 44 | tool description. |
| 45 | |
| 46 | ## Role taxonomy |
| 47 | |
| 48 | The `type` field on `agent` selects a Fleet posture for the child |
| 49 | (`agent_type` is accepted as a compatibility alias). Each role is a distinct |
| 50 | stance toward the work — not just a different label. |
| 51 | |
| 52 | ## Maintainer posture |
| 53 | |
| 54 | Sub-agents help Codewhale move faster, but the parent agent still owns the |
| 55 | maintainer decision. Use children to gather evidence, review patches, and run |
| 56 | verification while keeping the community posture in |
| 57 | [`AGENT_ETHOS.md`](AGENT_ETHOS.md): issues are open intake, PR gates are |
| 58 | review-load controls, and harvested work needs clear contributor credit. |
| 59 | |
| 60 | When a child reviews community work, the parent should still inspect the PR |
| 61 | diff, linked issues, tests, and CI before merging, harvesting, closing, or |
| 62 | deferring it. A sub-agent's result is a working set, not a substitute for |
| 63 | stewardship. |
| 64 | |
| 65 | | Role | Stance | Writes? | Shell posture | Typical use | |
| 66 | |---------------|----------------------------------------|---------|---------------|----------------------------------------------| |
| 67 | | `worker` | flexible; do whatever the parent says | yes | yes | the default; multi-step tasks | |
| 68 | | `scout` | read-only; map the relevant code fast | no | recon (net + bounded verify) | "find every call site of `Foo`; check the PR with gh" | |
| 69 | | `planner` | analyse and produce a strategy | minimal | minimal | "design the migration; don't execute" | |
| 70 | | `reviewer` | read-and-grade with severity scores | no | recon (net + bounded verify) | "audit this PR for bugs" | |
| 71 | | `builder` | land a specific change with min edit | yes | yes | "rewrite `bar.rs::Foo::bar` to do X" | |
| 72 | | `verifier` | run tests / validation, report outcome | no | test-focused | "run cargo test --workspace, report" | |
| 73 | | `consultant` | short-lived, high-reasoning counsel | no | none | "what are we missing in this design?" | |
| 74 | | `custom` | explicit narrow tool allowlist | depends | depends | locked-down dispatch with hand-picked tools | |
| 75 | |
| 76 | Each role's full system prompt lives in |
| 77 | `crates/tui/src/tools/subagent/mod.rs` (search for |
| 78 | `*_AGENT_INTRO`). The prompt prefix loads automatically when the |
| 79 | child agent boots; the parent's assignment prompt becomes the first |
| 80 | turn's user message. |
| 81 | |
| 82 | ## Context forking |
| 83 | |
| 84 | `agent` starts fresh by default: the child gets its role prompt plus the |
| 85 | task you pass. Use `fork_context: true` when the child should continue from |
| 86 | the parent's current request prefix instead. In fork mode the runtime keeps the |
| 87 | parent prefill/prompt prefix byte-identical where available, appends a |
| 88 | structured state snapshot, then adds the sub-agent role instructions and task |
| 89 | at the tail. That preserves DeepSeek prefix-cache reuse while giving the child |
| 90 | the context needed for continuation, review, summarization, or compaction work. |
| 91 | |
| 92 | Use fresh sessions for independent exploration. Use forked sessions when the |
| 93 | task depends on decisions, files, todos, or plan state already in the parent |
| 94 | transcript. |
| 95 | |
| 96 | Forked state renders concrete Work progress from the To-do ledger — the sole |
| 97 | canonical Work surface, written by `work_update`. The child's |
| 98 | `<codewhale:fork_state>` block carries the same bounded body |
| 99 | (`crates/tui/src/work_grounding.rs`) that the parent's own requests carry, so a |
| 100 | fork continues from the parent's real progress position rather than a |
| 101 | paraphrase. That Work section is resolved when the spawn happens, so a |
| 102 | `work_update` earlier in the same parent turn is included. |
| 103 | |
| 104 | Each agent then grounds on **its own** ledger: every sub-agent request carries |
| 105 | the same transient `<codewhale:work_state>` tail rendered from that agent's |
| 106 | private To-do list (#4810), refreshed after the agent's own `work_update`. It is |
| 107 | request-scoped — never stored in the child transcript or its system prefix — so |
| 108 | a worker can never read or write a parent's or sibling's **private transient |
| 109 | tail**. A deliberately forked child still receives the bounded immutable parent |
| 110 | ledger snapshot described above as part of its fork context; it cannot mutate |
| 111 | that snapshot or keep reading later parent changes. |
| 112 | |
| 113 | That same private ledger is what the child's in-transcript card shows. A |
| 114 | delegate card renders a bounded projection of **its own** agent's To-do — the |
| 115 | settled/total count, the in-progress item always included, up to three rows, and an |
| 116 | explicit `… +N more` when the bound elides the rest — built by |
| 117 | `card_todo_projection` from the same snapshot, priority order, and sanitizer the |
| 118 | model-facing body uses. A card only ever consumes an envelope whose `agent_id` |
| 119 | matches it, so a parent's list never appears under a child and no sibling's list |
| 120 | appears under another. An agent that has stated no work shows no To-do rows at |
| 121 | all rather than a placeholder task, and a terminal card keeps the last snapshot |
| 122 | its agent actually published. Fanout cards stay a dot grid and do not show child |
| 123 | To-do: with many workers behind one card there is no truthful place to hang a |
| 124 | single ledger. A child To-do appears only when the runtime already represents |
| 125 | that child as its own delegate card. |
| 126 | |
| 127 | The durable task/Fleet ledger still owns lifecycle state. `update_plan` is no |
| 128 | longer reachable by a model: `model_visible()` returns `false` |
| 129 | (`crates/tui/src/tools/plan.rs:408-413`), so it is filtered out of the API tool |
| 130 | list and never appears to a child. It survives only to replay older transcripts. |
| 131 | Strategy that used to go there now goes in the response body, and lifecycle |
| 132 | state goes in `work_update`. |
| 133 | |
| 134 | ## Worktree isolation |
| 135 | |
| 136 | For parallel edit lanes, launch the child with `worktree: true`. Codewhale |
| 137 | creates a fresh git worktree and branch for that child, runs the child from the |
| 138 | isolated checkout, and reports the resulting workspace/branch in the returned |
| 139 | session projection and worker record. By default the branch is |
| 140 | `codex/agent-<name>-<id>` and the checkout lives beside the parent repo under |
| 141 | `.codewhale-worktrees/`, so the parent checkout stays clean. |
| 142 | |
| 143 | Isolation is not write authority. A prompt-only worker starts read-only. |
| 144 | A writer also declares `write_authority: "workspace_write"` or |
| 145 | `"worktree_write"` and at least one normalized repo-relative `write_roots`, |
| 146 | `exact_files`, or `coordination_contracts` value. Active overlapping shared |
| 147 | claims fail before mutation; a real isolated worktree may proceed in parallel. |
| 148 | |
| 149 | Optional fields: |
| 150 | |
| 151 | - `worktree_branch`: exact branch to create. |
| 152 | - `worktree_base`: git ref to branch from; defaults to `HEAD`. |
| 153 | - `worktree_path`: exact checkout path. Relative paths stay under the default |
| 154 | sibling `.codewhale-worktrees/` root. |
| 155 | |
| 156 | Do not combine `cwd` with `worktree`; `cwd` remains the manual escape hatch for |
| 157 | an already-created directory inside the parent workspace. |
| 158 | |
| 159 | ## Delegation briefs |
| 160 | |
| 161 | The parent should pass a compact brief instead of a loose paragraph. Use the |
| 162 | structured `dependencies` and `acceptance` arrays for bounded prerequisite facts |
| 163 | and observable checks; keep the focused objective in `prompt`. Do not copy raw |
| 164 | parent reasoning or an unbounded transcript. |
| 165 | |
| 166 | ``` |
| 167 | QUESTION: |
| 168 | SCOPE: |
| 169 | ALREADY_KNOWN: |
| 170 | EFFORT: quick | medium | thorough |
| 171 | STOP_CONDITION: |
| 172 | OUTPUT: VERDICT, EVIDENCE, GAPS, NEXT |
| 173 | ``` |
| 174 | |
| 175 | `scout` briefs default to quick, read-only investigation (no writes, but |
| 176 | network reach and the bounded verification surface are available for real |
| 177 | recon). About 3-5 tool calls |
| 178 | is enough for quick exploration: orient, search, read the decisive lines, and |
| 179 | return. Do not repeat `ALREADY_KNOWN` work unless evidence contradicts it. Review |
| 180 | and verifier briefs can spend more calls, but should stop after decisive |
| 181 | evidence. Builder and repair-style briefs should use checkpoints before |
| 182 | scope expansion or after repeated failures rather than a tiny call cap. |
| 183 | |
| 184 | Good delegation prompt examples: |
| 185 | |
| 186 | ```text |
| 187 | QUESTION: Does PR #3124 introduce release-risk behavior around provider routing? |
| 188 | SCOPE: PR #3124 diff, linked issue, provider routing tests, docs/PROVIDERS.md. |
| 189 | ALREADY_KNOWN: Branch is hunter/0.8.62-glm-subagents; workspace version stays 0.8.61. |
| 190 | EFFORT: medium |
| 191 | STOP_CONDITION: Return once you have either one BLOCKER/MAJOR issue or enough evidence for no MAJOR+ issues. |
| 192 | OUTPUT: VERDICT, EVIDENCE with file:line refs or PR refs, GAPS, NEXT. |
| 193 | ``` |
| 194 | |
| 195 | ```text |
| 196 | QUESTION: Where is the child-agent prompt assembled? |
| 197 | SCOPE: crates/tui/src/prompts*, crates/tui/src/tools/subagent/*. |
| 198 | ALREADY_KNOWN: The model-facing launcher is only `agent`; do not look for removed lifecycle tools. |
| 199 | EFFORT: quick |
| 200 | STOP_CONDITION: Stop after identifying the prompt source files and the function that wraps assignment text. |
| 201 | OUTPUT: VERDICT, EVIDENCE, GAPS, NEXT. |
| 202 | ``` |
| 203 | |
| 204 | ```text |
| 205 | QUESTION: Is the focused prompt/subagent test filter valid, and what fails if not? |
| 206 | SCOPE: cargo test -p codewhale-tui --bin codewhale-tui --locked prompt; subagent filter if needed. |
| 207 | ALREADY_KNOWN: Do not fix failures; capture exact command, exit code, and first relevant assertion. |
| 208 | EFFORT: medium |
| 209 | STOP_CONDITION: Stop after one clean PASS or one reproducible failing assertion with command evidence. |
| 210 | OUTPUT: VERDICT, EVIDENCE, GAPS, NEXT. |
| 211 | ``` |
| 212 | |
| 213 | ### When to pick which role |
| 214 | |
| 215 | - **`worker`** — when the task is "do this whole thing", not "go |
| 216 | look", "design", or "verify". This is the right default; reach for |
| 217 | a more specific role only when the posture matters. |
| 218 | - **`scout`** — when the parent needs evidence before deciding what |
| 219 | to do next. Scouts are cheap and fast; open 2–3 in parallel |
| 220 | for independent regions. |
| 221 | They should orient first: confirm the project root, read relevant |
| 222 | `AGENTS.md`/`README.md` guidance in unfamiliar trees, search only the |
| 223 | likely scope, and return `path:line-range` evidence instead of a narrative |
| 224 | tour. The role name to use is `scout`. |
| 225 | - **`planner`** — when the parent has an objective but no executable |
| 226 | decomposition. Planners write artifacts (`work_update` items for the ledger, |
| 227 | strategy in the response body) but don't carry them out. |
| 228 | - **`reviewer`** — when there's already a change and the parent wants |
| 229 | it graded. Reviewers don't patch — they describe the fix in the |
| 230 | finding so the parent can dispatch a builder if the verdict |
| 231 | is "fix it". |
| 232 | - **`builder`** — when the change is already specified and just |
| 233 | needs to land. Builders stay tightly scoped: minimum edit, no |
| 234 | drive-by refactoring, run a quick verification before handing back. |
| 235 | - **`verifier`** — when the parent needs an authoritative pass/fail |
| 236 | on the test suite or other validation. Verifiers don't fix |
| 237 | failures; they capture the failing assertion + stack and put fix |
| 238 | candidates under RISKS. |
| 239 | - **`consultant`** — when the operator wants a high-leverage second opinion |
| 240 | before cheaper execution continues. Consultants read enough to ground a |
| 241 | recommendation, but cannot write or run shell commands. `oracle` and |
| 242 | `advisor` remain accepted only when loading older requests or persisted |
| 243 | records; new prompts, receipts, and UI use `consultant`. |
| 244 | - **`custom`** — only when the parent needs to constrain the tool |
| 245 | set explicitly. Pass the allowlist via the `allowed_tools` field |
| 246 | on legacy/internal sub-agent records; the model-facing `agent` tool keeps the |
| 247 | public schema intentionally small. |
| 248 | |
| 249 | ### Aliases |
| 250 | |
| 251 | The model can spell each role multiple ways: |
| 252 | |
| 253 | | Canonical | Aliases | |
| 254 | |---------------|------------------------------------------------------------------| |
| 255 | | `worker` | `general`, `default`, `general-purpose` | |
| 256 | | `scout` | `explore`, `explorer`, `exploration` | |
| 257 | | `planner` | `plan`, `planning`, `awaiter` | |
| 258 | | `reviewer` | `review`, `code-review`, `code_review` | |
| 259 | | `builder` | `implementer`, `implement`, `implementation` | |
| 260 | | `verifier` | `verify`, `verification`, `validator`, `tester` | |
| 261 | | `consultant` | `oracle`, `advisor` (compatibility input only) | |
| 262 | | `custom` | (none; explicit `allowed_tools` array required) | |
| 263 | |
| 264 | All matching is case-insensitive. Unknown values produce a typed |
| 265 | error listing the accepted set, so the model can self-correct on |
| 266 | the next turn. |
| 267 | |
| 268 | ## Concurrency cap |
| 269 | |
| 270 | Up to **64** sub-agents run concurrently by default (`DEFAULT_MAX_SUBAGENTS`), |
| 271 | configurable via `[subagents].max_concurrent` in `~/.codewhale/config.toml` up to |
| 272 | the hard ceiling of **128** (`MAX_SUBAGENTS`). The session admits a bounded |
| 273 | queue of up to **1024** running plus queued sub-agents by default |
| 274 | (`MAX_SUBAGENT_ADMISSION`, `crates/tui/src/config/subagent_limits.rs:21`), so a turn can |
| 275 | request broad fan-out and let the manager drain it without creating an |
| 276 | unbounded population. |
| 277 | |
| 278 | By default every admitted child may start immediately — there is no artificial |
| 279 | throttle. If you want gentler fan-out, lower `[subagents].launch_concurrency` |
| 280 | (how many direct children start at once); children beyond that limit **queue** |
| 281 | for a launch slot rather than bursting. `launch_concurrency` defaults to the |
| 282 | resolved `max_subagents` cap. (The pre-v0.8.61 `interactive_max_launch` key is |
| 283 | still accepted as a deprecated alias; the new key wins when both are set.) |
| 284 | |
| 285 | High-fanout Workflows can tune that bounded population with `[subagents] |
| 286 | max_admitted` (aliases: `max_total`, `admission_limit`). That total ceiling |
| 287 | counts both **running** and **queued** agents, while `launch_concurrency` keeps |
| 288 | instantaneous execution bounded. Completed / failed / cancelled records persist |
| 289 | for inspection but don't occupy an admission slot. Agents that lost their |
| 290 | `task_handle` (e.g. across a process restart) also don't count against the cap. |
| 291 | |
| 292 | Provider profiles let one config stay aggressive for direct API routes while |
| 293 | keeping subscription or aggregator routes gentle. Every key under |
| 294 | `[subagents.providers.<provider>]` inherits from `[subagents]` when omitted. |
| 295 | Provider keys accept canonical names such as `deepseek`, `zai`, `openrouter`, |
| 296 | and aliases such as `glm` for Z.ai: |
| 297 | |
| 298 | ```toml |
| 299 | [subagents] |
| 300 | # Global fallback for providers without a profile. |
| 301 | max_concurrent = 20 |
| 302 | launch_concurrency = 20 |
| 303 | max_admitted = 200 |
| 304 | max_depth = 6 |
| 305 | token_budget = 100000 |
| 306 | |
| 307 | [subagents.providers.deepseek] |
| 308 | # Direct API key with room to fan out. |
| 309 | max_concurrent = 20 |
| 310 | launch_concurrency = 20 |
| 311 | max_admitted = 200 |
| 312 | |
| 313 | [subagents.providers.glm] |
| 314 | # Z.ai / GLM subscription-style route: keep pressure tight. |
| 315 | max_concurrent = 4 |
| 316 | launch_concurrency = 3 |
| 317 | max_admitted = 12 |
| 318 | max_depth = 2 |
| 319 | api_timeout_secs = 180 |
| 320 | heartbeat_timeout_secs = 240 |
| 321 | |
| 322 | [subagents.providers.openrouter] |
| 323 | max_concurrent = 5 |
| 324 | launch_concurrency = 3 |
| 325 | max_admitted = 20 |
| 326 | |
| 327 | [subagents.providers.anthropic] |
| 328 | max_concurrent = 3 |
| 329 | launch_concurrency = 2 |
| 330 | max_admitted = 12 |
| 331 | ``` |
| 332 | |
| 333 | Use `/config subagents status` to see both the global values and the active |
| 334 | provider's resolved fanout, depth, and timeout profile. |
| 335 | |
| 336 | ## Token budget governor |
| 337 | |
| 338 | Set `[subagents].token_budget` to give each root `agent` run an aggregate |
| 339 | token ceiling shared by that child and all of its descendants. When no budget |
| 340 | is configured, behavior is unchanged. |
| 341 | |
| 342 | `token_budget` is **not** a field on the model-facing `agent` schema, and its |
| 343 | absence is deliberate — `crates/tui/src/tools/subagent/tests.rs:4260-4263` |
| 344 | asserts it, on the grounds that "ad-hoc children should inherit the generous |
| 345 | runtime budget; exposing an optional cap invites accidental micromanagement." |
| 346 | The parser still accepts the key (plus the `tokenBudget`/`max_tokens` aliases, |
| 347 | `mod.rs:10620`) so Workflow-shaped callers that construct the call themselves |
| 348 | can scope a budget, but the model is never told the field exists. Configure it |
| 349 | through `[subagents].token_budget` instead. |
| 350 | |
| 351 | Provider-reported input and output tokens are folded into the worker record as |
| 352 | each child model call completes. The persisted `usage` object shows the |
| 353 | worker's own totals plus aggregate `budget_spent_tokens` and |
| 354 | `budget_remaining_tokens` for the shared scope. Once the shared scope is |
| 355 | exhausted, further descendant spawns are rejected with an actionable message |
| 356 | instead of opening more agents into a spent pool. |
| 357 | |
| 358 | ## Per-role models (#3018) |
| 359 | |
| 360 | Children can run on a different model than the parent. Two config surfaces |
| 361 | feed the same override map (`[subagents.models]` keys win on conflict, keys |
| 362 | are case-insensitive): |
| 363 | |
| 364 | ```toml |
| 365 | [subagents] |
| 366 | default_model = "deepseek-v4-flash" # fallback for every role |
| 367 | worker_model = "deepseek-v4-pro" # worker |
| 368 | scout_model = "deepseek-v4-flash" # scout |
| 369 | planner_model = "deepseek-v4-flash" # planner |
| 370 | reviewer_model = "deepseek-v4-pro" # reviewer |
| 371 | custom_model = "deepseek-v4-pro" # custom |
| 372 | |
| 373 | [subagents.models] |
| 374 | # Free-form role → model map; any role alias accepted by agent works. |
| 375 | builder = "deepseek-v4-pro" |
| 376 | ``` |
| 377 | |
| 378 | The v0.9.x convenience keys `explorer_model`, `awaiter_model`, and |
| 379 | `review_model` remain accepted as deprecated aliases so existing config files |
| 380 | do not break. |
| 381 | |
| 382 | Model ids may be **any model the active provider accepts** — validation is |
| 383 | provider-aware and happens at spawn time, not load time. On the official |
| 384 | DeepSeek API only DeepSeek ids are accepted; every other provider passes the |
| 385 | id through to the provider API, which is the authority. A non-DeepSeek |
| 386 | example: |
| 387 | |
| 388 | ```toml |
| 389 | provider = "moonshot" |
| 390 | model = "kimi-k2.7-code" |
| 391 | |
| 392 | [subagents] |
| 393 | worker_model = "kimi-k2.6" |
| 394 | ``` |
| 395 | |
| 396 | Model ids are validated the same way when applied to a child route; an invalid |
| 397 | id on the official DeepSeek API fails the spawn with the accepted-id list |
| 398 | instead of an opaque provider 400. |
| 399 | |
| 400 | With `/model auto`, sub-agent routing is provider-aware too: providers with a |
| 401 | known big/cheap pair (DeepSeek, and the hosted DeepSeek routes on NVIDIA NIM, |
| 402 | OpenRouter, Novita, SiliconFlow, SGLang, vLLM) route between that pair; |
| 403 | providers without a known cheap tier (e.g. Ollama, Moonshot) skip the |
| 404 | network router and keep children on the session model. |
| 405 | |
| 406 | ## Per-profile provider routes (#3965) |
| 407 | |
| 408 | `[subagents.models]` changes the child model within the active provider. To pin |
| 409 | a child to a different provider, use a Fleet/AgentProfile and pass it to the |
| 410 | model-facing `agent` tool with `profile`. The profile's explicit `provider` + |
| 411 | `model` fields win over the parent session route; omitting `provider` preserves |
| 412 | the existing inherit behavior. |
| 413 | |
| 414 | Example: keep the parent session on DeepSeek, but run a formatter child on a |
| 415 | local LM Studio OpenAI-compatible endpoint: |
| 416 | |
| 417 | ```toml |
| 418 | # ~/.codewhale/config.toml or workspace config |
| 419 | provider = "deepseek" |
| 420 | |
| 421 | [providers.deepseek] |
| 422 | api_key = "YOUR_DEEPSEEK_KEY" |
| 423 | |
| 424 | [providers.lm-studio] |
| 425 | kind = "openai-compatible" |
| 426 | base_url = "http://127.0.0.1:1234/v1" |
| 427 | api_key = "lm-studio" |
| 428 | model = "qwen-2.5-7b" |
| 429 | ``` |
| 430 | |
| 431 | ```toml |
| 432 | # .codewhale/agents/local-formatter.toml |
| 433 | id = "local-formatter" |
| 434 | role_hint = "formatter" |
| 435 | provider = "lm-studio" |
| 436 | model = "qwen-2.5-7b" |
| 437 | reasoning_effort = "off" |
| 438 | |
| 439 | [instructions] |
| 440 | text = "Use small, local edits. Keep formatting changes mechanical." |
| 441 | ``` |
| 442 | |
| 443 | Then call `agent(profile: "local-formatter", prompt: "...")`. In-process |
| 444 | children build a client for `lm-studio`; Fleet workers forward |
| 445 | `--provider lm-studio` to `codewhale exec`, which resolves the same |
| 446 | `[providers.lm-studio]` table. Unknown or unconfigured provider ids fail the |
| 447 | spawn rather than silently falling back to the parent provider. |
| 448 | |
| 449 | ## Per-step API timeout (#1806, #1808) |
| 450 | |
| 451 | Each sub-agent step wraps its DeepSeek `create_message` call in a |
| 452 | per-step timeout so a single stuck request can't pin the parent's |
| 453 | completion wakeup channel indefinitely. The default is `600` seconds. |
| 454 | A timed-out attempt is retried with exponential backoff (up to 5 |
| 455 | retries) before the step interrupts with a preserved checkpoint. |
| 456 | Long-thinking children that legitimately exceed that, for example |
| 457 | heavy plan or review work behind `agent`, can extend the timeout in |
| 458 | `~/.codewhale/config.toml`: |
| 459 | |
| 460 | ```toml |
| 461 | [subagents] |
| 462 | api_timeout_secs = 900 # 15 minutes; clamped to 1..=3600 |
| 463 | ``` |
| 464 | |
| 465 | Values are clamped to `1..=3600`. `0` and `unset` keep the `600` |
| 466 | second default. |
| 467 | |
| 468 | ## Stale-agent heartbeat (#2614) |
| 469 | |
| 470 | Running agents also track manager-visible progress. If a child stops emitting |
| 471 | progress for the heartbeat window, the manager auto-cancels it, releases its |
| 472 | sub-agent slot, and keeps the cancelled record inspectable through the returned |
| 473 | transcript handle and persisted worker record. The default is 5 minutes |
| 474 | (resolved to at least 30 seconds above `api_timeout_secs`, so 630 seconds |
| 475 | with the 600-second default API timeout): |
| 476 | |
| 477 | ```toml |
| 478 | [subagents] |
| 479 | heartbeat_timeout_secs = 300 # clamped to 30..=3600 |
| 480 | ``` |
| 481 | |
| 482 | The effective heartbeat is kept at least 30 seconds above |
| 483 | `api_timeout_secs`, so a configured long model request is not cancelled before |
| 484 | its own request timeout can fire. |
| 485 | |
| 486 | ## Lifecycle |
| 487 | |
| 488 | Each opened session produces a record that progresses through: |
| 489 | |
| 490 | ``` |
| 491 | Pending → Running → (Completed | Failed(reason) | Cancelled | Interrupted(reason)) |
| 492 | ``` |
| 493 | |
| 494 | `Interrupted` fires when the manager detects a `Running` agent whose task |
| 495 | handle is gone — typically after a process restart that loaded the workspace's |
| 496 | persisted state from `.codewhale/state/subagents.v1.json`. The parent can open a |
| 497 | replacement session with the same assignment or treat it as a terminal state. |
| 498 | |
| 499 | ### Session boundaries (#405) |
| 500 | |
| 501 | Each `SubAgentManager` instance assigns itself a fresh `session_boot_id` on |
| 502 | construction. Every new session stamps the agent with that id; the workspace |
| 503 | state file records it for restart recovery. |
| 504 | |
| 505 | Work-bar/status projections focus on current-session agents by default. |
| 506 | Prior-session agents that are not still running are treated as archived records |
| 507 | so the model does not mistake stale work for live work. This is a |
| 508 | *prior-session* rule only: agents that finished in the CURRENT session keep |
| 509 | their work-bar rows for the rest of the session (quiet completion), and their |
| 510 | details still open from those rows. |
| 511 | |
| 512 | Records that loaded from a pre-#405 persisted state file (no |
| 513 | `session_boot_id` field) classify as prior-session because the |
| 514 | manager can't match them to the current boot. |
| 515 | |
| 516 | ## Run receipts, follow-up, and takeover |
| 517 | |
| 518 | Each compatibility sub-agent has a persisted worker record in |
| 519 | `.codewhale/state/subagents.v1.json`. The record is the current run-ledger |
| 520 | slice for sub-agent lanes until those lanes are backed directly by the fleet |
| 521 | ledger: it stores `run_id`, objective, role/model, |
| 522 | workspace/branch, lifecycle events, artifact refs, follow-up target, takeover |
| 523 | target, usage provenance, and verification provenance. |
| 524 | |
| 525 | `agent` returns a session projection with these fields at the top level and |
| 526 | inside `worker_record`. The normal parent contract is not polling: keep working |
| 527 | and consume the completion event when the child finishes. If audit detail is |
| 528 | needed, inspect the returned `transcript_handle` with `handle_read`. |
| 529 | |
| 530 | Legacy follow-up delivery is retained only for old transcripts and internal |
| 531 | recovery. If a message was delivered, the worker record stores a bounded preview |
| 532 | and timestamp. New model-facing flows should open a replacement `agent` when a |
| 533 | child's assignment no longer fits. |
| 534 | |
| 535 | Artifacts are symbolic refs. Use `handle_read` on the returned |
| 536 | `transcript_handle` for transcript details, and treat `result_summary` as a |
| 537 | child self-report unless `verification.status` points to a separate gate or |
| 538 | receipt. `usage.status` is `unknown` until provider usage is reported; then it |
| 539 | switches to `reported`, or `budget_exhausted` when a configured shared token |
| 540 | budget has no remaining tokens. |
| 541 | |
| 542 | ## Output contract |
| 543 | |
| 544 | Every sub-agent ends with five Markdown headings, in this order: |
| 545 | |
| 546 | ``` |
| 547 | ### SUMMARY one paragraph; what you did and what happened |
| 548 | ### EVIDENCE path:line-range citations and key findings; one bullet each |
| 549 | ### CHANGES files modified, with one-line descriptions; "None." if read-only |
| 550 | ### RISKS what could go wrong / what the parent should double-check |
| 551 | ### BLOCKERS what stopped you; "None." if you finished cleanly |
| 552 | ``` |
| 553 | |
| 554 | They are `### HEADING` lines, not `HEADING:` labels, and `EVIDENCE` comes |
| 555 | before `CHANGES`. The exact format lives in `crates/tui/src/prompts/text.rs` |
| 556 | (`SUBAGENT_OUTPUT_FORMAT`, :414-422) and `crates/tui/src/prompts.rs:3222` |
| 557 | asserts every heading against it. |
| 558 | The parent reads `EVIDENCE` as a working set for the next turn, so |
| 559 | scouts and reviewers should be precise here. |
| 560 | |
| 561 | ## Memory and the `remember` tool (#489) |
| 562 | |
| 563 | Sub-agents share the parent's native memory store when memory is enabled |
| 564 | (`[memory] enabled = true` or `DEEPSEEK_MEMORY=on`). They can |
| 565 | append durable notes via the `remember` tool — handy for a |
| 566 | scout that discovers a project convention worth carrying across |
| 567 | sessions, or a verifier that learns "this test is flaky". |
| 568 | |
| 569 | `remember` takes a `scope` of `global` or `workspace` |
| 570 | (`crates/tui/src/tools/remember.rs:79-108`) and writes through |
| 571 | `NativeMemoryStore` to `~/.codewhale/memory/global/MEMORY.md` or |
| 572 | `~/.codewhale/memory/workspace/<id>/MEMORY.md`. Writes do not go through the |
| 573 | standard write-approval flow. The legacy single-file `memory.md` path was |
| 574 | removed in v0.9.4 (remember.rs:165); see `docs/MEMORY.md` for the full layout. |
| 575 | |
| 576 | ## Implementation notes |
| 577 | |
| 578 | - Source: `crates/tui/src/tools/subagent/mod.rs`. |
| 579 | - Persisted state: `<workspace>/.codewhale/state/subagents.v1.json`. Schema |
| 580 | version `1` (forward-compatible — new optional fields use |
| 581 | `#[serde(default)]`). |
| 582 | - Worker records are pruned by time: completed / failed / cancelled / |
| 583 | interrupted records are evicted after the same retention window used for |
| 584 | finished agents (default 1h, `COMPLETED_AGENT_RETENTION`). Running / |
| 585 | starting / waiting records are preserved. The hard cap of 256 records |
| 586 | remains as a safety bound (#4217). |
| 587 | - `SubAgentRuntime::background_runtime()` starts from `child_runtime()` but |
| 588 | replaces the turn-scoped child token with a fresh cancellation token, so |
| 589 | parent turn cancellation does not stop detached background sessions. |
| 590 | - The `is_running` check ignores agents whose `task_handle` is |
| 591 | `None`; this avoids counting persisted-but-detached records |
| 592 | toward the concurrency cap (#509). |
| 593 | - `SharedSubAgentManager` is `Arc<RwLock<...>>` — read paths use |
| 594 | read locks so `/agents` and the sidebar projection don't block |
| 595 | the main loop during multi-agent fan-out (#510). |
| 596 |