Skip to content

Commit 081c161

Browse files
Merge pull request #1319 from corbitsdev/cl-9903-add-tester-director-hands-on-product-exercise-not-unit-tests
feat(dispatch): add QA Lead director for hands-on product proof
2 parents 6d2755d + ffc1386 commit 081c161

9 files changed

Lines changed: 119 additions & 23 deletions

File tree

‎docs/ARCHITECTURE.md‎

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ Enforcement is runtime code at the existing tool-mount point, not prompt wording
278278

279279
#### Closed director fleet (`src/agent/directors/`)
280280

281-
Every shipped specialist is a **director package** — a prompt-first `DirectorPackage` (system prompt, tool envelope, spawn rights, nudge budget, report contract, `modelRole`, fleet authority `tier`) registered in a **closed** set of 10 ids. There is no catch-all worker: `spawn_agent` without `agent` or non-general `intent`, and `spawn_agent(intent="general")`, fail closed so the primary reclassifies. Nested directors with a spawn allowlist reject off-list children at `spawn_agent` dispatch time (not prompt-only). dispatch is the primary session identity: `spawn_agent(agent="dispatch")` is refused, and `directorProfiles()` omits it from the spawn catalog.
281+
Every shipped specialist is a **director package** — a prompt-first `DirectorPackage` (system prompt, tool envelope, spawn rights, nudge budget, report contract, `modelRole`, fleet authority `tier`) registered in a **closed** set of 11 ids. There is no catch-all worker: `spawn_agent` without `agent` or non-general `intent`, and `spawn_agent(intent="general")`, fail closed so the primary reclassifies. Nested directors with a spawn allowlist reject off-list children at `spawn_agent` dispatch time (not prompt-only). dispatch is the primary session identity: `spawn_agent(agent="dispatch")` is refused, and `directorProfiles()` omits it from the spawn catalog.
282282

283283
**Primary**
284284

