Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,11 @@ Built-in directors with `spawn.maySpawn` may themselves call `spawn_agent` (one

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

| Tier | Who | Fleet surface |
| ------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| 1 — `orchestrator` | skywalker (primary) | Full fleet control over the whole tree. |
| 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. |
| 3 — worker | every other director (`tier: "leaf"` on the package) | No fleet verbs. Mounts `ask_director` and `submit_result`. |
| Tier | Who | Fleet surface |
| ------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| 1 — `orchestrator` | skywalker (primary) | Full fleet control over the whole tree. |
| 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. |
| 3 — worker | every other director (`tier: "leaf"` on the package) | No fleet verbs. Mounts `ask_director` and `submit_result`. |

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:

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

**Spawn matrix**

| Who | Spawn rights |
| --------------------------- | ----------------------------- |
| skywalker (primary session) | Full closed fleet |
| greybeard | intern, explorer, critic only |
| All other directors | no fleet delegation tools |
| Who | Spawn rights |
| --------------------------- | ------------------------- |
| skywalker (primary session) | Full closed fleet |
| All other directors | no fleet delegation tools |

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

Expand Down
2 changes: 1 addition & 1 deletion docs/IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ Sixteen packages under `src/agent/directors/<id>/` register in `DIRECTOR_REGISTR
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.
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.

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

### Auto Mode

Expand Down
2 changes: 1 addition & 1 deletion docs/PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ The primary session is always **orchestrator** (single-agent mode is gone). Its
| Design | draper, emil, rand |
| Docs / QA | shakespeare, testsmith, tester |

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

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

Expand Down
71 changes: 35 additions & 36 deletions src/agent/directors/greybeard/package.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, test } from "bun:test";
import { REVIEW_TOOLS } from "../tool-sets.js";
import { greybeardPackage } from "./package.js";

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

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

test("systemPrompt has Blinders against search_agents fleet discovery", () => {
test("systemPrompt has Blinders against fleet discovery and any spawn", () => {
const p = greybeardPackage.systemPrompt;
expect(p).toMatch(/Blinders/i);
expect(p).toMatch(/do not call search_agents/i);
expect(p).toMatch(/even when nested/i);
expect(p).toMatch(/not an orchestrator/i);
expect(p).toMatch(/Do not spawn builder/);
});

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

test("systemPrompt requires success_criteria when spawning critic", () => {
test("systemPrompt routes blocking unknowns to Blockers/ask_director instead of spawn", () => {
const p = greybeardPackage.systemPrompt;
expect(p).toContain("success_criteria");
expect(p).toMatch(/When spawning critic/);
expect(p).toMatch(/fail-closes without it/);
expect(p).toMatch(/intern and explorer remain optional/);
expect(p).toMatch(/When a concrete unknown blocks the judgment/);
expect(p).toMatch(/name it under Blockers/);
expect(p).toContain("ask_director");
expect(p).not.toMatch(/When spawning critic/);
expect(p).not.toMatch(/success_criteria/);
});

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

test("spawn.maySpawn is true with limited allowlist", () => {
expect(greybeardPackage.spawn.maySpawn).toBe(true);
expect(greybeardPackage.spawn.allowlist).toEqual([
"intern",
"explorer",
"critic",
]);
test("spawn.maySpawn is false (leaf)", () => {
expect(greybeardPackage.spawn.maySpawn).toBe(false);
expect(greybeardPackage.spawn.allowlist).toBeUndefined();
});

test("allowlist is only intern, explorer, critic", () => {
const allow = greybeardPackage.spawn.allowlist ?? [];
expect(allow).toHaveLength(3);
expect(allow).toContain("intern");
expect(allow).toContain("explorer");
expect(allow).toContain("critic");
expect(allow).not.toContain("implement");
expect(allow).not.toContain("builder");
expect(allow).not.toContain("skywalker");
expect(allow).not.toContain("counsel");
test("no spawn allowlist survives the leaf conversion", () => {
expect(greybeardPackage.spawn.allowlist ?? []).toHaveLength(0);
});

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

test("tier is leaf", () => {
expect(greybeardPackage.tier).toBe("leaf");
});

test("modelRole is review", () => {
expect(greybeardPackage.modelRole).toBe("review");
});
Expand All @@ -169,9 +170,7 @@ describe("greybeardPackage", () => {
});

test("primaryIntent and outOfLane match greybeard lane", () => {
expect(greybeardPackage.primaryIntent).toBe(
"Architecture judgment; limited spawn",
);
expect(greybeardPackage.primaryIntent).toBe("Architecture judgment");
expect(greybeardPackage.outOfLane).toContain("shipping product code");
expect(greybeardPackage.outOfLane).toContain(
"pedantic style-only nitpicking",
Expand Down
25 changes: 11 additions & 14 deletions src/agent/directors/greybeard/package.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
import type { DirectorPackage } from "../types.js";
import { ORCHESTRATOR_TOOLS } from "../tool-sets.js";
import { REVIEW_TOOLS } from "../tool-sets.js";

/**
* Greybeard nested orchestrator (CL-7019).
* Greybeard leaf worker (CL-7019).
* Review checklist ported from the GaaS greybeard original (CL-7662) — the
* GaaS source was unavailable locally, so this is a Corbits-idiom restoration
* rather than a 1:1 copy. Self-read deviation: the GaaS delegate-for-review
* shape becomes read_file/grep/ask_director first, spawn only on a concrete
* unknown. Architecture judgment with limited spawn — never ships product code.
* shape becomes read_file/grep/ask_director first, concluding with a verdict
* rather than a spawn. Architecture judgment as a leaf — never ships product code.
*/
export const greybeardPackage: DirectorPackage = {
id: "greybeard",
primaryIntent: "Architecture judgment; limited spawn",
primaryIntent: "Architecture judgment",
outOfLane: ["shipping product code", "pedantic style-only nitpicking"],
description: "Architecture judgment",
optionalSkills: ["style", "philosophy", "native-integration"],
tools: { allow: ORCHESTRATOR_TOOLS },
spawn: {
maySpawn: true,
allowlist: ["intern", "explorer", "critic"],
},
tools: { allow: REVIEW_TOOLS },
spawn: { maySpawn: false },
modelRole: "review",
tier: "nested-orchestrator",
tier: "leaf",
systemPrompt: `You are GreybeardDirector (Greybeard), a specialist in Corbits Code.

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.
Expand All @@ -32,7 +29,7 @@ Follow style and philosophy conventions (baked into this prompt) when reviewing

Your value is analysis, not delegation: reach the judgment yourself with
targeted reads (read_file, grep) and pointed questions (ask_director)
before considering a spawn.
before concluding.

Review checklist — work the list in order:
1. Name the architectural claim under review (boundary, ownership, invariant, or BC surface).
Expand All @@ -41,9 +38,9 @@ Review checklist — work the list in order:
4. Rank risks for long-term maintainability and backward compatibility.
5. Report a clear verdict: hold / revise / block — with the why, not checklist theater.

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

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

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

Expand Down
Loading
Loading