From 74e3f3141fd60908e5dadf056d6b616c372c3a41 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 12 Sep 2026 20:57:30 -0700 Subject: [PATCH 1/2] Add mutation-check gauntlet leaf director --- README.md | 2 +- docs/ARCHITECTURE.md | 3 +- docs/PRODUCT.md | 4 +- src/agent/directors/gauntlet/index.ts | 1 + src/agent/directors/gauntlet/package.test.ts | 103 ++++++++++++++++++ src/agent/directors/gauntlet/package.ts | 92 ++++++++++++++++ src/agent/directors/registry.test.ts | 29 +++-- src/agent/directors/registry.ts | 2 + src/agent/directors/skywalker/package.test.ts | 10 +- src/agent/directors/skywalker/package.ts | 6 +- src/agent/directors/types.ts | 1 + src/agent/prompt-sizes.test.ts | 6 + 12 files changed, 245 insertions(+), 14 deletions(-) create mode 100644 src/agent/directors/gauntlet/index.ts create mode 100644 src/agent/directors/gauntlet/package.test.ts create mode 100644 src/agent/directors/gauntlet/package.ts diff --git a/README.md b/README.md index 89eadfc36..c3a7344c8 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, and tester. Closed ids cannot be + emil, rand, shakespeare, testsmith, tester, and gauntlet. 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 91470ed90..0d1c0231c 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 16 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 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. **Primary** @@ -281,6 +281,7 @@ Every shipped specialist is a **director package** — a prompt-first `DirectorP | shakespeare | Docs maintain (scribe core baked into prompt); PRODUCT/ARCHITECTURE/IMPLEMENTATION lane | | 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 | **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 d59ccc023..5ea455bc6 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 16 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 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. | Lane | Directors | | --------- | -------------------------------------------------------------------------------------- | | Primary | skywalker | | Eng | builder, explorer, counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot | | Design | draper, emil, rand | -| Docs / QA | shakespeare, testsmith, tester | +| Docs / QA | shakespeare, testsmith, tester, gauntlet | 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. diff --git a/src/agent/directors/gauntlet/index.ts b/src/agent/directors/gauntlet/index.ts new file mode 100644 index 000000000..305c3c4e8 --- /dev/null +++ b/src/agent/directors/gauntlet/index.ts @@ -0,0 +1 @@ +export { gauntletPackage } from "./package.js"; diff --git a/src/agent/directors/gauntlet/package.test.ts b/src/agent/directors/gauntlet/package.test.ts new file mode 100644 index 000000000..167f7d6cb --- /dev/null +++ b/src/agent/directors/gauntlet/package.test.ts @@ -0,0 +1,103 @@ +import { describe, expect, test } from "bun:test"; +import { gauntletPackage } from "./package.js"; + +describe("gauntletPackage", () => { + test("id matches directory / registry id", () => { + expect(gauntletPackage.id).toBe("gauntlet"); + }); + + test("systemPrompt is non-empty and not a Placeholder", () => { + expect(gauntletPackage.systemPrompt.length).toBeGreaterThan(0); + expect(gauntletPackage.systemPrompt.startsWith("Placeholder")).toBe(false); + }); + + test("systemPrompt identity is Gauntlet / GauntletDirector", () => { + const p = gauntletPackage.systemPrompt; + expect(p).toMatch(/GauntletDirector \(Gauntlet\)/); + expect(p).toContain("PRIMARY INTENT"); + expect(p).toMatch(/mutation\/vacuity lane/i); + }); + + test("systemPrompt states the mutation-check lane (break, fail, restore, pass)", () => { + const p = gauntletPackage.systemPrompt; + expect(p).toMatch(/mutation-check that tests can actually fail/i); + expect(p).toMatch(/breaking mutation/i); + expect(p).toMatch(/must FAIL/); + expect(p).toMatch(/must PASS/); + expect(p).toMatch(/byte-identical/); + expect(p).toMatch(/tree clean/); + }); + + test("systemPrompt never leaves a breaking edit in the tree", () => { + const p = gauntletPackage.systemPrompt; + expect(p).toMatch(/never leave a breaking edit in the tree/i); + expect(p).toMatch(/git status/); + expect(p).toMatch(/git diff/); + }); + + test("systemPrompt names the vacuous-test verdict", () => { + const p = gauntletPackage.systemPrompt; + expect(p).toMatch(/vacuous/); + expect(p).toMatch(/passes under mutation/i); + }); + + test("systemPrompt does not replace tester or testsmith", () => { + const p = gauntletPackage.systemPrompt; + expect(p).toMatch(/not Tester, not Testsmith/i); + expect(p).toMatch(/do not replace tester/i); + expect(p).toMatch(/route to tester/); + expect(p).toMatch(/route to[\s\S]*testsmith/i); + }); + + test("systemPrompt states Corbits report shape and done gate", () => { + const p = gauntletPackage.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 = gauntletPackage.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(gauntletPackage.spawn.maySpawn).toBe(false); + }); + + test("tier is leaf", () => { + expect(gauntletPackage.tier).toBe("leaf"); + }); + + test("tools.allow mounts the review surface (lane discipline in prompt)", () => { + const allow = gauntletPackage.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(gauntletPackage.modelRole).toBe("test"); + }); + + test("primaryIntent and outOfLane match the gauntlet lane", () => { + expect(gauntletPackage.primaryIntent).toMatch(/mutation-check/i); + expect(gauntletPackage.primaryIntent).toMatch( + /never leave a breaking edit/i, + ); + const joined = gauntletPackage.outOfLane.join(" "); + expect(joined).toMatch(/shipping product code/i); + expect(joined).toMatch(/designing test cases/i); + expect(joined).toMatch(/full suite as a pass\/fail gate/i); + }); +}); diff --git a/src/agent/directors/gauntlet/package.ts b/src/agent/directors/gauntlet/package.ts new file mode 100644 index 000000000..81aecd943 --- /dev/null +++ b/src/agent/directors/gauntlet/package.ts @@ -0,0 +1,92 @@ +import type { DirectorPackage } from "../types.js"; +import { REVIEW_TOOLS } from "../tool-sets.js"; + +/** + * Gauntlet worker (CL-7658). + * Mutation/vacuity check only — proves named tests can actually fail by + * applying one temporary breaking mutation, running the named test (must + * fail), restoring the tree byte-identical, and re-running (must pass). + * Never leaves a breaking edit in the tree; never ships product code. + */ +export const gauntletPackage: DirectorPackage = { + id: "gauntlet", + primaryIntent: + "Mutation-check that tests can actually fail: break, fail, restore, pass; never leave a breaking edit in the tree", + outOfLane: [ + "shipping product code", + "designing test cases", + "running the full suite as a pass/fail gate", + "fleet orchestration", + "architecture judgment without a mutation run", + ], + description: "Mutation/vacuity check that named tests can actually fail", + systemPrompt: `You are GauntletDirector (Gauntlet), a specialist in Corbits Code. + +PRIMARY INTENT: mutation-check that tests can actually fail. Apply one temporary breaking mutation, run the named test (it must FAIL), restore the tree byte-identical, re-run the named test (it must PASS), and leave the tree clean. A test that passes under mutation is vacuous — report it, do not fix product code to satisfy it. + +You are the mutation/vacuity lane only — not Tester, not Testsmith, not an orchestrator. You do not replace tester (runs the suite / repro as a gate) or testsmith (designs permanent test cases). Do not spawn specialists. Do not ship product code, do not design new test cases, do not run the full suite as a gate. + +BLINDERS ON: check what the brief's success_criteria name, nothing else. One named test and one minimal breaking mutation per run unless the brief names more. + +# Protocol (in order, no shortcuts) + +1. Read the named test and the code it covers. Pick ONE minimal breaking + mutation (flip a condition, drop a branch, off-by-one) that the test + should catch. +2. Apply the mutation with edit_file. Record the exact file, symbol, and + mutation so the restore is exact. +3. Run the named test with run_shell (foreground, with a timeout — never + background). It must FAIL. A pass under mutation means the test is + vacuous: stop, restore immediately, and report the vacuous test as the + finding. +4. Restore the mutation exactly (edit_file back, or git checkout the file + when the mutation is the only change). Verify with git status / git diff: + the tree must be byte-identical to before the run. +5. Re-run the named test. It must PASS on the clean tree. +6. Final verify: git status clean of mutation residue. If restore fails for + any reason, keep restoring until clean and report the struggle under + Blockers — a breaking edit left in the tree is the one unforgivable + outcome of this lane. + +# Rules + +- run_shell is for the named suite command only, foreground with timeouts. +- Never leave a breaking edit in the tree, not even briefly past the run. +- Findings are verdicts (vacuous or guarded), never fixes — route follow-ups + to builder (product fix) or testsmith (stronger cases). +- If the brief asks for anything other than a mutation/vacuity check, say so + under Blockers and stop. If product would rather hang this lane off a + restored Critic, say so under Blockers and stop. + +# Corbits report shape + +When done, stop tooling and reply with ONLY this envelope: + +## Summary +One or two sentences: the named test, the mutation, and the verdict +(guarded or vacuous). + +## Findings +The mutation (file, symbol, exact change), the fail-under-mutation output, +the pass-after-restore output, and follow-ups for builder/testsmith. + +## Blockers +Open questions, restore struggles, or assumptions. Write "None." if clear. + +## Paths +Files you mutated, tests you ran, and outputs you produced (one per line). +Write "None." if none. + +DONE GATE: stop when the named test has failed under mutation AND passed +after restore with the tree clean, OR when a vacuous test is restored-clean +and reported. Do not expand into fixes, new cases, suite gates, or +orchestration. + +OUT OF LANE: shipping product code, designing test cases (route to +testsmith), running the full suite as a gate (route to tester), fleet +orchestration, architecture judgment without a mutation run.`, + 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 475fbdc52..53c746d79 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 16 directors", () => { - expect(DIRECTOR_IDS).toHaveLength(16); - expect(listDirectors()).toHaveLength(16); + test("closed set has exactly 17 directors", () => { + expect(DIRECTOR_IDS).toHaveLength(17); + expect(listDirectors()).toHaveLength(17); 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(15); - expect(new Set(profiles.map((p) => p.id)).size).toBe(15); + expect(profiles).toHaveLength(16); + expect(new Set(profiles.map((p) => p.id)).size).toBe(16); expect(profiles.map((p) => p.id)).not.toContain("skywalker"); }); @@ -151,6 +151,7 @@ describe("director registry", () => { "bruckheimer", "rand", "skywalker", + "gauntlet", ] as const) { const allow = DIRECTOR_REGISTRY[id].tools?.allow ?? []; expect(allow).toContain("write_file"); @@ -194,11 +195,11 @@ 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(15); + expect(s.spawn.allowlist).toHaveLength(16); }); // CL-6941: tier and spawn.maySpawn independently encode "may this package - // spawn", hand-set across 16 files. This pins their agreement so drift + // spawn", hand-set across 17 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", () => { @@ -211,6 +212,20 @@ describe("director registry", () => { expect(DIRECTOR_REGISTRY.greybeard.tier).toBe("nested-orchestrator"); }); + test("gauntlet is a mutation-check leaf (CL-7658)", () => { + const r = resolveDirector({ agentId: "gauntlet" }); + expect(r.ok).toBe(true); + if (r.ok) { + expect(r.package.id).toBe("gauntlet"); + expect(r.package.tier).toBe("leaf"); + expect(r.package.spawn.maySpawn).toBe(false); + expect(r.package.modelRole).toBe("test"); + expect(r.package.primaryIntent).toMatch(/mutation-check/i); + } + expect(isDirectorId("gauntlet")).toBe(true); + expect(tierForDirectorId("gauntlet")).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 6e94407be..e4119b581 100644 --- a/src/agent/directors/registry.ts +++ b/src/agent/directors/registry.ts @@ -15,6 +15,7 @@ import { shakespearePackage } from "./shakespeare/index.js"; 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 { formatDirectorSystemPrompt } from "./identity.js"; import { DIRECTOR_IDS, @@ -58,6 +59,7 @@ export const DIRECTOR_REGISTRY: Readonly> = shakespeare: shakespearePackage, testsmith: testsmithPackage, tester: testerPackage, + gauntlet: gauntletPackage, }; export function isDirectorId(value: unknown): value is DirectorId { diff --git a/src/agent/directors/skywalker/package.test.ts b/src/agent/directors/skywalker/package.test.ts index d917eb682..b651d3f0a 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(15); + expect(skywalkerPackage.spawn.allowlist).toHaveLength(16); expect(skywalkerPackage.spawn.allowlist).toEqual([ "builder", "explorer", @@ -45,6 +45,7 @@ describe("skywalkerPackage", () => { "shakespeare", "testsmith", "tester", + "gauntlet", ]); }); @@ -316,4 +317,11 @@ describe("skywalkerPackage", () => { expect(p).not.toContain("gh pr create"); expect(p).not.toContain("gh pr review"); }); + + test("systemPrompt routes mutation-checks to gauntlet (tiny verify-after-ship mention)", () => { + const p = skywalkerPackage.systemPrompt; + expect(p).toContain( + "gauntlet = mutation-check that tests can actually fail (tree clean)", + ); + }); }); diff --git a/src/agent/directors/skywalker/package.ts b/src/agent/directors/skywalker/package.ts index 562f9b2c8..6a8b778a6 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. +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. No catch-all worker. If unsure, reclassify — do not spawn a blob agent. Quick routing: @@ -43,6 +43,7 @@ Quick routing: - neckbeard = hygiene / pedantry with receipts - tester = run the suite / repro - testsmith = design permanent test cases +- gauntlet = mutation-check that tests can actually fail (tree clean) - 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. +You may spawn: builder, explorer, counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot, draper, emil, rand, shakespeare, testsmith, tester, gauntlet. When spawning, pass a typed brief. success_criteria is required for implement/review and their default directors; recommended otherwise: - intent — explore | implement | plan | review @@ -204,6 +205,7 @@ export const skywalkerPackage: DirectorPackage = { "shakespeare", "testsmith", "tester", + "gauntlet", ], }, modelRole: "orchestrator", diff --git a/src/agent/directors/types.ts b/src/agent/directors/types.ts index a77e51b33..06b608c7b 100644 --- a/src/agent/directors/types.ts +++ b/src/agent/directors/types.ts @@ -20,6 +20,7 @@ export const DIRECTOR_IDS = [ "shakespeare", "testsmith", "tester", + "gauntlet", ] 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 9ac3269b1..d4ffc9b2f 100644 --- a/src/agent/prompt-sizes.test.ts +++ b/src/agent/prompt-sizes.test.ts @@ -33,6 +33,9 @@ const CHAR_BUDGET: Record = { shakespeare: 54700, testsmith: 16200, tester: 13900, + // CL-7658: grok family is the max (13990 chars); budget = measured + + // 2000 allowance, ceiling to 100. + gauntlet: 16000, }; const BYTE_BUDGET: Record = { @@ -54,6 +57,9 @@ const BYTE_BUDGET: Record = { shakespeare: 55900, testsmith: 17200, tester: 15000, + // CL-7658: grok family is the max (14050 bytes); budget = measured + + // 3000 allowance, ceiling to 100. + gauntlet: 17100, }; function budgetMessage( From b3b619486b38ba6f6204cb459625ed45bb89c638 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 12 Sep 2026 22:13:39 -0700 Subject: [PATCH 2/2] Yield gauntlet 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/gauntlet/package.test.ts | 14 +++++++++----- src/agent/directors/gauntlet/package.ts | 19 ++----------------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/agent/directors/gauntlet/package.test.ts b/src/agent/directors/gauntlet/package.test.ts index 167f7d6cb..d95d7d71e 100644 --- a/src/agent/directors/gauntlet/package.test.ts +++ b/src/agent/directors/gauntlet/package.test.ts @@ -49,12 +49,16 @@ describe("gauntletPackage", () => { expect(p).toMatch(/route to[\s\S]*testsmith/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 = gauntletPackage.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(/fail-under-mutation output/i); + expect(p).toMatch(/pass-after-restore output/i); expect(p).toMatch(/DONE GATE/i); expect(p).toMatch(/BLINDERS ON/i); expect(p).toContain("success_criteria"); diff --git a/src/agent/directors/gauntlet/package.ts b/src/agent/directors/gauntlet/package.ts index 81aecd943..f5907cb69 100644 --- a/src/agent/directors/gauntlet/package.ts +++ b/src/agent/directors/gauntlet/package.ts @@ -58,24 +58,9 @@ BLINDERS ON: check what the brief's success_criteria name, nothing else. One nam under Blockers and stop. If product would rather hang this lane off a restored Critic, say so under Blockers and stop. -# Corbits report shape +# Report -When done, stop tooling and reply with ONLY this envelope: - -## Summary -One or two sentences: the named test, the mutation, and the verdict -(guarded or vacuous). - -## Findings -The mutation (file, symbol, exact change), the fail-under-mutation output, -the pass-after-restore output, and follow-ups for builder/testsmith. - -## Blockers -Open questions, restore struggles, or assumptions. Write "None." if clear. - -## Paths -Files you mutated, tests you ran, and outputs you 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: the mutation (file, symbol, exact change), the fail-under-mutation output, the pass-after-restore output, and follow-ups for builder/testsmith. DONE GATE: stop when the named test has failed under mutation AND passed after restore with the tree clean, OR when a vacuous test is restored-clean