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
25 changes: 13 additions & 12 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ Enforcement is runtime code at the existing tool-mount point, not prompt wording

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

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.
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.

**Primary**

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

**Specialists**

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

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

Expand Down
10 changes: 5 additions & 5 deletions docs/PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,12 @@ Capabilities beyond the core toolset are opt-in plugins, enabled per workspace t

## Multi-agent (fleet agents)

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.
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.

| Role | Directors |
| ---------- | --------------------------------------------------------------------------------- |
| Primary | dispatch |
| Specialist | explorer, planner, coder, reviewer, designer, artist, warden, shakespeare, prober |
| Role | Directors |
| ---------- | ------------------------------------------------------------------------------------------ |
| Primary | dispatch |
| Specialist | explorer, planner, coder, reviewer, designer, artist, warden, shakespeare, prober, qa-lead |

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.

Expand Down
2 changes: 1 addition & 1 deletion scripts/ci-timings.json
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@
"src/agent/directors/prober/package.test.ts": 1,
"src/agent/directors/rand/package.test.ts": 1,
"src/agent/directors/shakespeare/package.test.ts": 1,
"src/agent/directors/tester/package.test.ts": 1,
"src/agent/directors/qa-lead/package.test.ts": 1,
"src/agent/directors/warden/package.test.ts": 1,
"src/agent/grok-residual.test.ts": 1,
"src/agent/tool-classification.test.ts": 1,
Expand Down
2 changes: 2 additions & 0 deletions src/agent/directors/dispatch/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ You are Dispatch, the coordinator for Corbits Code. Specialists own substantive
- Security auditing, trust boundaries, permissions, or secret guard review: spawn warden (permission and trust review).
- Product, architecture, or implementation documentation: spawn shakespeare (PRODUCT, ARCHITECTURE, IMPLEMENTATION docs).
- Model distribution benchmarks, latency probing, or prompt evaluation: spawn prober (latency and behavior distributions).
- 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.

# Delegation boundary
- Delegate by default. File count does not determine complexity: a single-file bug fix or behavior change still belongs to coder.
Expand Down Expand Up @@ -75,6 +76,7 @@ export const dispatchPackage: DirectorPackage = {
"warden",
"shakespeare",
"prober",
"qa-lead",
],
},
modelRole: "orchestrator",
Expand Down
59 changes: 59 additions & 0 deletions src/agent/directors/qa-lead/package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { DirectorPackage } from "../types.js";
import { REVIEW_TOOLS } from "../tool-sets.js";

/**
* QA Lead worker (CL-9903).
* Hands-on product exercise — actually run the CLI / e2e / occupancy /
* capability path as behavior proof (pass/fail with commands); never author
* unit tests, never measure family/model latency, never fix product code.
*/
export const qaLeadPackage: DirectorPackage = {
id: "qa-lead",
primaryIntent:
"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",
outOfLane: [
"authoring unit tests (coder)",
"measuring family/model latency (prober)",
"fixing product code",
"fleet orchestration or spawning",
],
description:
"QA Lead — hands-on product exercise: CLI/e2e/occupancy/capability proof",
systemPrompt: `You are QA Lead (\`qa-lead\`), a specialist in Corbits Code.

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.

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.

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.

# What to run

- The operator CLI: \`corbits\` and \`corbits exec\` against fixture copies or the worktree the brief names.
- Integration scenarios: \`bun test ./e2e\` — production agent loop, compaction occupancy, mailbox, permission, spawn lanes.
- 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.

# How to judge

- Each success_criteria item is pass, fail, or blocked, with the exact command, exit status, and relevant output.
- 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.
- 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.
- 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.

# Findings route to follow-ups, never silent product edits

- Report commands, exit codes, and failing output so Coder can reproduce.
- Product-looking conclusions go to Blockers as follow-up tickets — do not patch src/ here.

# Report

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.

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.

OUT OF LANE: authoring unit tests (route to coder), measuring family/model latency (route to prober), fixing product code, fleet orchestration, spawning.`,
tools: { allow: REVIEW_TOOLS },
spawn: { maySpawn: false },
tier: "leaf",
modelRole: "test",
};
40 changes: 35 additions & 5 deletions src/agent/directors/registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import {
} from "./registry.js";

