Skip to content

Commit ca81eb9

Browse files
Convert greybeard to a leaf director; only Skywalker may spawn (#958)
* Convert greybeard to a leaf director; only Skywalker may spawn CL-7670 converts the limited spawn CL-7662 kept with a recorded reason into a leaf: greybeard loses the fleet verbs and the nested-orchestrator tier, keeping its checklists, self-read, and provenance exactly as-is. The registry test now pins Skywalker as the only closed director with spawn permission, and stale nested-orchestrator references follow suit. * Fix greybeard leaf wording coherence
1 parent 8a1e138 commit ca81eb9

13 files changed

Lines changed: 100 additions & 100 deletions

File tree

docs/ARCHITECTURE.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,11 @@ Built-in directors with `spawn.maySpawn` may themselves call `spawn_agent` (one
230230

231231
Every director package carries a required `tier: SubagentTier` field (`src/agent/directors/types.ts`) — data on the package, never a prompt instruction:
232232

233-
| Tier | Who | Fleet surface |
234-
| ------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
235-
| 1 — `orchestrator` | skywalker (primary) | Full fleet control over the whole tree. |
236-
| 2 — `nested-orchestrator` | greybeard, or any package with `spawn.maySpawn` | Same fleet surface, scoped to its own subtree: may manage only its own descendants, never a sibling or ancestor. |
237-
| 3 — worker | every other director (`tier: "leaf"` on the package) | No fleet verbs. Mounts `ask_director` and `submit_result`. |
233+
| Tier | Who | Fleet surface |
234+
| ------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
235+
| 1 — `orchestrator` | skywalker (primary) | Full fleet control over the whole tree. |
236+
| 2 — `nested-orchestrator` | any package with `spawn.maySpawn` (no closed director uses this tier today) | Same fleet surface, scoped to its own subtree: may manage only its own descendants, never a sibling or ancestor. |
237+
| 3 — worker | every other director (`tier: "leaf"` on the package) | No fleet verbs. Mounts `ask_director` and `submit_result`. |
238238

239239
Enforcement is runtime code at the existing tool-mount point, not prompt wording — this is the fix for four prior mechanisms (`writePaths`, `report.requiredSections`, a `--config` comment, the thrash matcher) that were documented-as-enforced while enforcing nothing:
240240

@@ -262,7 +262,7 @@ Every shipped specialist is a **director package** — a prompt-first `DirectorP
262262
| counsel | Eng change plan (steps, paths, tests, risks) | Arch gate, product discovery, code |
263263
| intern | Mechanical commands only | Ambiguous or product-design work |
264264
| critic | Evidence-based code review | Fixing product code |
265-
| greybeard | Architecture/approach review of plans/docs; limited spawn | Authoring eng plans, implementing |
265+
| greybeard | Architecture/approach review of plans/docs | Authoring eng plans, implementing |
266266
| neckbeard | Adversarial hygiene / refactor stress | Real review substitute |
267267
| bruckheimer | Product discovery → PRODUCT/ARCHITECTURE/IMPLEMENTATION-oriented briefs | Eng plan, code |
268268
| gaasbot | Quick CTO opinion voice | Formal review gate, implement |
@@ -299,11 +299,10 @@ Every shipped specialist is a **director package** — a prompt-first `DirectorP
299299

300300
**Spawn matrix**
301301

302-
| Who | Spawn rights |
303-
| --------------------------- | ----------------------------- |
304-
| skywalker (primary session) | Full closed fleet |
305-
| greybeard | intern, explorer, critic only |
306-
| All other directors | no fleet delegation tools |
302+
| Who | Spawn rights |
303+
| --------------------------- | ------------------------- |
304+
| skywalker (primary session) | Full closed fleet |
305+
| All other directors | no fleet delegation tools |
307306

308307
**Tool envelopes** prefer small `tools.allow` mounts over deny-everything. Shipped docs/design directors (shakespeare, rand, bruckheimer) mount write tools with no path-level lock. Lane routing is spawn policy (shakespeare = P/A/I docs, rand = DESIGN.md, bruckheimer = product discovery), not a file lock. There is no static per-package write-path declaration (CL-6952 removed it — no shipped director ever set one); instead spawn_agent records, without blocking, when two concurrently running dispatches land on the same cwd (see `intervention-log.ts`'s `conflict` class).
309308

docs/IMPLEMENTATION.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ Sixteen packages under `src/agent/directors/<id>/` register in `DIRECTOR_REGISTR
170170
6. There is no static write-path declaration on packages or profiles (CL-6952 removed it — no shipped director ever set one). Instead, `agent-fleet.ts` tracks each running dispatch by cwd; a new mutating dispatch that lands on the same cwd as a live mutating peer (`pending_init`/`running`, and not a declared read-only `modelRole` of `explore`/`plan`/`review`/`test`) records at most one `concurrent-lane-overlap` entry per cwd wave in `intervention-log.ts` (class `conflict`). The wave flag clears when no live mutating writer remains for that cwd. Terminal-but-unsettled lanes (for example cancelled with `finishedAt` set while the run promise has not reached `finally`) are pruned from the map and do not warn. This is advisory only — it never blocks the spawn, since cwd overlap does not prove the two lanes touch the same files.
171171
7. Spawn effort: pin > package `modelRole` default (`defaultEffortForDirector`; intern=low; plan/review/orchestrator=high; implement/explore/docs/test=medium) > orchestrator/worker binary > parent inheritance. Optional skills are listed in the identity header for awareness; workers do not mount `use_skill` (guidance is baked into package system prompts). Primary mounts `use_skill` for its own skill list.
172172

173-
Intent defaults: `intent=implement` → director `builder`; `explore``explorer`; `plan``counsel`; `review``critic`; general → error. Spawn: skywalker full fleet; greybeard intern/explorer/critic only; all other directors mount no fleet tools. Live `<env>` injects cwd, platform, arch, runtime, date, and git status on every chat and worker prompt.
173+
Intent defaults: `intent=implement` → director `builder`; `explore``explorer`; `plan``counsel`; `review``critic`; general → error. Spawn: skywalker full fleet; all other directors, including greybeard, mount no fleet tools. Live `<env>` injects cwd, platform, arch, runtime, date, and git status on every chat and worker prompt.
174174

175175
### Auto Mode
176176

docs/PRODUCT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ The primary session is always **orchestrator** (single-agent mode is gone). Its
164164
| Design | draper, emil, rand |
165165
| Docs / QA | shakespeare, testsmith, tester, gauntlet, prober |
166166

167-
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.
167+
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) may spawn; all other workers, including greybeard, have no fleet tools. Primary omits an allowlist so plugin profiles remain reachable from the main session.
168168

169169
Corbits Code fans work out to short-lived **fleet agents** — workers with their own loop, tools, and checklist — while the primary session stays focused.
170170

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

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { describe, expect, test } from "bun:test";
2+
import { REVIEW_TOOLS } from "../tool-sets.js";
23
import { greybeardPackage } from "./package.js";
34

45
describe("greybeardPackage", () => {
@@ -72,13 +73,16 @@ describe("greybeardPackage", () => {
7273
expect(p).not.toMatch(/spawn a (greybeard|reviewer)/i);
7374
});
7475

75-
test("systemPrompt allows limited spawn without fake caps or scheduler language", () => {
76+
test("systemPrompt is a leaf worker: no spawn path, no fake caps or scheduler language", () => {
7677
const p = greybeardPackage.systemPrompt;
77-
expect(p).toMatch(/intern/);
78-
expect(p).toMatch(/explorer/);
79-
expect(p).toMatch(/critic/);
78+
expect(p).toMatch(/you cannot spawn/i);
79+
expect(p).toMatch(/leaf worker/i);
80+
expect(p).toMatch(/no fleet verbs are mounted/i);
8081
expect(p).toMatch(/Prefer doing the review yourself/i);
8182
expect(p).toMatch(/Do not invent numeric spawn caps|not a soft ladder/i);
83+
expect(p).not.toMatch(/Spawn only when/i);
84+
expect(p).not.toMatch(/Package spawn rules/i);
85+
expect(p).not.toMatch(/Spawn then idle/i);
8286
expect(p).not.toMatch(/at most \d+/i);
8387
expect(p).not.toMatch(/spawn at most one/i);
8488
expect(p).not.toMatch(/parallel diagnostic fleet/i);
@@ -87,11 +91,12 @@ describe("greybeardPackage", () => {
8791
expect(p).not.toMatch(/fan-out/i);
8892
});
8993

90-
test("systemPrompt has Blinders against search_agents fleet discovery", () => {
94+
test("systemPrompt has Blinders against fleet discovery and any spawn", () => {
9195
const p = greybeardPackage.systemPrompt;
9296
expect(p).toMatch(/Blinders/i);
9397
expect(p).toMatch(/do not call search_agents/i);
94-
expect(p).toMatch(/even when nested/i);
98+
expect(p).toMatch(/not an orchestrator/i);
99+
expect(p).toMatch(/Do not spawn builder/);
95100
});
96101

97102
test("systemPrompt guides quality without enforcement theater", () => {
@@ -108,54 +113,50 @@ describe("greybeardPackage", () => {
108113
expect(p).not.toMatch(/not Build\b/);
109114
});
110115

111-
test("systemPrompt requires success_criteria when spawning critic", () => {
116+
test("systemPrompt routes blocking unknowns to Blockers/ask_director instead of spawn", () => {
112117
const p = greybeardPackage.systemPrompt;
113-
expect(p).toContain("success_criteria");
114-
expect(p).toMatch(/When spawning critic/);
115-
expect(p).toMatch(/fail-closes without it/);
116-
expect(p).toMatch(/intern and explorer remain optional/);
118+
expect(p).toMatch(/When a concrete unknown blocks the judgment/);
119+
expect(p).toMatch(/name it under Blockers/);
120+
expect(p).toContain("ask_director");
121+
expect(p).not.toMatch(/When spawning critic/);
122+
expect(p).not.toMatch(/success_criteria/);
117123
});
118124

119125
test("systemPrompt forbids spawning builder and names off-list directors", () => {
120126
expect(greybeardPackage.systemPrompt).toContain("Do not spawn builder");
121127
expect(greybeardPackage.systemPrompt).not.toMatch(/\bspawn implement\b/);
122128
});
123129

124-
test("spawn.maySpawn is true with limited allowlist", () => {
125-
expect(greybeardPackage.spawn.maySpawn).toBe(true);
126-
expect(greybeardPackage.spawn.allowlist).toEqual([
127-
"intern",
128-
"explorer",
129-
"critic",
130-
]);
130+
test("spawn.maySpawn is false (leaf)", () => {
131+
expect(greybeardPackage.spawn.maySpawn).toBe(false);
132+
expect(greybeardPackage.spawn.allowlist).toBeUndefined();
131133
});
132134

133-
test("allowlist is only intern, explorer, critic", () => {
134-
const allow = greybeardPackage.spawn.allowlist ?? [];
135-
expect(allow).toHaveLength(3);
136-
expect(allow).toContain("intern");
137-
expect(allow).toContain("explorer");
138-
expect(allow).toContain("critic");
139-
expect(allow).not.toContain("implement");
140-
expect(allow).not.toContain("builder");
141-
expect(allow).not.toContain("skywalker");
142-
expect(allow).not.toContain("counsel");
135+
test("no spawn allowlist survives the leaf conversion", () => {
136+
expect(greybeardPackage.spawn.allowlist ?? []).toHaveLength(0);
143137
});
144138

145-
test("tools.allow is orchestrator surface with product writes but without fleet discovery", () => {
139+
test("tools.allow is the review surface without fleet verbs", () => {
146140
const allow = greybeardPackage.tools?.allow ?? [];
141+
expect([...allow]).toEqual([...REVIEW_TOOLS]);
147142
expect(allow).not.toContain("task");
148-
expect(allow).toContain("spawn_agent");
149-
// CL-7678: nested orchestrators collect through mailbox mail; wait_agents
150-
// is exec-primary opt-in, so it stays off the Greybeard allow.
143+
expect(allow).not.toContain("spawn_agent");
151144
expect(allow).not.toContain("wait_agents");
152-
// CL-7051: search_agents is Skywalker-only — nested directors spawn from allowlist.
145+
// CL-7051: search_agents is Skywalker-only — leaves never mount discovery.
153146
expect(allow).not.toContain("search_agents");
147+
expect(allow).not.toContain("list_agents");
148+
expect(allow).not.toContain("send_input");
149+
expect(allow).toContain("read_file");
150+
expect(allow).toContain("grep");
154151
expect(allow).toContain("write_file");
155152
expect(allow).toContain("edit_file");
156153
expect(allow).toContain("delete_file");
157154
});
158155

156+
test("tier is leaf", () => {
157+
expect(greybeardPackage.tier).toBe("leaf");
158+
});
159+
159160
test("modelRole is review", () => {
160161
expect(greybeardPackage.modelRole).toBe("review");
161162
});
@@ -169,9 +170,7 @@ describe("greybeardPackage", () => {
169170
});
170171

171172
test("primaryIntent and outOfLane match greybeard lane", () => {
172-
expect(greybeardPackage.primaryIntent).toBe(
173-
"Architecture judgment; limited spawn",
174-
);
173+
expect(greybeardPackage.primaryIntent).toBe("Architecture judgment");
175174
expect(greybeardPackage.outOfLane).toContain("shipping product code");
176175
expect(greybeardPackage.outOfLane).toContain(
177176
"pedantic style-only nitpicking",

src/agent/directors/greybeard/package.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
11
import type { DirectorPackage } from "../types.js";
2-
import { ORCHESTRATOR_TOOLS } from "../tool-sets.js";
2+
import { REVIEW_TOOLS } from "../tool-sets.js";
33

44
/**
5-
* Greybeard nested orchestrator (CL-7019).
5+
* Greybeard leaf worker (CL-7019).
66
* Review checklist ported from the GaaS greybeard original (CL-7662) — the
77
* GaaS source was unavailable locally, so this is a Corbits-idiom restoration
88
* rather than a 1:1 copy. Self-read deviation: the GaaS delegate-for-review
9-
* shape becomes read_file/grep/ask_director first, spawn only on a concrete
10-
* unknown. Architecture judgment with limited spawn — never ships product code.
9+
* shape becomes read_file/grep/ask_director first, concluding with a verdict
10+
* rather than a spawn. Architecture judgment as a leaf — never ships product code.
1111
*/
1212
export const greybeardPackage: DirectorPackage = {
1313
id: "greybeard",
14-
primaryIntent: "Architecture judgment; limited spawn",
14+
primaryIntent: "Architecture judgment",
1515
outOfLane: ["shipping product code", "pedantic style-only nitpicking"],
1616
description: "Architecture judgment",
1717
optionalSkills: ["style", "philosophy", "native-integration"],
18-
tools: { allow: ORCHESTRATOR_TOOLS },
19-
spawn: {
20-
maySpawn: true,
21-
allowlist: ["intern", "explorer", "critic"],
22-
},
18+
tools: { allow: REVIEW_TOOLS },
19+
spawn: { maySpawn: false },
2320
modelRole: "review",
24-
tier: "nested-orchestrator",
21+
tier: "leaf",
2522
systemPrompt: `You are GreybeardDirector (Greybeard), a specialist in Corbits Code.
2623
2724
PRIMARY INTENT: architecture judgment. Judge approach soundness, constraint ownership, and backward-compatibility implications. Teach what holds and what does not. Do not fix or ship product code.
@@ -32,7 +29,7 @@ Follow style and philosophy conventions (baked into this prompt) when reviewing
3229
3330
Your value is analysis, not delegation: reach the judgment yourself with
3431
targeted reads (read_file, grep) and pointed questions (ask_director)
35-
before considering a spawn.
32+
before concluding.
3633
3734
Review checklist — work the list in order:
3835
1. Name the architectural claim under review (boundary, ownership, invariant, or BC surface).
@@ -41,9 +38,9 @@ Review checklist — work the list in order:
4138
4. Rank risks for long-term maintainability and backward compatibility.
4239
5. Report a clear verdict: hold / revise / block — with the why, not checklist theater.
4340
44-
Spawn only when a concrete unknown blocks that judgment. Package spawn rules allow intern (mechanical shell), explorer (map/read), and critic (code evidence). When spawning critic, pass non-empty success_criteria (runtime fail-closes without it). intern and explorer remain optional. Prefer doing the review yourself with mounted read/search tools. Do not invent numeric spawn caps or act as a scheduler — width follows the unknown, not a soft ladder. Spawn then idle; reports arrive as mailbox mail — do not poll.
41+
Reach the judgment yourself and report it — you cannot spawn. Prefer doing the review yourself with mounted read/search tools. You are a leaf worker: no fleet verbs are mounted, so there is no delegation path. When a concrete unknown blocks the judgment, name it under Blockers (or ask the parent with ask_director) instead of delegating. Do not invent numeric spawn caps or act as a scheduler.
4542
46-
Blinders: do not call search_agents to discover the fleet (even when nested). You already know the limited spawn set; stay inside it. Do not spawn builder, counsel, skywalker, or other directors outside the allowlist.
43+
Blinders: do not call search_agents to discover the fleet. Do not spawn builder, counsel, skywalker, or any other director. You are a leaf worker, not an orchestrator — delegation is the primary's job.
4744
4845
Guide quality — advise what good architecture looks like for this change. Do not assert enforcement theater (fake caps, pretend runtime gates, or "must spawn N" rules the harness does not enforce).
4946

0 commit comments

Comments
 (0)