@@ -288,17 +288,18 @@ Every shipped specialist is a **director package** — a prompt-first `DirectorP
288288

289289
**Specialists**
290290

291-
| Director | Owns | Does not own |
292-
| ----------- | -------------------------------------------------------------------- | ---------------------- |
293-
| explorer | Read-only codebase mapping | Product edits |
294-
| planner | PRD.md, SOLUTION_SCOPE.md, BUILD_PLAN.md authoring | Code |
295-
| coder | Minimal safe diffs, root-cause fixes, tests | Pure docs, pure review |
296-
| reviewer | Evidence-based defect and quality review with temp-test verification | Fixing product code |
297-
| designer | DESIGN.md, design laws, tokens, interface polish | Non-UI product code |
298-
| artist | SVGs, diagrams, generative graphic prompts | Product code |
299-
| warden | Trust review of permission, provider-auth, and plugin-loader diffs | Fixes, general review |
300-
| shakespeare | Docs maintenance (PRODUCT / ARCHITECTURE / IMPLEMENTATION) | Product code |
301-
| prober | Measure-only latency and behavior probe per family/model | Fixes, product edits |
291+
| Director | Owns | Does not own |
292+
| ----------- | -------------------------------------------------------------------- | ---------------------------------- |
293+
| explorer | Read-only codebase mapping | Product edits |
294+
| planner | PRD.md, SOLUTION_SCOPE.md, BUILD_PLAN.md authoring | Code |
295+
| coder | Minimal safe diffs, root-cause fixes, tests | Pure docs, pure review |
296+
| reviewer | Evidence-based defect and quality review with temp-test verification | Fixing product code |
297+
| designer | DESIGN.md, design laws, tokens, interface polish | Non-UI product code |
298+
| artist | SVGs, diagrams, generative graphic prompts | Product code |
299+
| warden | Trust review of permission, provider-auth, and plugin-loader diffs | Fixes, general review |
300+
| shakespeare | Docs maintenance (PRODUCT / ARCHITECTURE / IMPLEMENTATION) | Product code |
301+
| prober | Measure-only latency and behavior probe per family/model | Fixes, product edits |
302+
| qa-lead | QA Lead: hands-on CLI, e2e, occupancy, capability proof | Unit tests, latency, product fixes |
302303

303304
**Intent → director** (`spawn_agent(intent=…)` when `agent` is omitted). implement/review (and their default directors) fail closed without non-empty `success_criteria`.
304305

‎docs/PRODUCT.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,12 @@ Capabilities beyond the core toolset are opt-in plugins, enabled per workspace t
155155

156156
## Multi-agent (fleet agents)
157157

158-
The primary session is always **orchestrator** (single-agent mode is gone). Its director is **dispatch**: classify work, DIY tiny/single-file/one-route product edits, dispatch a **closed fleet of 10 directors** for substantial work, track the fleet, and synthesize. Product mutation tools (`write_file` / `edit_file` / `delete_file`) are mounted on the primary (CORE / `DISPATCH_TOOLS`) — path tools are the DIY surface; spawn remains the default for substantial, multi-file, parallel, or specialist work. Shell file-writes stay denied. MCP tools are not re-filtered by a product-write deny list (that list is gone). There is no static per-package write-path declaration (CL-6952 removed it — no shipped director ever set one). A concurrent dispatch landing on the same working directory as another still-running lane is recorded as a `conflict` intervention, not blocked. Operator slash recipes (`/implement`, `/plan`, `/refactor`, `/review`, `/pull-request`, `/issue`, `/docs`, `/interview`) tell dispatch which directors to spawn for substantial work; tiny/bounded edits may run on the primary.
158+
The primary session is always **orchestrator** (single-agent mode is gone). Its director is **dispatch**: classify work, DIY tiny/single-file/one-route product edits, dispatch a **closed fleet of 11 directors** for substantial work, track the fleet, and synthesize. Product mutation tools (`write_file` / `edit_file` / `delete_file`) are mounted on the primary (CORE / `DISPATCH_TOOLS`) — path tools are the DIY surface; spawn remains the default for substantial, multi-file, parallel, or specialist work. Shell file-writes stay denied. MCP tools are not re-filtered by a product-write deny list (that list is gone). There is no static per-package write-path declaration (CL-6952 removed it — no shipped director ever set one). A concurrent dispatch landing on the same working directory as another still-running lane is recorded as a `conflict` intervention, not blocked. Operator slash recipes (`/implement`, `/plan`, `/refactor`, `/review`, `/pull-request`, `/issue`, `/docs`, `/interview`) tell dispatch which directors to spawn for substantial work; tiny/bounded edits may run on the primary.
159159

160-
| Role | Directors |
161-
| ---------- | --------------------------------------------------------------------------------- |
162-
| Primary | dispatch |
163-
| Specialist | explorer, planner, coder, reviewer, designer, artist, warden, shakespeare, prober |
160+
| Role | Directors |
161+
| ---------- | ------------------------------------------------------------------------------------------ |
162+
| Primary | dispatch |
163+
| Specialist | explorer, planner, coder, reviewer, designer, artist, warden, shakespeare, prober, qa-lead |
164164

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

‎scripts/ci-timings.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@
451451
"src/agent/directors/prober/package.test.ts": 1,
452452
"src/agent/directors/rand/package.test.ts": 1,
453453
"src/agent/directors/shakespeare/package.test.ts": 1,
454-
"src/agent/directors/tester/package.test.ts": 1,
454+
"src/agent/directors/qa-lead/package.test.ts": 1,
455455
"src/agent/directors/warden/package.test.ts": 1,
456456
"src/agent/grok-residual.test.ts": 1,
457457
"src/agent/tool-classification.test.ts": 1,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ You are Dispatch, the coordinator for Corbits Code. Specialists own substantive
1616
- Security auditing, trust boundaries, permissions, or secret guard review: spawn warden (permission and trust review).
1717
- Product, architecture, or implementation documentation: spawn shakespeare (PRODUCT, ARCHITECTURE, IMPLEMENTATION docs).
1818
- Model distribution benchmarks, latency probing, or prompt evaluation: spawn prober (latency and behavior distributions).
19+
- Hands-on product exercise, e2e, occupancy, or actually using the CLI: spawn qa-lead (behavior proof, pass/fail with commands). Distinct from coder unit tests and prober measurement.
1920
2021
# Delegation boundary
2122
- Delegate by default. File count does not determine complexity: a single-file bug fix or behavior change still belongs to coder.
@@ -75,6 +76,7 @@ export const dispatchPackage: DirectorPackage = {
7576
"warden",
7677
"shakespeare",
7778
"prober",
79+
"qa-lead",
7880
],
7981
},
8082
modelRole: "orchestrator",
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { DirectorPackage } from "../types.js";
2+
import { REVIEW_TOOLS } from "../tool-sets.js";
3+
4+
/**
5+
* QA Lead worker (CL-9903).
6+
* Hands-on product exercise — actually run the CLI / e2e / occupancy /
7+
* capability path as behavior proof (pass/fail with commands); never author
8+
* unit tests, never measure family/model latency, never fix product code.
9+
*/
10+
export const qaLeadPackage: DirectorPackage = {
11+
id: "qa-lead",
12+
primaryIntent:
13+
"Hands-on product exercise: actually run CLI/e2e/occupancy/capability as proof (pass/fail with commands); never author unit tests, never measure latency, never fix product code",
14+
outOfLane: [
15+
"authoring unit tests (coder)",
16+
"measuring family/model latency (prober)",
17+
"fixing product code",
18+
"fleet orchestration or spawning",
19+
],
20+
description:
21+
"QA Lead — hands-on product exercise: CLI/e2e/occupancy/capability proof",
22+
systemPrompt: `You are QA Lead (\`qa-lead\`), a specialist in Corbits Code.
23+
24+
PRIMARY INTENT: hands-on product exercise. Actually run the product as an operator would and report behavior as proof — pass/fail with exact commands. Never author unit tests. Never measure family/model latency. Never fix product code. Never spawn.
25+
26+
You are the hands-on lane — not Coder (unit tests with product diffs), not Prober (family/model latency matrices), not Reviewer, not an orchestrator, not a product fixer. Do not spawn specialists. Do not edit product code to make a failing exercise pass; a test that moves the target is not an exercise.
27+
28+
BLINDERS ON: exercise what the brief's success_criteria ask for, on the product below. Do not wander into unit-test authorship, latency matrices, or product fixes.
29+
30+
# What to run
31+
32+
- The operator CLI: \`corbits\` and \`corbits exec\` against fixture copies or the worktree the brief names.
33+
- Integration scenarios: \`bun test ./e2e\` — production agent loop, compaction occupancy, mailbox, permission, spawn lanes.
34+
- Capability runs as behavior: \`bun run eval:capability\` (\`scripts/eval-capability.ts\` over \`evals/capability\`) — pass/fail of the product path, not a latency matrix.
35+
36+
# How to judge
37+
38+
- Each success_criteria item is pass, fail, or blocked, with the exact command, exit status, and relevant output.
39+
- Compaction occupancy: run the e2e compaction suite (\`e2e/compaction-*.test.ts\` and related occupancy coverage) and report whether occupancy, mailbox mail, and compact-then-continue actually behave.
40+
- Do not author \`src/**/*.test.ts\` unit tests — that is Coder's lane. Temporary repro files under \`tmp/\` are allowed only when the brief needs a scripted operator session, then clean them up.
41+
- Do not slice by family/model latency (TTFT, tool-only streaks, salvage/nudge counts) — that is Prober's lane. A capability run here is a behavior verdict, not a distribution.
42+
43+
# Findings route to follow-ups, never silent product edits
44+
45+
- Report commands, exit codes, and failing output so Coder can reproduce.
46+
- Product-looking conclusions go to Blockers as follow-up tickets — do not patch src/ here.
47+
48+
# Report
49+
50+
When done, stop tooling and reply with ONLY the Corbits report envelope — the shared scaffold owns its shape (Summary / Findings / Blockers / Paths, in that order), so this package does not re-specify it. Findings for this lane: pass/fail/blocked per success_criteria item, exact commands and exit statuses, occupancy/e2e/CLI evidence.
51+
52+
DONE GATE: stop when the brief's exercise ask is answered with evidence OR explicitly blocked under Blockers. Do not expand into unit tests, latency matrices, product fixes, or orchestration.
53+
54+
OUT OF LANE: authoring unit tests (route to coder), measuring family/model latency (route to prober), fixing product code, fleet orchestration, spawning.`,
55+
tools: { allow: REVIEW_TOOLS },
56+
spawn: { maySpawn: false },
57+
tier: "leaf",
58+
modelRole: "test",
59+
};

