Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions src/agent/directors/skywalker/package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,19 +115,25 @@ describe("skywalkerPackage", () => {
expect(p).not.toContain("2–4 workers");
expect(p).not.toContain("at most 4");
expect(p).not.toContain("Prefer synthesizing early returns");
expect(p).toContain("queues excess");
// CL-6953: admission queue owns queueing (src/subagent/admission.ts) —
// the prompt keeps the fan-out judgment, not the mechanism restatement.
expect(p).not.toContain("queues excess");
expect(p).toContain("Do not invent a numeric cap");
});

test("systemPrompt prefers spawn_agent then idle (idle-orchestrator)", () => {
const p = skywalkerPackage.systemPrompt;
expect(p).toContain("spawn_agent");
expect(p).toContain("wait_agents");
// CL-6953: collection path (wait_agents vs mailbox) lives in the runtime
// mount + spawn_agent tool description — not the static prompt.
expect(p).not.toContain("wait_agents");
expect(p).toContain("Idle-orchestrator");
expect(p).not.toContain("task()");
expect(p).toContain("Spawn then idle; do not poll");
expect(p).not.toContain("do not poll wait_agents");
expect(p).toContain("wait_agents is mounted on exec-primary runs only");
// CL-6953: wait_agents mounting lives in the runtime (exec/runner.ts) and
// tool descriptions — the prompt keeps spawn-then-idle, not the mount fact.
expect(p).not.toContain("wait_agents is mounted on exec-primary runs only");
expect(p).toContain("mailbox mail arrives as inbound");
expect(p).toContain(
"When the fleet goes dry the runtime re-enters with collected reports",
Expand Down Expand Up @@ -171,6 +177,7 @@ describe("skywalkerPackage", () => {
const p = skywalkerPackage.systemPrompt;
expect(p).toContain("incomplete-report");
expect(p).toContain("MAY `spawn_agent` **one** successor");
expect(p).toContain("Cap is one successor for that stall");
expect(p).toContain("changed** brief");
expect(p).toContain("wait for the operator");
expect(p).toContain("Do not auto-retry");
Expand Down Expand Up @@ -218,6 +225,9 @@ describe("skywalkerPackage", () => {
expect(p).toContain("clean-room");
expect(p).toContain("no fork");
expect(p).toContain("required for implement/review");
// CL-6953 / CL-6807: single contract statement (spawn graph); the routing
// and handoff restatements are gone.
expect(p).not.toContain("Runtime requires success_criteria");
expect(p).not.toContain("Brief completeness");
expect(p).not.toContain("Prefer typed spawn");
expect(p.indexOf("Critic stays clean-room")).toBeGreaterThan(
Expand Down Expand Up @@ -303,7 +313,9 @@ describe("skywalkerPackage", () => {
expect(p).toContain("re-dispatch **builder**");
expect(p).toMatch(/narrowed or changed follow-up brief/i);
expect(p).toContain("ship → verify → fix → re-verify");
expect(p).toContain("Cap re-fix rounds");
// CL-6953: no runtime retry budget enforces a re-fix cap — keep the loop
// judgment, not the number.
expect(p).not.toContain("Cap re-fix rounds");
});

test("systemPrompt Linear three-state: In Review at PR-open, never Done at PR-open", () => {
Expand Down
11 changes: 5 additions & 6 deletions src/agent/directors/skywalker/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ You do not do the specialists' jobs by default. For tiny bounded product edits,

Do not run long-blocking jobs on the parent (evals, full test suites, long installs, long-running implementation). Dispatch intern (mechanical shell), tester (suite / repro), or builder (substantial code). Path tools (write_file/edit_file/delete_file) are the DIY surface; shell file-writes stay denied.

Idle-orchestrator: fire one or more spawn_agent calls in a turn — each returns immediately with an agent_id and does not hold the parent. Then **reply to the operator** with who is running and **end the turn**. Workers keep running while you are idle; mailbox mail arrives as inbound when a worker finishes or fails — read it and decide the next action. Spawn then idle; do not poll. wait_agents is mounted on exec-primary runs only. list_agents shows the fleet without blocking; do not poll list_agents. interrupt_agent unblocks an in-flight wait immediately. Enter mid-run delivers at the next parent tool.boundary — a long parent foreground run_shell holds those steers (start long commands with run_shell background:true instead). A bare spawn_agent does not. When the fleet goes dry the runtime re-enters with collected reports.
Idle-orchestrator: fire one or more spawn_agent calls in a turn — each returns immediately with an agent_id and does not hold the parent. Then **reply to the operator** with who is running and **end the turn**. Workers keep running while you are idle; mailbox mail arrives as inbound when a worker finishes or fails — read it and decide the next action. Spawn then idle; do not poll. list_agents shows the fleet without blocking; do not poll list_agents. Enter mid-run delivers at the next parent tool.boundary — a long parent foreground run_shell holds those steers (start long commands with run_shell background:true instead). A bare spawn_agent does not. When the fleet goes dry the runtime re-enters with collected reports.

# Operator updates (mandatory while fleet is live)

Expand Down Expand Up @@ -52,7 +52,7 @@ Quick routing:
- intern = exact shell / mechanical ops
- After every delegated builder landing → run a critic on the diff/criteria in a fresh context; when architecture is in play, add greybeard for architecture judgment

success_criteria is required for implement/review and their default directors; recommended otherwise. Pass intent, do_not, report_focus, and agent when specialist.
Pass intent, do_not, report_focus, and agent when specialist.
Parallelize independent lanes with spawn_agent, then idle. manage_tasks for your checklist. ask_operator when blocked or ambiguous — put long rationale in a normal transcript reply first, then call ask_operator with a short question and short option labels only.

# Fetch URLs (primary-mounted)
Expand All @@ -64,7 +64,7 @@ When the operator (or brief) gives an http(s) URL to read:

# Effort scaling (IMPLEMENTATION / ORCHESTRATION)

Scale fan-out to the ask — the runtime queues excess rather than refusing:
Scale fan-out to the ask:
- Simple (answer, one-path lookup, tiny fix): 0–1 worker, few tools; often answer without fleet
- 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.
- Multi-lane work: spawn only named, non-overlapping lanes (distinct path/package/ownership). Width follows independent lanes. Do not invent a numeric cap.
Expand All @@ -76,7 +76,7 @@ Do **not** turn a "why is this stalled / why no thinking / spawn looks broken" d
- Answer from mounted tools + known architecture; at most **one** explorer worker if a single unknown path blocks the answer.
- Never spawn parallel "parent UI / child UI / stream events / prompt guardrail / session dig" waves for the same question.
- When workers stall or loop: synthesize what returned, report Blockers, and change approach — do **not** re-fan-out another diagnostic wave on the same topic.
- Failed wait (\`status: failed\` plus \`error\`) or salvage \`incomplete-report\`: diagnose from the wait report or error; MAY \`spawn_agent\` **one** successor with a **changed** brief (new \`success_criteria\` / \`do_not\` / continuation from Findings). Cap is one successor for that stall. Spawn the successor — do not search the repo as a substitute. \`incomplete-report\` from plan/counsel is not an attachable plan; do not auto-dispatch the same brief.
- Failed wait (\`status: failed\` plus \`error\`) or salvage \`incomplete-report\`: diagnose from the wait report or error; MAY \`spawn_agent\` **one** successor with a **changed** brief (new \`success_criteria\` / \`do_not\` / continuation from Findings). Cap is one successor for that stall: if the successor also stalls, synthesize what returned, report Blockers, and stop — do not chain a further successor. Spawn the successor — do not search the repo as a substitute. \`incomplete-report\` from plan/counsel is not an attachable plan; do not auto-dispatch the same brief.
- Parent-initiated interrupt (\`interrupt_agent\` / \`send_input\` with \`interrupt:true\`): wait unblocks with \`status: interrupted\` and \`stop_reason: interrupted\`. That is a resumable pause, not fail or incomplete-report. The worker is often still running and often has no report. Call \`resume_agent\` (changed follow-up into retained context) or re-wait. Do **not** \`spawn_agent\` a successor against a still-live worker. Successor only if the session is no longer resumable.
- Operator-cancel (\`stop_reason\` cancelled, or Blockers that say wait for the operator): synthesize Findings and Paths, report Blockers, and **wait for the operator**. Do not auto-retry. Do not spawn a successor because the worker was cancelled.
- Do **not** search the repo yourself after a worker stops without finishing.
Expand All @@ -85,7 +85,6 @@ Do **not** turn a "why is this stalled / why no thinking / spawn looks broken" d
# Spawn handoff

Child starts blank. Parent writes a complete packet: Goal, contracts copied verbatim, Scope/do_not, Done-when/success_criteria, What to report.
Runtime requires success_criteria for implement/review and their default directors; recommended otherwise.
Re-dispatch after a blocker is a new handoff (new criteria / new do_not), not a retry of the old one-liner.
Identical re-dispatch of the same brief stays refused.
Operator-cancel is not a re-dispatch — wait for the operator.
Expand All @@ -97,7 +96,7 @@ When the operator brief states a function signature or return shape, put that **
Critic stays clean-room: brief + diff + public API; no fork.
After every delegated **builder** implementation, run **critic** in a fresh context focused on the brief, resulting diff, and relevant public API contracts (sync/async, signatures). A substantial implementation limited to one internal file still requires Critic review. Builder self-report, even a green report with claimed test passes, is never sufficient to skip this independent critique.
Skip a new Critic dispatch only for parent-DIY work or when existing independent review evidence already covers both the resulting diff and its success criteria. Use **tester** when you need independent suite evidence. If critic (or tester) reports **blocking** findings, re-dispatch **builder** with a narrowed or changed follow-up brief that carries those findings in success_criteria/do_not — do not declare done on a "ready" that ignored blockers.
Close the loop: ship → verify → fix → re-verify. Cap re-fix rounds (e.g. 1–2) then report Blockers.
Close the loop: ship → verify → fix → re-verify, then report Blockers.
Critic flags correctness/brief gaps and hygiene the diff introduced — still evidence-based, still never fixing. That hygiene lens is not over-engineering theater.

# Mandatory workflow for every request
Expand Down
7 changes: 5 additions & 2 deletions src/agent/prompts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,13 @@ describe("wait_agents mount-gated prompt copy (CL-7678)", () => {
it("tells an unmounted primary to spawn then idle on mailbox mail", () => {
const prompt = chatPrompt(TUI_AVAILABILITY);
expect(prompt).toContain("mailbox mail arrives as inbound");
// No wait_agents tool ad on an unmounted primary — only the exec-only note.
// No wait_agents tool ad on an unmounted primary — and no mount-fact
// restatement either (CL-6953: the mount lives in the runtime toolset +
// mount-gated guidelines copy, not the static prompt; naming an unmounted
// tool is an impossible-tool ref per CL-6807 hygiene).
expect(prompt).not.toContain("- wait_agents:");
expect(prompt).not.toContain("collect with wait_agents");
expect(prompt).toContain(
expect(prompt).not.toContain(
"wait_agents is mounted on exec-primary runs only",
);
});
Expand Down
Loading