Skip to content
Closed
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
51 changes: 51 additions & 0 deletions src/agent/directors/dispatch/package.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, expect, test } from "bun:test";

import { DIRECTOR_REGISTRY } from "../registry.js";

const card = (): string => DIRECTOR_REGISTRY.dispatch.systemPrompt;

/**
* CL-9920: mailbox turn yield vs task completion, plus midflight steering.
* Instruction-only change, so the existing harness is the prompt contract:
* each test scripts a staggered-worker scenario and asserts the dispatch
* card carries the guidance that scenario requires. All fail before the
* guidance lands.
*/
describe("dispatch yield/steering guidance (CL-9920)", () => {
test("staggered completion: yield with pending workers is not task completion", () => {
// Two workers spawned; one reports while the other still runs. The
// resume turn must end as a yield, never as a completion claim.
expect(card()).toMatch(/yield ends this turn/i);
expect(card()).toMatch(/never .* completion .* pending/i);
});

test("completion requires acceptance assessment and blocker disposition", () => {
// All workers reported but a check is unassessed and a blocker is open:
// completion still needs report-vs-brief acceptance plus disposition.
expect(card()).toMatch(/acceptance/i);
expect(card()).toMatch(/blocker/i);
});

test("resumed steering reaches active workers through existing messaging only", () => {
// Operator steers midflight; a worker is still running. The resumed
// turn must incorporate the steering and forward it via send_input —
// the existing channel — with no new scheduling/messaging invented.
expect(card()).toMatch(/steering/i);
expect(card()).toMatch(/send_input/);
expect(card()).not.toMatch(/new channel|new tool|new messag/i);
});

test("superseded work is named; stale results are never approval", () => {
// Steering supersedes one lane while its stale report lands anyway.
// Dispatch must name the superseded work and refuse the stale result.
expect(card()).toMatch(/superseded/i);
expect(card()).toMatch(/stale/i);
});

test("unreachable steering is disclosed, acceptance withheld, stop preserved", () => {
// Steering arrives but cannot reach active work: dispatch must say so,
// withhold acceptance, and keep stop/cancel semantics unchanged.
expect(card()).toMatch(/withhold acceptance/i);
expect(card()).toMatch(/stop/i);
});
});
4 changes: 4 additions & 0 deletions src/agent/directors/dispatch/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ You are Dispatch, the coordinator for Corbits Code. Specialists own substantive
- Spawn independent lanes in the same turn. There is no parallelization cap. Duplicate = the same live job, not the same repo.
- Split by path, ownership, or lens. One worker = one outcome. Independent tickets/files/reviews go out together. Do not serialize explorer-then-coder unless the coder brief needs the map.
- After spawn, yield. Mailbox (TUI/nested) or wait_agents (exec) delivers. Spawn the next independent wave before the first finishes. Do not poll.
- A yield ends this turn while work stays active: never claim completion while workers or checks are still pending.
- Completion requires acceptance: assess each worker report against its brief, disposition blockers, and only then report done.
- On resumption, incorporate newly received steering first. Forward relevant changes to still-active workers with send_input only — the existing messaging surface; invent no additional scheduling or delivery capabilities. Name superseded work, never adopt its stale results as approval, and never present them as acceptance.
- When steering cannot reach active work, disclose the limitation and withhold acceptance until it can be re-driven. Stop/cancel semantics are unchanged: stopping ends the run, it never completes it.
- Use reports as the working record. Resolve gaps with the same worker instead of repeating its investigation. Route unfinished implementation back to coder, not yourself.
- After coder finishes non-trivial or risky code changes, including single-file changes, run reviewer on the diff. Skip review only for mechanical or docs-only diffs and say so.

Expand Down
Loading