describe("director registry", () => {
test("closed set has exactly 10 directors", () => {
expect(DIRECTOR_IDS).toHaveLength(10);
expect(listDirectors()).toHaveLength(10);
test("closed set has exactly 11 directors", () => {
expect(DIRECTOR_IDS).toHaveLength(11);
expect(listDirectors()).toHaveLength(11);
for (const id of DIRECTOR_IDS) {
expect(DIRECTOR_REGISTRY[id].id).toBe(id);
}
Expand Down Expand Up @@ -116,7 +116,7 @@ describe("director registry", () => {

test("directorProfiles is the spawn catalog (closed set minus dispatch)", () => {
const profiles = directorProfiles();
expect(profiles).toHaveLength(9);
expect(profiles).toHaveLength(10);
expect(profiles.map((p) => p.id)).not.toContain("dispatch");
});

Expand Down Expand Up @@ -157,7 +157,7 @@ describe("director registry", () => {
expect(s.tools?.allow).toContain("write_file");
expect(s.tools?.allow).toContain("edit_file");
expect(s.tools?.allow).toContain("delete_file");
expect(s.spawn.allowlist).toHaveLength(9);
expect(s.spawn.allowlist).toHaveLength(10);
});

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

test("qa-lead is a hands-on leaf, not prober, with no spawn", () => {
const r = resolveDirector({ agentId: "qa-lead" });
expect(r.ok).toBe(true);
if (r.ok) {
expect(r.package.id).toBe("qa-lead");
expect(r.package.tier).toBe("leaf");
expect(r.package.spawn.maySpawn).toBe(false);
expect(r.package.modelRole).toBe("test");
expect(r.package.tools?.allow).toContain("write_file");
expect(r.package.description).toMatch(/QA Lead/);
expect(r.package.systemPrompt).toMatch(/QA Lead/);
expect(r.package.systemPrompt).toMatch(/hands-on/i);
expect(r.package.systemPrompt).toMatch(/corbits exec/i);
expect(r.package.systemPrompt).toMatch(/bun test \.\/e2e/);
expect(r.package.systemPrompt).toMatch(/eval:capability/);
expect(r.package.systemPrompt).toMatch(/occupancy/i);
expect(r.package.systemPrompt).toMatch(/never author unit tests/i);
expect(r.package.systemPrompt).toMatch(
/never measure family\/model latency/i,
);
expect(r.package.systemPrompt).toMatch(/never fix product code/i);
expect(r.package).not.toBe(DIRECTOR_REGISTRY.prober);
}
expect(isDirectorId("qa-lead")).toBe(true);
expect(isDirectorId("tester")).toBe(false);
expect(tierForDirectorId("qa-lead")).toBe("leaf");
expect(DIRECTOR_REGISTRY.dispatch.spawn.allowlist).toContain("qa-lead");
expect(DIRECTOR_REGISTRY.dispatch.spawn.allowlist).not.toContain("tester");
});

test("dispatch card has no parallelization cap; duplicate is the same live job", () => {
const card = DIRECTOR_REGISTRY.dispatch.systemPrompt;
expect(card).toContain("no parallelization cap");
Expand Down
2 changes: 2 additions & 0 deletions src/agent/directors/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { dispatchPackage } from "./dispatch/package.js";
import { explorerPackage } from "./explorer/package.js";
import { plannerPackage } from "./planner/package.js";
import { proberPackage } from "./prober/package.js";
import { qaLeadPackage } from "./qa-lead/package.js";
import { reviewerPackage } from "./reviewer/package.js";
import { shakespearePackage } from "./shakespeare/package.js";
import { wardenPackage } from "./warden/package.js";
Expand Down Expand Up @@ -46,6 +47,7 @@ export const DIRECTOR_REGISTRY: Readonly<Record<DirectorId, DirectorPackage>> =
warden: wardenPackage,
shakespeare: shakespearePackage,
prober: proberPackage,
"qa-lead": qaLeadPackage,
};

export function isDirectorId(value: unknown): value is DirectorId {
Expand Down
1 change: 1 addition & 0 deletions src/agent/directors/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const DIRECTOR_IDS = [
"warden",
"shakespeare",
"prober",
"qa-lead",
] as const;

export type DirectorId = (typeof DIRECTOR_IDS)[number];
Expand Down
1 change: 1 addition & 0 deletions src/agent/prompt-sizes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const PROMPT_SIZE_BASELINE: Record<
warden: { chars: 4113, bytes: 4127 },
shakespeare: { chars: 5993, bytes: 6015 },
prober: { chars: 5428, bytes: 5454 },
"qa-lead": { chars: 5226, bytes: 5246 },
};

/**
Expand Down
Loading