From 3adc4102adf457543d5b172c93370ceae1be7783 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 12 Sep 2026 18:10:42 -0700 Subject: [PATCH 1/5] Add measure-only prober leaf director --- src/agent/directors/prober/index.ts | 1 + src/agent/directors/prober/package.test.ts | 108 +++++++++++++++++++++ src/agent/directors/prober/package.ts | 90 +++++++++++++++++ src/agent/directors/registry.test.ts | 27 ++++-- src/agent/directors/registry.ts | 2 + src/agent/directors/types.ts | 1 + src/agent/prompt-sizes.test.ts | 2 + 7 files changed, 225 insertions(+), 6 deletions(-) create mode 100644 src/agent/directors/prober/index.ts create mode 100644 src/agent/directors/prober/package.test.ts create mode 100644 src/agent/directors/prober/package.ts diff --git a/src/agent/directors/prober/index.ts b/src/agent/directors/prober/index.ts new file mode 100644 index 000000000..0a0caaa3a --- /dev/null +++ b/src/agent/directors/prober/index.ts @@ -0,0 +1 @@ +export { proberPackage } from "./package.js"; diff --git a/src/agent/directors/prober/package.test.ts b/src/agent/directors/prober/package.test.ts new file mode 100644 index 000000000..e3940ce94 --- /dev/null +++ b/src/agent/directors/prober/package.test.ts @@ -0,0 +1,108 @@ +import { describe, expect, test } from "bun:test"; +import { proberPackage } from "./package.js"; + +describe("proberPackage", () => { + test("id matches directory / registry id", () => { + expect(proberPackage.id).toBe("prober"); + }); + + test("systemPrompt is non-empty and not a Placeholder", () => { + expect(proberPackage.systemPrompt.length).toBeGreaterThan(0); + expect(proberPackage.systemPrompt.startsWith("Placeholder")).toBe(false); + }); + + test("systemPrompt identity is Prober / ProberDirector", () => { + const p = proberPackage.systemPrompt; + expect(p).toMatch(/ProberDirector \(Prober\)/); + expect(p).toContain("PRIMARY INTENT"); + expect(p).toMatch(/measure-only lane/i); + }); + + test("systemPrompt states the measure-only lane (TTFT, latency, streaks, salvage/nudge)", () => { + const p = proberPackage.systemPrompt; + expect(p).toMatch(/measure latency and behavior distributions/i); + expect(p).toMatch(/TTFT/); + expect(p).toMatch(/per-turn latency/i); + expect(p).toMatch(/tool-only streak/i); + expect(p).toMatch(/salvage counts/i); + expect(p).toMatch(/nudge counts/i); + expect(p).toMatch(/per family\/model/i); + }); + + test("systemPrompt forbids shipping product code and tuning prompts/policy", () => { + const p = proberPackage.systemPrompt; + expect(p).toMatch(/never ship product code/i); + expect(p).toMatch(/never tune prompts or model-family policy/i); + expect(p).toMatch(/never silent retunes/i); + }); + + test("systemPrompt consumes the existing harness (no second harness)", () => { + const p = proberPackage.systemPrompt; + expect(p).toContain("bun run eval:capability"); + expect(p).toContain("scripts/eval-capability.ts"); + expect(p).toContain("src/perf"); + expect(p).toContain("rollup.ts"); + expect(p).toContain("assert-spans.ts"); + expect(p).toContain("attribution-report.ts"); + expect(p).toMatch(/do not build a second one/i); + expect(p).toMatch(/do not hand-roll/i); + }); + + test("systemPrompt routes findings to model-family-policy follow-ups", () => { + const p = proberPackage.systemPrompt; + expect(p).toContain("src/agent/model-family-policy.ts"); + expect(p).toMatch(/follow-up tickets/i); + expect(p).toMatch(/do not edit the policy here/i); + }); + + test("systemPrompt states Corbits report shape and done gate", () => { + const p = proberPackage.systemPrompt; + expect(p).toContain("## Summary"); + expect(p).toContain("## Findings"); + expect(p).toContain("## Blockers"); + expect(p).toContain("## Paths"); + expect(p).toMatch(/DONE GATE/i); + expect(p).toMatch(/BLINDERS ON/i); + expect(p).toContain("success_criteria"); + }); + + test("systemPrompt has no tool-schema restatement or fake caps", () => { + const p = proberPackage.systemPrompt; + expect(p).not.toMatch(/parameters?:/i); + expect(p).not.toMatch(/fan-out/i); + expect(p).not.toMatch(/at most \d+/i); + expect(p).not.toMatch(/turn budget/i); + expect(p).not.toMatch(/scheduler/i); + }); + + test("spawn.maySpawn is false (leaf)", () => { + expect(proberPackage.spawn.maySpawn).toBe(false); + }); + + test("tier is leaf", () => { + expect(proberPackage.tier).toBe("leaf"); + }); + + test("tools.allow mounts the review surface (lane discipline in prompt)", () => { + const allow = proberPackage.tools?.allow ?? []; + expect(allow).toContain("read_file"); + expect(allow).toContain("run_shell"); + expect(allow).toContain("write_file"); + expect(allow).toContain("edit_file"); + expect(allow).toContain("delete_file"); + }); + + test("modelRole is test", () => { + expect(proberPackage.modelRole).toBe("test"); + }); + + test("primaryIntent and outOfLane match the prober lane", () => { + expect(proberPackage.primaryIntent).toMatch(/measure/i); + expect(proberPackage.primaryIntent).toMatch(/never ship product code/i); + expect(proberPackage.primaryIntent).toMatch(/never tune/i); + const joined = proberPackage.outOfLane.join(" "); + expect(joined).toMatch(/shipping product code/i); + expect(joined).toMatch(/tuning prompts or model-family policy/i); + expect(joined).toMatch(/new eval harness/i); + }); +}); diff --git a/src/agent/directors/prober/package.ts b/src/agent/directors/prober/package.ts new file mode 100644 index 000000000..2dd8bfb75 --- /dev/null +++ b/src/agent/directors/prober/package.ts @@ -0,0 +1,90 @@ +import type { DirectorPackage } from "../types.js"; +import { REVIEW_TOOLS } from "../tool-sets.js"; + +/** + * Prober worker (CL-7656). + * Measure-only latency/behavior prober — report distributions per + * family/model; never ship product code, never tune prompts or policy. + */ +export const proberPackage: DirectorPackage = { + id: "prober", + primaryIntent: + "Measure latency and behavior distributions per family/model; never ship product code, never tune prompts or policy", + outOfLane: [ + "shipping product code", + "tuning prompts or model-family policy", + "building a new eval harness", + "fleet orchestration", + "architecture essays without measurements", + ], + description: "Measure-only latency/behavior prober per family/model", + systemPrompt: `You are ProberDirector (Prober), a specialist in Corbits Code. + +PRIMARY INTENT: measure latency and behavior distributions per family/model and report the numbers with evidence. Never ship product code. Never tune prompts or model-family policy. Findings feed model-family-policy as follow-up tickets — never silent retunes. + +You are the measure-only lane — not Builder, not Counsel, not an orchestrator. Do not spawn specialists. Do not edit product code, prompts, or policy to "improve" the numbers mid-probe; a probe that moves the target is not a measurement. + +BLINDERS ON: measure what the brief's success_criteria ask for, on the harness below, sliced per family/model. Do not wander into fixes, retunes, or fleet orchestration. + +# What to measure + +- TTFT (time to first token) and per-turn latency distributions. +- Tool-only streak distribution (consecutive tool-call turns with no text). +- Salvage counts (incomplete-report recoveries) and nudge counts + (wrap-up / stall check-ins) per family/model. +- Always slice by family/model — a global average that hides a family + regression is a failed probe. + +# Harness (consume — do not build a second one) + +- Capability runs: \`bun run eval:capability\` (\`scripts/eval-capability.ts\` + over \`evals/capability\`) — the product non-TUI path (\`loadConfig\` + + \`runExec\`) against fixture copies with \`verify.sh\` graders. +- Latency spans: the PerfTrace harness under \`src/perf\` — \`rollup.ts\` + (TTFT vs stream split), \`assert-spans.ts\` (span assertions), and + \`attribution-report.ts\` (per-turn attribution). +- Read how they invoke before running; extend neither. If the harness + cannot answer the brief, report that under Blockers — do not hand-roll + a replacement harness. + +# Findings route to follow-ups, never silent retunes + +- Report numbers with the exact commands, matrix cells, and harness + outputs behind them so a follow-up can reproduce the probe. +- Policy-looking conclusions (e.g. "family X needs a tighter tool-only + nudge") go to Blockers/Findings as follow-up tickets against + \`src/agent/model-family-policy.ts\` — do not edit the policy here. +- Prompt-looking conclusions go the same route — named follow-ups, not + edits. + +# Corbits report shape + +When done, stop tooling and reply with ONLY this envelope: + +## Summary +One or two sentences: what was measured and the headline numbers. + +## Findings +Distributions per family/model (TTFT, per-turn latency, tool-only +streaks, salvage/nudge counts), the exact harness commands and matrix +behind them, and follow-up tickets for policy/prompt owners. + +## Blockers +Open questions, harness gaps, or assumptions. Write "None." if clear. + +## Paths +Harness files, fixtures, and outputs you read or produced (one per +line). Write "None." if none. + +DONE GATE: stop when the brief's measure ask is answered with evidence +OR explicitly blocked under Blockers. Do not expand into fixes, +retunes, or orchestration. + +OUT OF LANE: shipping product code, tuning prompts or model-family +policy (route to follow-up tickets), building a new harness, fleet +orchestration, architecture essays without measurements.`, + tools: { allow: REVIEW_TOOLS }, + spawn: { maySpawn: false }, + tier: "leaf", + modelRole: "test", +}; diff --git a/src/agent/directors/registry.test.ts b/src/agent/directors/registry.test.ts index 53c746d79..fecbb6a56 100644 --- a/src/agent/directors/registry.test.ts +++ b/src/agent/directors/registry.test.ts @@ -13,9 +13,9 @@ import { } from "./registry.js"; describe("director registry", () => { - test("closed set has exactly 17 directors", () => { - expect(DIRECTOR_IDS).toHaveLength(17); - expect(listDirectors()).toHaveLength(17); + test("closed set has exactly 18 directors", () => { + expect(DIRECTOR_IDS).toHaveLength(18); + expect(listDirectors()).toHaveLength(18); for (const id of DIRECTOR_IDS) { expect(DIRECTOR_REGISTRY[id].id).toBe(id); } @@ -116,8 +116,8 @@ describe("director registry", () => { test("directorProfiles is the spawn catalog (closed set minus skywalker)", () => { const profiles = directorProfiles(); - expect(profiles).toHaveLength(16); - expect(new Set(profiles.map((p) => p.id)).size).toBe(16); + expect(profiles).toHaveLength(17); + expect(new Set(profiles.map((p) => p.id)).size).toBe(17); expect(profiles.map((p) => p.id)).not.toContain("skywalker"); }); @@ -150,6 +150,7 @@ describe("director registry", () => { "shakespeare", "bruckheimer", "rand", + "prober", "skywalker", "gauntlet", ] as const) { @@ -199,7 +200,7 @@ describe("director registry", () => { }); // CL-6941: tier and spawn.maySpawn independently encode "may this package - // spawn", hand-set across 17 files. This pins their agreement so drift + // spawn", hand-set across 18 files. This pins their agreement so drift // (adding maySpawn: true without bumping tier, or vice versa) fails a test // instead of surfacing as an unexplained FleetAuthorityError at dispatch. test("tier agrees with spawn.maySpawn for every director", () => { @@ -226,6 +227,20 @@ describe("director registry", () => { expect(tierForDirectorId("gauntlet")).toBe("leaf"); }); + test("prober is a measure-only leaf (CL-7656)", () => { + const r = resolveDirector({ agentId: "prober" }); + expect(r.ok).toBe(true); + if (r.ok) { + expect(r.package.id).toBe("prober"); + expect(r.package.tier).toBe("leaf"); + expect(r.package.spawn.maySpawn).toBe(false); + expect(r.package.modelRole).toBe("test"); + expect(r.package.primaryIntent).toMatch(/measure/i); + } + expect(isDirectorId("prober")).toBe(true); + expect(tierForDirectorId("prober")).toBe("leaf"); + }); + test("every director profile declares matching agent id in system prompt", () => { for (const id of DIRECTOR_IDS) { const profile = packageToProfile(DIRECTOR_REGISTRY[id]); diff --git a/src/agent/directors/registry.ts b/src/agent/directors/registry.ts index e4119b581..bd846d751 100644 --- a/src/agent/directors/registry.ts +++ b/src/agent/directors/registry.ts @@ -16,6 +16,7 @@ import { skywalkerPackage } from "./skywalker/index.js"; import { testerPackage } from "./tester/index.js"; import { testsmithPackage } from "./testsmith/index.js"; import { gauntletPackage } from "./gauntlet/index.js"; +import { proberPackage } from "./prober/index.js"; import { formatDirectorSystemPrompt } from "./identity.js"; import { DIRECTOR_IDS, @@ -60,6 +61,7 @@ export const DIRECTOR_REGISTRY: Readonly> = testsmith: testsmithPackage, tester: testerPackage, gauntlet: gauntletPackage, + prober: proberPackage, }; export function isDirectorId(value: unknown): value is DirectorId { diff --git a/src/agent/directors/types.ts b/src/agent/directors/types.ts index 06b608c7b..bbb54643a 100644 --- a/src/agent/directors/types.ts +++ b/src/agent/directors/types.ts @@ -21,6 +21,7 @@ export const DIRECTOR_IDS = [ "testsmith", "tester", "gauntlet", + "prober", ] as const; export type DirectorId = (typeof DIRECTOR_IDS)[number]; diff --git a/src/agent/prompt-sizes.test.ts b/src/agent/prompt-sizes.test.ts index d4ffc9b2f..fab67081b 100644 --- a/src/agent/prompt-sizes.test.ts +++ b/src/agent/prompt-sizes.test.ts @@ -36,6 +36,7 @@ const CHAR_BUDGET: Record = { // CL-7658: grok family is the max (13990 chars); budget = measured + // 2000 allowance, ceiling to 100. gauntlet: 16000, + prober: 15200, }; const BYTE_BUDGET: Record = { @@ -60,6 +61,7 @@ const BYTE_BUDGET: Record = { // CL-7658: grok family is the max (14050 bytes); budget = measured + // 3000 allowance, ceiling to 100. gauntlet: 17100, + prober: 16300, }; function budgetMessage( From 36fb60741fcf5a25e76a0e0c5d3bcb6f56194838 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 12 Sep 2026 18:28:58 -0700 Subject: [PATCH 2/5] Wire prober into the Skywalker spawn graph --- src/agent/directors/registry.test.ts | 2 +- src/agent/directors/skywalker/package.test.ts | 3 ++- src/agent/directors/skywalker/package.ts | 6 ++++-- src/agent/prompt-sizes.test.ts | 8 ++++++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/agent/directors/registry.test.ts b/src/agent/directors/registry.test.ts index fecbb6a56..22655713e 100644 --- a/src/agent/directors/registry.test.ts +++ b/src/agent/directors/registry.test.ts @@ -196,7 +196,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(16); + expect(s.spawn.allowlist).toHaveLength(17); }); // CL-6941: tier and spawn.maySpawn independently encode "may this package diff --git a/src/agent/directors/skywalker/package.test.ts b/src/agent/directors/skywalker/package.test.ts index d9f3e4263..3a1022486 100644 --- a/src/agent/directors/skywalker/package.test.ts +++ b/src/agent/directors/skywalker/package.test.ts @@ -28,7 +28,7 @@ describe("skywalkerPackage", () => { test("maySpawn true with full closed allowlist", () => { expect(skywalkerPackage.spawn.maySpawn).toBe(true); - expect(skywalkerPackage.spawn.allowlist).toHaveLength(16); + expect(skywalkerPackage.spawn.allowlist).toHaveLength(17); expect(skywalkerPackage.spawn.allowlist).toEqual([ "builder", "explorer", @@ -46,6 +46,7 @@ describe("skywalkerPackage", () => { "testsmith", "tester", "gauntlet", + "prober", ]); }); diff --git a/src/agent/directors/skywalker/package.ts b/src/agent/directors/skywalker/package.ts index 247ea6cee..2b79bae38 100644 --- a/src/agent/directors/skywalker/package.ts +++ b/src/agent/directors/skywalker/package.ts @@ -31,7 +31,7 @@ Example chains: - feature: explorer → plan → implement → critic - "why / how / is this stalled": answer yourself; at most one explorer if a single unknown blocks you -Closed directors (use search_agents / registry; each id is a spawn agent= target): builder, explorer, counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot, draper, emil, rand, shakespeare, testsmith, tester, gauntlet. +Closed directors (use search_agents / registry; each id is a spawn agent= target): builder, explorer, counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot, draper, emil, rand, shakespeare, testsmith, tester, gauntlet, prober. No catch-all worker. If unsure, reclassify — do not spawn a blob agent. Quick routing: @@ -44,6 +44,7 @@ Quick routing: - tester = run the suite / repro - testsmith = design permanent test cases - gauntlet = mutation-check that tests can actually fail (tree clean) +- prober = measure-only latency/behavior probe per family/model - shakespeare = PRODUCT/ARCHITECTURE/IMPLEMENTATION docs - rand = DESIGN.md only - draper = visual/CBS review @@ -144,7 +145,7 @@ Do not reclassify COMMUNICATION as ORCHESTRATION just to justify parallel spawn # Spawn graph Skywalker = full closed set. Greybeard = limited spawn only (intern/explorer/critic) — not a second primary. -You may spawn: builder, explorer, counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot, draper, emil, rand, shakespeare, testsmith, tester, gauntlet. +You may spawn: builder, explorer, counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot, draper, emil, rand, shakespeare, testsmith, tester, gauntlet, prober. When spawning, pass a typed brief. success_criteria is required for implement/review and their default directors; recommended otherwise: - intent — explore | implement | plan | review @@ -205,6 +206,7 @@ export const skywalkerPackage: DirectorPackage = { "testsmith", "tester", "gauntlet", + "prober", ], }, modelRole: "orchestrator", diff --git a/src/agent/prompt-sizes.test.ts b/src/agent/prompt-sizes.test.ts index fab67081b..17f259423 100644 --- a/src/agent/prompt-sizes.test.ts +++ b/src/agent/prompt-sizes.test.ts @@ -36,7 +36,9 @@ const CHAR_BUDGET: Record = { // CL-7658: grok family is the max (13990 chars); budget = measured + // 2000 allowance, ceiling to 100. gauntlet: 16000, - prober: 15200, + // CL-7656: grok family is the max (13711 chars); budget = measured + + // 2000 allowance, ceiling to 100. + prober: 15800, }; const BYTE_BUDGET: Record = { @@ -61,7 +63,9 @@ const BYTE_BUDGET: Record = { // CL-7658: grok family is the max (14050 bytes); budget = measured + // 3000 allowance, ceiling to 100. gauntlet: 17100, - prober: 16300, + // CL-7656: grok family is the max (13779 bytes); budget = measured + + // 3000 allowance, ceiling to 100. + prober: 16800, }; function budgetMessage( From 123113e57cc306e33994187bc7e66196e8d26fc2 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 12 Sep 2026 22:14:01 -0700 Subject: [PATCH 3/5] Yield prober report format to the scaffold-owned worker envelope The shared scaffold owns Summary / Findings / Blockers / Paths, so the lane report content is carried as a Findings sentence instead of re-specified headings. --- src/agent/directors/prober/package.test.ts | 14 +++++++++----- src/agent/directors/prober/package.ts | 19 ++----------------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/agent/directors/prober/package.test.ts b/src/agent/directors/prober/package.test.ts index e3940ce94..4f5ee089b 100644 --- a/src/agent/directors/prober/package.test.ts +++ b/src/agent/directors/prober/package.test.ts @@ -55,12 +55,16 @@ describe("proberPackage", () => { expect(p).toMatch(/do not edit the policy here/i); }); - test("systemPrompt states Corbits report shape and done gate", () => { + test("systemPrompt points at the scaffold-owned worker report envelope (no re-spec)", () => { const p = proberPackage.systemPrompt; - expect(p).toContain("## Summary"); - expect(p).toContain("## Findings"); - expect(p).toContain("## Blockers"); - expect(p).toContain("## Paths"); + expect(p).toMatch(/Corbits report envelope/); + expect(p).toMatch(/scaffold owns its shape/i); + expect(p).not.toContain("## Summary"); + expect(p).not.toContain("## Findings"); + expect(p).not.toContain("## Blockers"); + expect(p).not.toContain("## Paths"); + expect(p).toMatch(/distributions per family\/model/i); + expect(p).toMatch(/follow-up tickets for policy\/prompt owners/i); expect(p).toMatch(/DONE GATE/i); expect(p).toMatch(/BLINDERS ON/i); expect(p).toContain("success_criteria"); diff --git a/src/agent/directors/prober/package.ts b/src/agent/directors/prober/package.ts index 2dd8bfb75..dad93a15b 100644 --- a/src/agent/directors/prober/package.ts +++ b/src/agent/directors/prober/package.ts @@ -57,24 +57,9 @@ BLINDERS ON: measure what the brief's success_criteria ask for, on the harness b - Prompt-looking conclusions go the same route — named follow-ups, not edits. -# Corbits report shape +# Report -When done, stop tooling and reply with ONLY this envelope: - -## Summary -One or two sentences: what was measured and the headline numbers. - -## Findings -Distributions per family/model (TTFT, per-turn latency, tool-only -streaks, salvage/nudge counts), the exact harness commands and matrix -behind them, and follow-up tickets for policy/prompt owners. - -## Blockers -Open questions, harness gaps, or assumptions. Write "None." if clear. - -## Paths -Harness files, fixtures, and outputs you read or produced (one per -line). Write "None." if none. +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: distributions per family/model (TTFT, per-turn latency, tool-only streaks, salvage/nudge counts), the exact harness commands and matrix behind them, and follow-up tickets for policy/prompt owners. DONE GATE: stop when the brief's measure ask is answered with evidence OR explicitly blocked under Blockers. Do not expand into fixes, From 84e90a5bb791e9960b26f80bf849f84b14ef7196 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 12 Sep 2026 22:36:05 -0700 Subject: [PATCH 4/5] Renumber closed fleet 17->18 for prober (docs prose) --- README.md | 2 +- docs/ARCHITECTURE.md | 3 ++- docs/PRODUCT.md | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c3a7344c8..30115bb2c 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ Corbits Code keeps repository guidance and the closed director fleet separate: - `src/agent/directors/` — closed spawn catalog (`directorProfiles()`). Skywalker is the primary orchestrator; spawnable directors include builder, explorer, counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot, draper, - emil, rand, shakespeare, testsmith, tester, and gauntlet. Closed ids cannot be + emil, rand, shakespeare, testsmith, tester, gauntlet, and prober. Closed ids cannot be overridden by plugins or local files. - `.agents/agents/` — optional local profile additions; this directory is not required and may be absent diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 0d1c0231c..b46da1319 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -244,7 +244,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 17 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). Skywalker is the primary session identity: `spawn_agent(agent="skywalker")` 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 18 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). Skywalker is the primary session identity: `spawn_agent(agent="skywalker")` is refused, and `directorProfiles()` omits it from the spawn catalog. **Primary** @@ -282,6 +282,7 @@ Every shipped specialist is a **director package** — a prompt-first `DirectorP | testsmith | Test design only (what/how to test) | | tester | Runtime verification; never fix product code | | gauntlet | Mutation/vacuity check that named tests can actually fail | +| prober | Measure-only latency/behavior probe per family/model | **Intent → director** (`spawn_agent(intent=…)` when `agent` is omitted). implement/review (and their default directors) fail closed without non-empty `success_criteria`. diff --git a/docs/PRODUCT.md b/docs/PRODUCT.md index 70af236ea..dab98b953 100644 --- a/docs/PRODUCT.md +++ b/docs/PRODUCT.md @@ -155,14 +155,14 @@ 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 identity is **Skywalker** (product name remains Corbits Code; when asked its name, answer Skywalker): classify work, DIY tiny/single-file/one-route product edits, dispatch a **closed fleet of 17 directors** for substantial work, track the fleet, and synthesize. Product mutation tools (`write_file` / `edit_file` / `delete_file`) are mounted on the primary (CORE / `SKYWALKER_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-review`, `/create-issue`, `/scribe`, `/interview`, `/ast-grep`) tell Skywalker 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 identity is **Skywalker** (product name remains Corbits Code; when asked its name, answer Skywalker): classify work, DIY tiny/single-file/one-route product edits, dispatch a **closed fleet of 18 directors** for substantial work, track the fleet, and synthesize. Product mutation tools (`write_file` / `edit_file` / `delete_file`) are mounted on the primary (CORE / `SKYWALKER_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-review`, `/create-issue`, `/scribe`, `/interview`, `/ast-grep`) tell Skywalker which directors to spawn for substantial work; tiny/bounded edits may run on the primary. | Lane | Directors | | --------- | -------------------------------------------------------------------------------------- | | Primary | skywalker | | Eng | builder, explorer, counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot | | Design | draper, emil, rand | -| Docs / QA | shakespeare, testsmith, tester, gauntlet | +| Docs / QA | shakespeare, testsmith, tester, gauntlet, prober | There is **no catch-all worker**. `spawn_agent` requires `agent=…` or a non-general `intent` (implement/explore/plan/review→critic); bare dispatch and `intent=general` are refused. Named `spawn_agent(agent=…)` selects a director package without requiring a plugin profile, except `skywalker` which is the primary session identity and is refused as a spawned worker. Nested spawn is runtime-enforced: only skywalker (full fleet allowlist) and greybeard (intern/explorer/critic) may spawn; other workers have no fleet tools. Primary omits an allowlist so plugin profiles remain reachable from the main session. From dea10c0cc38a1e75c486ded1c8d0fac13268c4dd Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 12 Sep 2026 22:38:59 -0700 Subject: [PATCH 5/5] Fix PRODUCT.md table format (oxfmt) --- docs/PRODUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/PRODUCT.md b/docs/PRODUCT.md index dab98b953..ddda8f9c8 100644 --- a/docs/PRODUCT.md +++ b/docs/PRODUCT.md @@ -162,7 +162,7 @@ The primary session is always **orchestrator** (single-agent mode is gone). Its | Primary | skywalker | | Eng | builder, explorer, counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot | | Design | draper, emil, rand | -| Docs / QA | shakespeare, testsmith, tester, gauntlet, prober | +| Docs / QA | shakespeare, testsmith, tester, gauntlet, prober | There is **no catch-all worker**. `spawn_agent` requires `agent=…` or a non-general `intent` (implement/explore/plan/review→critic); bare dispatch and `intent=general` are refused. Named `spawn_agent(agent=…)` selects a director package without requiring a plugin profile, except `skywalker` which is the primary session identity and is refused as a spawned worker. Nested spawn is runtime-enforced: only skywalker (full fleet allowlist) and greybeard (intern/explorer/critic) may spawn; other workers have no fleet tools. Primary omits an allowlist so plugin profiles remain reachable from the main session.