Skip to content

Commit ef383f6

Browse files
Add warden trust-review director for permission and plugin paths (#966)
* Add warden trust-review director for permission and plugin paths * chore: retrigger CI for rebased HEAD
1 parent e42b490 commit ef383f6

12 files changed

Lines changed: 213 additions & 20 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ Corbits Code keeps repository guidance and the closed director fleet separate:
198198
- `src/agent/directors/` — closed spawn catalog (`directorProfiles()`). Skywalker
199199
is the primary orchestrator; spawnable directors include builder, explorer,
200200
counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot, draper,
201-
emil, rand, shakespeare, testsmith, tester, gauntlet, prober, and migrator. Closed ids cannot be
201+
emil, rand, shakespeare, testsmith, tester, gauntlet, prober, migrator, and warden. Closed ids cannot be
202202
overridden by plugins or local files.
203203
- `.agents/agents/` — optional local profile additions; this directory is not
204204
required and may be absent

docs/ARCHITECTURE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ Enforcement is runtime code at the existing tool-mount point, not prompt wording
244244

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

247-
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 19 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.
247+
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 20 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.
248248

249249
**Primary**
250250

@@ -266,6 +266,7 @@ Every shipped specialist is a **director package** — a prompt-first `DirectorP
266266
| bruckheimer | Product discovery → PRODUCT/ARCHITECTURE/IMPLEMENTATION-oriented briefs | Eng plan, code |
267267
| gaasbot | Quick CTO opinion voice | Formal review gate, implement |
268268
| migrator | Reversible settings/config/session-state migrations | Live-state execution, new features |
269+
| warden | Trust review of permission/provider-auth/plugin-loader diffs | Fixes, general review |
269270

270271
**Design trio (dev perspective)**
271272

docs/PRODUCT.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,14 @@ 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 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 19 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`, `/lexicon`) tell Skywalker which directors to spawn for substantial work (`/lexicon` runs on the primary and spawns nothing); tiny/bounded edits may run on the primary.
159-
160-
| Lane | Directors |
161-
| --------- | ------------------------------------------------------------------------------------------------ |
162-
| Primary | skywalker |
163-
| Eng | builder, explorer, counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot, migrator |
164-
| Design | draper, emil, rand |
165-
| Docs / QA | shakespeare, testsmith, tester, gauntlet, prober |
158+
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 20 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`, `/lexicon`) tell Skywalker which directors to spawn for substantial work (`/lexicon` runs on the primary and spawns nothing); tiny/bounded edits may run on the primary.
159+
160+
| Lane | Directors |
161+
| --------- | -------------------------------------------------------------------------------------------------------- |
162+
| Primary | skywalker |
163+
| Eng | builder, explorer, counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot, migrator, warden |
164+
| Design | draper, emil, rand |
165+
| Docs / QA | shakespeare, testsmith, tester, gauntlet, prober |
166166

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

src/agent/directors/registry.test.ts

Lines changed: 8 additions & 7 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 19 directors", () => {
17-
expect(DIRECTOR_IDS).toHaveLength(19);
18-
expect(listDirectors()).toHaveLength(19);
16+
test("closed set has exactly 20 directors", () => {
17+
expect(DIRECTOR_IDS).toHaveLength(20);
18+
expect(listDirectors()).toHaveLength(20);
1919
for (const id of DIRECTOR_IDS) {
2020
expect(DIRECTOR_REGISTRY[id].id).toBe(id);
2121
}
@@ -116,8 +116,8 @@ describe("director registry", () => {
116116

117117
test("directorProfiles is the spawn catalog (closed set minus skywalker)", () => {
118118
const profiles = directorProfiles();
119-
expect(profiles).toHaveLength(18);
120-
expect(new Set(profiles.map((p) => p.id)).size).toBe(18);
119+
expect(profiles).toHaveLength(19);
120+
expect(new Set(profiles.map((p) => p.id)).size).toBe(19);
121121
expect(profiles.map((p) => p.id)).not.toContain("skywalker");
122122
});
123123

@@ -168,6 +168,7 @@ describe("director registry", () => {
168168
"prober",
169169
"skywalker",
170170
"gauntlet",
171+
"warden",
171172
] as const) {
172173
const allow = DIRECTOR_REGISTRY[id].tools?.allow ?? [];
173174
expect(allow).toContain("write_file");
@@ -211,11 +212,11 @@ describe("director registry", () => {
211212
expect(s.tools?.allow).toContain("write_file");
212213
expect(s.tools?.allow).toContain("edit_file");
213214
expect(s.tools?.allow).toContain("delete_file");
214-
expect(s.spawn.allowlist).toHaveLength(18);
215+
expect(s.spawn.allowlist).toHaveLength(19);
215216
});
216217

217218
// CL-6941: tier and spawn.maySpawn independently encode "may this package
218-
// spawn", hand-set across 19 files. This pins their agreement so drift
219+
// spawn", hand-set across 20 files. This pins their agreement so drift
219220
// (adding maySpawn: true without bumping tier, or vice versa) fails a test
220221
// instead of surfacing as an unexplained FleetAuthorityError at dispatch.
221222
test("tier agrees with spawn.maySpawn for every director", () => {

src/agent/directors/registry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { testerPackage } from "./tester/index.js";
1818
import { testsmithPackage } from "./testsmith/index.js";
1919
import { gauntletPackage } from "./gauntlet/index.js";
2020
import { proberPackage } from "./prober/index.js";
21+
import { wardenPackage } from "./warden/index.js";
2122
import { formatDirectorSystemPrompt } from "./identity.js";
2223
import {
2324
DIRECTOR_IDS,
@@ -64,6 +65,7 @@ export const DIRECTOR_REGISTRY: Readonly<Record<DirectorId, DirectorPackage>> =
6465
gauntlet: gauntletPackage,
6566
prober: proberPackage,
6667
migrator: migratorPackage,
68+
warden: wardenPackage,
6769
};
6870

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

src/agent/directors/skywalker/package.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe("skywalkerPackage", () => {
2828

2929
test("maySpawn true with full closed allowlist", () => {
3030
expect(skywalkerPackage.spawn.maySpawn).toBe(true);
31-
expect(skywalkerPackage.spawn.allowlist).toHaveLength(18);
31+
expect(skywalkerPackage.spawn.allowlist).toHaveLength(19);
3232
expect(skywalkerPackage.spawn.allowlist).toEqual([
3333
"builder",
3434
"explorer",
@@ -48,6 +48,7 @@ describe("skywalkerPackage", () => {
4848
"gauntlet",
4949
"prober",
5050
"migrator",
51+
"warden",
5152
]);
5253
});
5354

@@ -338,4 +339,14 @@ describe("skywalkerPackage", () => {
338339
"gauntlet = mutation-check that tests can actually fail (tree clean)",
339340
);
340341
});
342+
343+
test("systemPrompt routes trust-path diffs to warden (tiny verify-after-ship mention)", () => {
344+
const p = skywalkerPackage.systemPrompt;
345+
expect(p).toContain("warden = permission / provider-auth / plugin-loader");
346+
expect(p).toContain("only when the diff touches those paths");
347+
expect(p).toContain(
348+
"When the diff touches permission, provider-auth, or plugin-loader paths, add a warden trust review alongside critic",
349+
);
350+
expect(p).toContain("never ships fixes");
351+
});
341352
});

src/agent/directors/skywalker/package.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,15 @@ Example chains:
3131
- feature: explorer → plan → implement → critic
3232
- "why / how / is this stalled": answer yourself; at most one explorer if a single unknown blocks you
3333
34-
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, migrator.
34+
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, migrator, warden.
3535
No catch-all worker. If unsure, reclassify — do not spawn a blob agent.
3636
3737
Quick routing:
3838
- explorer = map/read codebase
3939
- counsel = ordered eng plan (no ship)
4040
- builder = ship product code + tests
4141
- critic = defects with evidence including hygiene the diff introduced (no fix)
42+
- warden = permission / provider-auth / plugin-loader trust review (no fix; only when the diff touches those paths)
4243
- greybeard = architecture judgment
4344
- neckbeard = hygiene / pedantry with receipts
4445
- tester = run the suite / repro
@@ -54,6 +55,7 @@ Quick routing:
5455
- bruckheimer = product discovery docs
5556
- intern = exact shell / mechanical ops
5657
- After every delegated builder landing → run a critic on the diff/criteria in a fresh context; when architecture is in play, add greybeard for architecture judgment
58+
- When the diff touches permission, provider-auth, or plugin-loader paths, add a warden trust review alongside critic; warden reports trust findings and never ships fixes.
5759
5860
Pass intent, do_not, report_focus, and agent when specialist.
5961
Parallelize independent lanes with spawn_agent, then idle. manage_tasks for your checklist. ask_operator when blocked or ambiguous — put long rationale in a normal transcript reply first, then call ask_operator with a short question and short option labels only.
@@ -146,7 +148,7 @@ Do not reclassify COMMUNICATION as ORCHESTRATION just to justify parallel spawn
146148
# Spawn graph
147149
148150
Skywalker = full closed set. Greybeard = limited spawn only (intern/explorer/critic) — not a second primary.
149-
You may spawn: builder, explorer, counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot, draper, emil, rand, shakespeare, testsmith, tester, gauntlet, prober, migrator.
151+
You may spawn: builder, explorer, counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot, draper, emil, rand, shakespeare, testsmith, tester, gauntlet, prober, migrator, warden.
150152
151153
When spawning, pass a typed brief. success_criteria is required for implement/review and their default directors; recommended otherwise:
152154
- intent — explore | implement | plan | review
@@ -209,6 +211,7 @@ export const skywalkerPackage: DirectorPackage = {
209211
"gauntlet",
210212
"prober",
211213
"migrator",
214+
"warden",
212215
],
213216
},
214217
modelRole: "orchestrator",

src/agent/directors/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const DIRECTOR_IDS = [
2323
"gauntlet",
2424
"prober",
2525
"migrator",
26+
"warden",
2627
] as const;
2728

2829
export type DirectorId = (typeof DIRECTOR_IDS)[number];
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { wardenPackage } from "./package.js";
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { wardenPackage } from "./package.js";
3+
4+
describe("wardenPackage", () => {
5+
test("id matches directory", () => {
6+
expect(wardenPackage.id).toBe("warden");
7+
});
8+
9+
test("systemPrompt is real, not a placeholder", () => {
10+
expect(wardenPackage.systemPrompt.length).toBeGreaterThan(0);
11+
expect(wardenPackage.systemPrompt.startsWith("Placeholder")).toBe(false);
12+
});
13+
14+
test("systemPrompt states PRIMARY INTENT", () => {
15+
expect(wardenPackage.systemPrompt).toMatch(/PRIMARY INTENT/i);
16+
});
17+
18+
test("systemPrompt identity is Warden / WardenDirector", () => {
19+
const p = wardenPackage.systemPrompt;
20+
expect(p).toMatch(/WardenDirector \(Warden\)/);
21+
expect(p).toMatch(/trust lane only/i);
22+
});
23+
24+
test("systemPrompt trigger is written trust paths only", () => {
25+
const p = wardenPackage.systemPrompt;
26+
expect(p).toMatch(/TRIGGER/i);
27+
expect(p).toMatch(/permission/);
28+
expect(p).toMatch(/provider-auth/);
29+
expect(p).toMatch(/plugin-loader/);
30+
expect(p).toMatch(/Anything else is out of lane/);
31+
expect(p).toMatch(/Do not expand into general code review/);
32+
});
33+
34+
test("systemPrompt findings lens covers the trust surface", () => {
35+
const p = wardenPackage.systemPrompt;
36+
expect(p).toMatch(/Findings lens/i);
37+
expect(p).toMatch(/grant-matching holes/i);
38+
expect(p).toMatch(/secret-guard bypass/i);
39+
expect(p).toMatch(/arktype boundary skips/i);
40+
expect(p).toMatch(/shell-policy peel gaps/i);
41+
expect(p).toMatch(/plugin trust/i);
42+
expect(p).toMatch(/blocking, should-fix, or file-for-later/i);
43+
});
44+
45+
test("systemPrompt is evidence-based findings, never-fix", () => {
46+
const p = wardenPackage.systemPrompt;
47+
expect(p).toMatch(/evidence/);
48+
expect(p).toMatch(/never fix/i);
49+
expect(p).toMatch(/Do not ship fixes/);
50+
expect(p).toMatch(/permanent tests/i);
51+
expect(p).toContain("testsmith/builder");
52+
expect(p).toContain("route to builder");
53+
});
54+
55+
test("systemPrompt is not a second critic or greybeard", () => {
56+
const p = wardenPackage.systemPrompt;
57+
expect(p).toMatch(/Do not become critic/);
58+
expect(p).toMatch(/greybeard/);
59+
});
60+
61+
test("systemPrompt has no tool-schema restatement or fake caps", () => {
62+
const p = wardenPackage.systemPrompt;
63+
expect(p).not.toMatch(/parameters?:/i);
64+
expect(p).not.toMatch(/fan-out/i);
65+
expect(p).not.toMatch(/at most \d+/i);
66+
expect(p).not.toMatch(/turn budget/i);
67+
expect(p).not.toMatch(/scheduler/i);
68+
expect(p).not.toMatch(/Prefer grep\/search_files/i);
69+
expect(p).not.toMatch(/Shell find\/rg/i);
70+
expect(p).not.toMatch(/Write tools are not mounted/i);
71+
expect(p).not.toMatch(/via run_shell/i);
72+
});
73+
74+
test("spawn.maySpawn is false", () => {
75+
expect(wardenPackage.spawn.maySpawn).toBe(false);
76+
});
77+
78+
test("tier is leaf", () => {
79+
expect(wardenPackage.tier).toBe("leaf");
80+
});
81+
82+
test("tools.allow is review surface with product writes", () => {
83+
const allow = wardenPackage.tools?.allow ?? [];
84+
expect(allow).toContain("read_file");
85+
expect(allow).not.toContain("use_skill");
86+
expect(allow).toContain("write_file");
87+
expect(allow).toContain("edit_file");
88+
expect(allow).toContain("delete_file");
89+
});
90+
91+
test("modelRole is review", () => {
92+
expect(wardenPackage.modelRole).toBe("review");
93+
});
94+
95+
test("optionalSkills order is style, philosophy, native-integration, idiot-proof", () => {
96+
expect(wardenPackage.optionalSkills).toEqual([
97+
"style",
98+
"philosophy",
99+
"native-integration",
100+
"idiot-proof",
101+
]);
102+
});
103+
104+
test("primaryIntent and outOfLane match warden lane", () => {
105+
expect(wardenPackage.primaryIntent).toBe(
106+
"Trust review of permission, provider-auth, and plugin-loader diffs; never fix product code",
107+
);
108+
expect(wardenPackage.outOfLane).toContain("implementing fixes");
109+
expect(wardenPackage.outOfLane).toContain(
110+
"general code review outside trust paths",
111+
);
112+
expect(wardenPackage.outOfLane).toContain(
113+
"architecture judgment without trust evidence",
114+
);
115+
expect(wardenPackage.outOfLane).toContain("feature design");
116+
});
117+
});

0 commit comments

Comments
 (0)