‎src/agent/directors/registry.test.ts‎

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import {
1313
} from "./registry.js";
1414

1515
describe("director registry", () => {
16-
test("closed set has exactly 10 directors", () => {
17-
expect(DIRECTOR_IDS).toHaveLength(10);
18-
expect(listDirectors()).toHaveLength(10);
16+
test("closed set has exactly 11 directors", () => {
17+
expect(DIRECTOR_IDS).toHaveLength(11);
18+
expect(listDirectors()).toHaveLength(11);
1919
for (const id of DIRECTOR_IDS) {
2020
expect(DIRECTOR_REGISTRY[id].id).toBe(id);
2121
}
@@ -116,7 +116,7 @@ describe("director registry", () => {
116116

117117
test("directorProfiles is the spawn catalog (closed set minus dispatch)", () => {
118118
const profiles = directorProfiles();
119-
expect(profiles).toHaveLength(9);
119+
expect(profiles).toHaveLength(10);
120120
expect(profiles.map((p) => p.id)).not.toContain("dispatch");
121121
});
122122

@@ -157,7 +157,7 @@ describe("director registry", () => {
157157
expect(s.tools?.allow).toContain("write_file");
158158
expect(s.tools?.allow).toContain("edit_file");
159159
expect(s.tools?.allow).toContain("delete_file");
160-
expect(s.spawn.allowlist).toHaveLength(9);
160+
expect(s.spawn.allowlist).toHaveLength(10);
161161
});
162162

163163
test("tier agrees with spawn.maySpawn for every director", () => {
@@ -182,6 +182,36 @@ describe("director registry", () => {
182182
expect(tierForDirectorId("prober")).toBe("leaf");
183183
});
184184

185+
test("qa-lead is a hands-on leaf, not prober, with no spawn", () => {
186+
const r = resolveDirector({ agentId: "qa-lead" });
187+
expect(r.ok).toBe(true);
188+
if (r.ok) {
189+
expect(r.package.id).toBe("qa-lead");
190+
expect(r.package.tier).toBe("leaf");
191+
expect(r.package.spawn.maySpawn).toBe(false);
192+
expect(r.package.modelRole).toBe("test");
193+
expect(r.package.tools?.allow).toContain("write_file");
194+
expect(r.package.description).toMatch(/QA Lead/);
195+
expect(r.package.systemPrompt).toMatch(/QA Lead/);
196+
expect(r.package.systemPrompt).toMatch(/hands-on/i);
197+
expect(r.package.systemPrompt).toMatch(/corbits exec/i);
198+
expect(r.package.systemPrompt).toMatch(/bun test \.\/e2e/);
199+
expect(r.package.systemPrompt).toMatch(/eval:capability/);
200+
expect(r.package.systemPrompt).toMatch(/occupancy/i);
201+
expect(r.package.systemPrompt).toMatch(/never author unit tests/i);
202+
expect(r.package.systemPrompt).toMatch(
203+
/never measure family\/model latency/i,
204+
);
205+
expect(r.package.systemPrompt).toMatch(/never fix product code/i);
206+
expect(r.package).not.toBe(DIRECTOR_REGISTRY.prober);
207+
}
208+
expect(isDirectorId("qa-lead")).toBe(true);
209+
expect(isDirectorId("tester")).toBe(false);
210+
expect(tierForDirectorId("qa-lead")).toBe("leaf");
211+
expect(DIRECTOR_REGISTRY.dispatch.spawn.allowlist).toContain("qa-lead");
212+
expect(DIRECTOR_REGISTRY.dispatch.spawn.allowlist).not.toContain("tester");
213+
});
214+
185215
test("dispatch card has no parallelization cap; duplicate is the same live job", () => {
186216
const card = DIRECTOR_REGISTRY.dispatch.systemPrompt;
187217
expect(card).toContain("no parallelization cap");

‎src/agent/directors/registry.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { dispatchPackage } from "./dispatch/package.js";
66
import { explorerPackage } from "./explorer/package.js";
77
import { plannerPackage } from "./planner/package.js";
88
import { proberPackage } from "./prober/package.js";
9+
import { qaLeadPackage } from "./qa-lead/package.js";
910
import { reviewerPackage } from "./reviewer/package.js";
1011
import { shakespearePackage } from "./shakespeare/package.js";
1112
import { wardenPackage } from "./warden/package.js";
@@ -46,6 +47,7 @@ export const DIRECTOR_REGISTRY: Readonly<Record<DirectorId, DirectorPackage>> =
4647
warden: wardenPackage,
4748
shakespeare: shakespearePackage,
4849
prober: proberPackage,
50+
"qa-lead": qaLeadPackage,
4951
};
5052

5153
export function isDirectorId(value: unknown): value is DirectorId {

‎src/agent/directors/types.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const DIRECTOR_IDS = [
1414
"warden",
1515
"shakespeare",
1616
"prober",
17+
"qa-lead",
1718
] as const;
1819

1920
export type DirectorId = (typeof DIRECTOR_IDS)[number];

‎src/agent/prompt-sizes.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const PROMPT_SIZE_BASELINE: Record<
4444
warden: { chars: 4113, bytes: 4127 },
4545
shakespeare: { chars: 5993, bytes: 6015 },
4646
prober: { chars: 5428, bytes: 5454 },
47+
"qa-lead": { chars: 5226, bytes: 5246 },
4748
};
4849

4950
/**

0 commit comments

Comments
 (0)