Skip to content

Commit f67b30d

Browse files
committed
feat(dispatch): distinguish mailbox turn yield from task completion
1 parent 081c161 commit f67b30d

2 files changed

Lines changed: 55 additions & 0 deletions

File tree

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { describe, expect, test } from "bun:test";
2+
3+
import { DIRECTOR_REGISTRY } from "../registry.js";
4+
5+
const card = (): string => DIRECTOR_REGISTRY.dispatch.systemPrompt;
6+
7+
/**
8+
* CL-9920: mailbox turn yield vs task completion, plus midflight steering.
9+
* Instruction-only change, so the existing harness is the prompt contract:
10+
* each test scripts a staggered-worker scenario and asserts the dispatch
11+
* card carries the guidance that scenario requires. All fail before the
12+
* guidance lands.
13+
*/
14+
describe("dispatch yield/steering guidance (CL-9920)", () => {
15+
test("staggered completion: yield with pending workers is not task completion", () => {
16+
// Two workers spawned; one reports while the other still runs. The
17+
// resume turn must end as a yield, never as a completion claim.
18+
expect(card()).toMatch(/yield ends this turn/i);
19+
expect(card()).toMatch(/never .* completion .* pending/i);
20+
});
21+
22+
test("completion requires acceptance assessment and blocker disposition", () => {
23+
// All workers reported but a check is unassessed and a blocker is open:
24+
// completion still needs report-vs-brief acceptance plus disposition.
25+
expect(card()).toMatch(/acceptance/i);
26+
expect(card()).toMatch(/blocker/i);
27+
});
28+
29+
test("resumed steering reaches active workers through existing messaging only", () => {
30+
// Operator steers midflight; a worker is still running. The resumed
31+
// turn must incorporate the steering and forward it via send_input —
32+
// the existing channel — with no new scheduling/messaging invented.
33+
expect(card()).toMatch(/steering/i);
34+
expect(card()).toMatch(/send_input/);
35+
expect(card()).not.toMatch(/new channel|new tool|new messag/i);
36+
});
37+
38+
test("superseded work is named; stale results are never approval", () => {
39+
// Steering supersedes one lane while its stale report lands anyway.
40+
// Dispatch must name the superseded work and refuse the stale result.
41+
expect(card()).toMatch(/superseded/i);
42+
expect(card()).toMatch(/stale/i);
43+
});
44+
45+
test("unreachable steering is disclosed, acceptance withheld, stop preserved", () => {
46+
// Steering arrives but cannot reach active work: dispatch must say so,
47+
// withhold acceptance, and keep stop/cancel semantics unchanged.
48+
expect(card()).toMatch(/withhold acceptance/i);
49+
expect(card()).toMatch(/stop/i);
50+
});
51+
});

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ You are Dispatch, the coordinator for Corbits Code. Specialists own substantive
3636
- Spawn independent lanes in the same turn. There is no parallelization cap. Duplicate = the same live job, not the same repo.
3737
- 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.
3838
- After spawn, yield. Mailbox (TUI/nested) or wait_agents (exec) delivers. Spawn the next independent wave before the first finishes. Do not poll.
39+
- A yield ends this turn while work stays active: never claim completion while workers or checks are still pending.
40+
- Completion requires acceptance: assess each worker report against its brief, disposition blockers, and only then report done.
41+
- 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.
42+
- 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.
3943
- 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.
4044
- 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.
4145

0 commit comments

Comments
 (0)