Skip to content

Commit 29611f3

Browse files
fix(skywalker): spawn one focused task per worker (#1148)
1 parent 66552a9 commit 29611f3

9 files changed

Lines changed: 23 additions & 13 deletions

File tree

‎docs/ARCHITECTURE.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,9 @@ Three distinct concepts (do not conflate them):
230230
| --------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------- |
231231
| **Agent** | A runtime entity with its own loop, tools, and context | Primary session or a spawned child |
232232
| **Task** | A checklist item owned by _one_ agent via `manage_tasks` | Local work plan — not a spawn |
233-
| **Fleet agent** | A short-lived worker for one self-contained job | Spawned with **`spawn_agent`**; mailbox mail arrives as inbound on TUI and nested runs |
233+
| **Fleet agent** | A short-lived worker for one focused, self-contained job | Spawned with **`spawn_agent`**; mailbox mail arrives as inbound on TUI and nested runs |
234234

235-
The **`spawn_agent`** tool starts a fleet agent on a separate inference source (tier/profile resolved from settings) and returns immediately with an `agent_id`. On the TUI primary, Skywalker idles after spawn and occupancy delivers mailbox mail as system inbound when a worker finishes or fails (including while siblings still run). Nested orchestrators collect through mailbox mail the same way. Declared fan-out is unlimited: excess dispatches enqueue rather than fail. `run()` is admitted by `src/subagent/admission.ts` (default burst window of 8 is race-avoidance so a 429 freeze can fire before a herd — not a declared-spawn cap). Occupancy is the whole first `run()`, including a mounted `wait_agents` (exec primary). Nested children of an already-admitted parent bypass **capacity** so a nested orchestrator cannot deadlock while holding a slot; they still wait on a provider 429 pause. Drain is FIFO among currently admissible jobs (a paused provider is skipped, not head-of-line for every provider). Resume and followup inference re-enter the same queue. Queued workers report wait/list status `queued` (live, not failed). Lowering capacity never cancels in-flight work. Retryable provider 429s freeze new admits via the shared retry remapper in `createCorbitsRetryPolicy`; `quota_exhausted` does not freeze. `list_agents` remains mailbox-scoped. The dispatch brief separates durable `context`, actionable `prompt`, and optional `goals` (checklist seeds for the _child's_ own `manage_tasks` list). Implement/review dispatches (and their default directors) fail closed without non-empty `success_criteria`. The child returns a structured report (`Summary` / `Findings` / `Blockers` / `Paths`) plus a tools-used footer. Parent and child never share a `manage_tasks` list.
235+
The **`spawn_agent`** tool starts a fleet agent on a separate inference source (tier/profile resolved from settings) and returns immediately with an `agent_id`. Each spawned worker gets one focused task; fan-out width follows independent lanes (one lane per PR/path/ownership). Keep the dispatch brief contract (`intent`, `success_criteria`, `do_not`, `report_focus`) tight. On the TUI primary, Skywalker idles after spawn and occupancy delivers mailbox mail as system inbound when a worker finishes or fails (including while siblings still run). Nested orchestrators collect through mailbox mail the same way. Declared fan-out is unlimited: excess dispatches enqueue rather than fail. `run()` is admitted by `src/subagent/admission.ts` (default burst window of 8 is race-avoidance so a 429 freeze can fire before a herd — not a declared-spawn cap). Occupancy is the whole first `run()`, including a mounted `wait_agents` (exec primary). Nested children of an already-admitted parent bypass **capacity** so a nested orchestrator cannot deadlock while holding a slot; they still wait on a provider 429 pause. Drain is FIFO among currently admissible jobs (a paused provider is skipped, not head-of-line for every provider). Resume and followup inference re-enter the same queue. Queued workers report wait/list status `queued` (live, not failed). Lowering capacity never cancels in-flight work. Retryable provider 429s freeze new admits via the shared retry remapper in `createCorbitsRetryPolicy`; `quota_exhausted` does not freeze. `list_agents` remains mailbox-scoped. The dispatch brief separates durable `context`, actionable `prompt`, and optional `goals` (checklist seeds for the _child's_ own `manage_tasks` list). Implement/review dispatches (and their default directors) fail closed without non-empty `success_criteria`. The child returns a structured report (`Summary` / `Findings` / `Blockers` / `Paths`) plus a tools-used footer. Parent and child never share a `manage_tasks` list.
236236

237237
Workers ask the spawning parent with **`ask_director`** (not the human). That parks a question while the worker stays `running`. On the TUI primary that arrives as an idle-send wake. On a nested orchestrator the question arrives as mailbox mail with an `awaiting_director` status — that is not terminal. Once a parked ask is surfaced (TUI wake, nested mailbox mail, or a successful `list_agents`), further `list_agents` calls fail closed until **`send_input`** answers or the ask is dropped — `list_agents` is not a poll. The parent answers with **`send_input`**, then continues. Escalate to the human with **`ask_operator`** only when the parent cannot resolve it.
238238

@@ -340,7 +340,7 @@ The primary session identity is **Skywalker** (`buildChatRole` → `createSkywal
340340

341341
- `buildChatRole` — Skywalker primary identity (orchestrate; DIY tiny/bounded product edits; spawn for substantial work).
342342
- `buildHarnessFacts` — the non-derivable rules: shell file-writes are blocked, path tools are the DIY surface on primary (spawn builder/docs directors for substantial work), dependency installs and off-limits paths need approval, images are native multimodal input, core tools plus the advertised catalog (including `skill_search`) are resident (MCP and other unadvertised tools load via `tool_search`; use `search_agents` before dispatching specialists), workflows run only from slash-command steps, and session memory lives at `.corbits/MEMORY.md`.
343-
- `buildGuidelines` — be concise, prefer `spawn_agent` (mailbox mail on TUI; exec-primary `wait_agents`) for substantial product work, DIY tiny/bounded edits on the parent, answer questions and diagnose visual/product feedback before editing, work autonomously for explicit coding tasks, use `lsp` for symbol work, and require implementation agents to run the repository-defined typecheck, relevant tests, and every defined full verification command. Agents report exact commands, outcomes, and exit statuses; a repository with no typecheck command produces an explicit Blocker backed by project-configuration evidence rather than an invented command or silent skip.
343+
- `buildGuidelines` — be concise, prefer `spawn_agent` (mailbox mail on TUI; exec-primary `wait_agents`) for substantial product work with one focused task per worker and fan-out by independent lanes, DIY tiny/bounded edits on the parent, answer questions and diagnose visual/product feedback before editing, work autonomously for explicit coding tasks, use `lsp` for symbol work, and require implementation agents to run the repository-defined typecheck, relevant tests, and every defined full verification command. Agents report exact commands, outcomes, and exit statuses; a repository with no typecheck command produces an explicit Blocker backed by project-configuration evidence rather than an invented command or silent skip.
344344
- `buildPromptDisciplineBlock` — a shared, prohibition-form section appended exactly once to every built prompt (chat and sub-agent, every provider family). Primary vs worker wording differs for product writes: workers are told to use `read_file`/`edit_file`/`write_file`; Skywalker is told to DIY tiny/bounded edits with those path tools and spawn directors for substantial work. Shared rules: never `cat`/`sed`/heredoc/`echo` for file work, no setting or exporting environment variables (recurring needs belong in project settings), `web_fetch`/`web_search` instead of `curl`/`wget`/hand-rolled queries, one operation per `run_shell` call, turn semantics (a tool-less reply is the final answer, no repeat searches, stop and change approach after three failed attempts, batch independent reads in parallel), and TTY output rules (short bold headers, one-line bullets, backticks for paths/commands, no wide tables).
345345

346346
**Provider-conditional residuals.** Per-family additions layer on top of the shared block via the same `ModelFamilyPolicy` mechanism the directors use (`src/subagent/provider-family.ts`, `src/agent/model-family-policy.ts`) — additive lines, never prompt forks. **Grok** leaves get `buildGrokLeafAntiThrashNote` (gated by `shouldApplyGrokAntiThrash` / `applyGrokFinishBias`, withheld from orchestrators): a compact finish-bias reinforcement plus a one-line reminder to route file/web work through the dedicated tools rather than `run_shell`, motivated by observed tool-routing thrash on the same harness. **Kimi** intentionally has no residual yet — `detectModelFamily` already resolves the family so callers can branch on it, but the prompt seam is left unfilled pending eval characterization of Kimi's behavior, mirroring the provisional (permissive-default) policy in `model-family-policy.ts`.

‎docs/IMPLEMENTATION.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ Twenty packages under `src/agent/directors/<id>/` register in `DIRECTOR_REGISTRY
170170
6. There is no static write-path declaration on packages or profiles (CL-6952 removed it — no shipped director ever set one). Instead, `agent-fleet.ts` tracks each running dispatch by cwd; a new mutating dispatch that lands on the same cwd as a live mutating peer (`pending_init`/`running`, and not a declared read-only `modelRole` of `explore`/`plan`/`review`/`test`) records at most one `concurrent-lane-overlap` entry per cwd wave in `intervention-log.ts` (class `conflict`). The wave flag clears when no live mutating writer remains for that cwd. Terminal-but-unsettled lanes (for example cancelled with `finishedAt` set while the run promise has not reached `finally`) are pruned from the map and do not warn. This is advisory only — it never blocks the spawn, since cwd overlap does not prove the two lanes touch the same files.
171171
7. Spawn effort: pin > package `modelRole` default (`defaultEffortForDirector`; intern=low; plan/review/orchestrator=high; implement/explore/docs/test=medium) > orchestrator/worker binary > parent inheritance. Optional skills are listed in the identity header for awareness; workers mount `skill_search` + `use_skill` scoped to the dispatch's `optionalSkills` and load bodies on demand (grok/kimi leaves omit `skill_search` and load brief-named skills straight through `use_skill`). Primary mounts `use_skill` for its own skill list.
172172

173-
Intent defaults: `intent=implement` → director `builder`; `explore` → `explorer`; `plan` → `counsel`; `review` → `critic`; general → error. Spawn: skywalker full fleet; all other directors, including greybeard, mount no fleet tools. Live `<env>` injects cwd, platform, arch, runtime, date, and git status on every chat and worker prompt.
173+
Intent defaults: `intent=implement` → director `builder`; `explore` → `explorer`; `plan` → `counsel`; `review` → `critic`; general → error. Spawn: skywalker full fleet; all other directors, including greybeard, mount no fleet tools. Skywalker assigns one focused task per worker; fan-out width follows independent lanes (one lane per PR/path/ownership). Live `<env>` injects cwd, platform, arch, runtime, date, and git status on every chat and worker prompt.
174174

175175
### Auto Mode
176176

‎docs/PRODUCT.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ The primary session is always **orchestrator** (single-agent mode is gone). Its
166166

167167
There is **no catch-all worker**. `spawn_agent` requires `agent=…` or a non-general `intent` (implement/explore/plan/review→critic); bare dispatch and `intent=general` are refused. Named `spawn_agent(agent=…)` selects a director package without requiring a plugin profile, except `skywalker` which is the primary session identity and is refused as a spawned worker. Nested spawn is runtime-enforced: only skywalker (full fleet allowlist) may spawn; all other workers, including greybeard, have no fleet tools. Primary omits an allowlist so plugin profiles remain reachable from the main session.
168168

169-
Corbits Code fans work out to short-lived **fleet agents** — workers with their own loop, tools, and checklist — while the primary session stays focused.
169+
Corbits Code fans work out to short-lived **fleet agents** — workers with their own loop, tools, and checklist — while the primary session stays focused. Each worker gets one focused task; fan-out width follows independent lanes (one lane per PR/path/ownership).
170170

171171
- **Agents** are runtime entities (primary session or child).
172172
- **Tasks** are checklist items owned by one agent via `manage_tasks`.

‎src/agent/directors/skywalker/package.test.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ describe("skywalkerPackage", () => {
100100
expect(p).toContain("fan-out");
101101
expect(p).toContain("0–1 worker");
102102
expect(p).toContain("named, non-overlapping lanes");
103+
expect(p).toContain("one focused task");
104+
expect(p).toContain("one lane per PR/path/ownership");
105+
expect(p).toContain("Do not pack a multi-step workflow into one worker");
103106
expect(p).not.toContain("2–4 workers");
104107
expect(p).not.toContain("at most 4");
105108
expect(p).not.toContain("Prefer synthesizing early returns");
@@ -221,6 +224,8 @@ describe("skywalkerPackage", () => {
221224
expect(p).toContain("clean-room");
222225
expect(p).toContain("no fork");
223226
expect(p).toContain("required for implement/review");
227+
expect(p).toContain("keep it tight");
228+
expect(p).toContain("One job per spawn");
224229
// CL-6953 / CL-6807: single contract statement (spawn graph); the routing
225230
// and handoff restatements are gone.
226231
expect(p).not.toContain("Runtime requires success_criteria");

‎src/agent/directors/skywalker/package.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,10 @@ When the operator (or brief) gives an http(s) URL to read:
6868
# Effort scaling (IMPLEMENTATION / ORCHESTRATION)
6969
7070
Scale fan-out to the ask:
71+
- Each spawned worker gets **one focused task**. Do not pack a multi-step workflow into one worker.
7172
- Simple (answer, one-path lookup, tiny fix): 0–1 worker, few tools; often answer without fleet
7273
- Tiny single-file / one-route asks: **DIY on the parent** with write_file/edit_file; skip spawn, skip explorer, skip plan, skip critic. Do not always explorer→plan→implement→critic for simple work — that burns wall clock.
73-
- Multi-lane work: spawn only named, non-overlapping lanes (distinct path/package/ownership). Width follows independent lanes. Do not invent a numeric cap.
74+
- Multi-lane work: spawn only named, non-overlapping lanes (one lane per PR/path/ownership). Width follows independent lanes. Do not invent a numeric cap.
7475
7576
# Anti-cascade (stall / dig / diagnose)
7677
@@ -115,7 +116,7 @@ Docs/design (PRODUCT.md, ARCHITECTURE.md, docs/design/*, brand) still spawn shak
115116
116117
## If ORCHESTRATION → coordinate
117118
118-
Track with manage_tasks. Parallelize independent lanes via spawn_agent, then idle. After each spawn wave, update the operator and end the turn.
119+
Track with manage_tasks. One focused task per worker. Parallelize independent lanes (one lane per PR/path/ownership) via spawn_agent, then idle. After each spawn wave, update the operator and end the turn.
119120
120121
## If COMMUNICATION → answer directly
121122
@@ -137,12 +138,13 @@ Do not reclassify COMMUNICATION as ORCHESTRATION just to justify parallel spawn
137138
Skywalker = full closed set. Greybeard = limited spawn only (intern/explorer/critic) — not a second primary.
138139
You may spawn: builder, explorer, counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot, draper, emil, rand, shakespeare, testsmith, tester, gauntlet, prober, migrator, warden.
139140
140-
When spawning, pass a typed brief. success_criteria is required for implement/review and their default directors; recommended otherwise:
141+
When spawning, pass a typed brief and keep it tight. success_criteria is required for implement/review and their default directors; recommended otherwise:
141142
- intent — explore | implement | plan | review
142143
- success_criteria — done-definition the worker must meet
143144
- do_not — hard constraints
144145
- report_focus — what the parent needs back
145146
- agent — specialist id when known (must match a closed director id above)
147+
One job per spawn — do not stuff extra work into the prompt.
146148
147149
# Report shape
148150

‎src/agent/prompts.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,8 @@ Scope and conventions:
221221
- If a required check genuinely cannot run because of a missing runtime or dependency, sandbox restriction, or permissions, record the exact inability under Blockers; never silently skip a required check.
222222
223223
Orchestration:
224-
- Break multi-step or parallel work into focused worker dispatches with distinct lenses; prefer \`spawn_agent\` (fire several in one turn when jobs are independent), then reply with who is running and end the turn — workers keep running while you are idle. Mailbox mail arrives as inbound when a worker finishes; read it and do not poll. \`list_agents\` shows the fleet without blocking; after a parked ask is surfaced, answer with \`send_input\` and do not poll \`list_agents\`.
225-
- Pass the typed spawn contract: \`intent\`, \`success_criteria\` (done-when; required for implement/review and their default directors), \`do_not\` (scope fence), and \`report_focus\`. Free-form \`prompt\` without \`success_criteria\` fail-closes for implement/review and their default directors.
224+
- One focused task per spawned worker. Fan-out width follows independent lanes (one lane per PR/path/ownership). Break multi-step or parallel work into those dispatches with distinct lenses; prefer \`spawn_agent\` (fire several in one turn when jobs are independent), then reply with who is running and end the turn — workers keep running while you are idle. Mailbox mail arrives as inbound when a worker finishes; read it and do not poll. \`list_agents\` shows the fleet without blocking; after a parked ask is surfaced, answer with \`send_input\` and do not poll \`list_agents\`.
225+
- Pass the typed spawn contract and keep it tight: \`intent\`, \`success_criteria\` (done-when; required for implement/review and their default directors), \`do_not\` (scope fence), and \`report_focus\`. Free-form \`prompt\` without \`success_criteria\` fail-closes for implement/review and their default directors.
226226
- After workers return, classify fail / incomplete-report vs parent-initiated interrupt vs operator-cancel vs clean complete. Fail-path (\`status: failed\` or salvage \`incomplete-report\`): diagnose from the report or error and MAY spawn one successor with a changed brief. Parent-initiated interrupt (\`interrupt_agent\` / \`send_input\` with \`interrupt:true\` unblocks wait with \`stop_reason: interrupted\`): the worker is often still running and often has no report — \`resume_agent\`, or idle for its mailbox mail; do not \`spawn_agent\` a successor against a still-live worker. Successor only if that session is no longer resumable. Operator-cancel (\`stop_reason\` cancelled): wait for the operator; do not auto-retry. Identical brief: refuse. Merge Summary/Findings into a coherent answer for the operator; do not paste raw fleet-agent dumps.
227227
- Use manage_tasks for your own coordination checklist; spawning workers is \`spawn_agent\`, not manage_tasks.
228228
- If context is compacted automatically, do not stop tasks early due to token fear; persist progress via manage_tasks and worker reports.`);

0 commit comments

Comments
 (0)