Skip to content

Commit eeda728

Browse files
committed
Add fleet authority tiers to the subagents package
Every director package now carries a required tier (orchestrator / nested-orchestrator / leaf), enforced in code at runSubAgent's existing tool-mount point via src/subagent/authority.ts, not by prompt wording. A Tier 3 leaf can never mount a fleet verb, and a Tier 2 nested orchestrator can only target its own descendants. task() is unchanged.
1 parent 87a2610 commit eeda728

23 files changed

Lines changed: 272 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@ parallel copies under `docs/` or `scripts/notes/`. At cut time: rename
1313

1414
## [Unreleased]
1515

16+
### Agent
17+
18+
- **Fleet authority tiers are now runtime-enforced, not documented in a prompt.**
19+
Every director package carries a required `tier` (`orchestrator` /
20+
`nested-orchestrator` / `leaf`): skywalker gets full fleet control, greybeard
21+
(and any package with `spawn.maySpawn`) is scoped to its own subtree, and every
22+
other director gets no fleet verbs at all. The check lives in code
23+
(`src/subagent/authority.ts`, wired into `runSubAgent`'s tool-mount point) so a
24+
leaf cannot obtain a fleet verb and a nested orchestrator cannot reach a
25+
sibling or ancestor — this is the foundation the next fleet-control verbs land
26+
against. `task()` is unchanged and still the only spawn verb.
27+
1628
## [0.2.107] - 2026-08-24
1729

1830
### Agent

docs/ARCHITECTURE.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,25 @@ When profiles exist (local `.agents/agents/` and/or enabled **`kind: "agent"`**
227227

228228
Profiles with `orchestrator: true` may themselves call `task` (one hop only): nested dispatch installs `task` + `search_agents` with `allowOrchestrator: false` so the tree bottoms out. Unknown `agent` ids fail closed.
229229

230+
#### Fleet authority tiers (`src/subagent/authority.ts`) (CL-6941)
231+
232+
Every director package carries a required `tier: SubagentTier` field (`src/agent/directors/types.ts`) — data on the package, never a prompt instruction:
233+
234+
| Tier | Who | Fleet surface |
235+
| ------------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
236+
| 1 — `orchestrator` | skywalker (primary) | Full fleet control over the whole tree. |
237+
| 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. |
238+
| 3 — `leaf` | every other director | No fleet verbs at all. |
239+
240+
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:
241+
242+
- **Mount-time gate.** `runSubAgent` (`src/subagent/run.ts`) resolves the spawned director's tier via `tierForDirectorId` (`src/agent/directors/registry.ts`) and calls `assertTierMayMountFleetVerb(tier, toolName)` (`src/subagent/authority.ts`) before installing `task` / `search_agents`. A Tier 3 leaf can never receive a fleet verb even if `orchestrator: true` is passed by mistake — the mount throws instead of silently installing the tool. `FLEET_VERBS` in `authority.ts` also names the not-yet-implemented verbs (`spawn_agent`, `wait_agents`, `list_agents`, `send_input`, `interrupt_agent`, `close_agent`, `resume_agent`, `read_agent_trace`, `followup_task`) so their future mount sites inherit the same gate.
243+
- **Subtree authority.** `assertCanTargetAgent(actor, targetId, nodes)` (`src/subagent/authority.ts`) implements the "root owns its tree; a child manages only its own descendants" rule: Tier 1 may target anyone, Tier 2 may target only its own descendants (walked over the same `{id, parentSessionId}` shape `SubAgentSessionStore` already tracks — no parallel tree), Tier 3 holds no fleet verbs and always fails closed. This is the authority primitive the fleet-targeting verbs land against in later child issues (CL-6942, CL-6951, CL-6945, CL-6946); CL-6941 lands the boundary and its tests, not the verbs.
244+
- `task()` is unaffected and remains the only spawn verb until the new verbs land beside it (deprecated-not-deleted per the CL-6940 epic).
245+
230246
#### Closed director fleet (`src/agent/directors/`)
231247

232-
Every shipped specialist is a **director package** — a prompt-first `DirectorPackage` (system prompt, tool envelope, spawn rights, nudge budget, report contract, `modelRole`) registered in a **closed** set of 16 ids. There is no catch-all worker: `task` without `agent` or non-general `intent`, and `task(intent="general")`, fail closed so the primary reclassifies. Nested directors with a spawn allowlist reject off-list children at `createTaskTool` (not prompt-only). Skywalker is the primary session identity: `task(agent="skywalker")` is refused, and `directorProfiles()` omits it from the spawn catalog.
248+
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: `task` without `agent` or non-general `intent`, and `task(intent="general")`, fail closed so the primary reclassifies. Nested directors with a spawn allowlist reject off-list children at `createTaskTool` (not prompt-only). Skywalker is the primary session identity: `task(agent="skywalker")` is refused, and `directorProfiles()` omits it from the spawn catalog.
233249

234250
**Primary**
235251

src/agent/directors/brand-reviewer/package.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const brandReviewerPackage: DirectorPackage = {
1616
description: "DESIGN.md brand gate",
1717
tools: { allow: DOCS_TOOLS },
1818
spawn: { maySpawn: false },
19+
tier: "leaf",
1920
nudge: { maxTurns: 40 },
2021
modelRole: "docs",
2122
systemPrompt: `You are BrandReviewerDirector, a specialist in Corbits Code.

src/agent/directors/bruckheimer/package.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const bruckheimerPackage: DirectorPackage = {
1717
description: "Product discovery specialist — user/product shape docs, not code",
1818
tools: { allow: DOCS_TOOLS },
1919
spawn: { maySpawn: false },
20+
tier: "leaf",
2021
nudge: { maxTurns: 40 },
2122
modelRole: "docs",
2223
systemPrompt: `You are BruckheimerDirector, a specialist in Corbits Code.

src/agent/directors/build/package.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const buildDirectorPackage: DirectorPackage = {
1515
optionalSkills: ["style", "philosophy", "typescript"],
1616
tools: { allow: BUILD_TOOLS },
1717
spawn: { maySpawn: false },
18+
tier: "leaf",
1819
nudge: { maxTurns: 60 },
1920
modelRole: "implement",
2021
systemPrompt: `You are BuildDirector, a specialist in Corbits Code.

src/agent/directors/critique/package.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const critiquePackage: DirectorPackage = {
1919
optionalSkills: ["style", "philosophy"],
2020
tools: { allow: REVIEW_TOOLS },
2121
spawn: { maySpawn: false },
22+
tier: "leaf",
2223
nudge: { maxTurns: 45 },
2324
modelRole: "review",
2425
systemPrompt: `You are CritiqueDirector, a specialist in Corbits Code.

src/agent/directors/draper/package.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const draperPackage: DirectorPackage = {
1818
// Read-only critique — product write tools not mounted.
1919
tools: { allow: REVIEW_TOOLS },
2020
spawn: { maySpawn: false },
21+
tier: "leaf",
2122
nudge: { maxTurns: 40 },
2223
modelRole: "review",
2324
systemPrompt: `You are DraperDirector, a specialist in Corbits Code.

src/agent/directors/emil/package.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const emilPackage: DirectorPackage = {
1818
// Critique only — write tools not mounted.
1919
tools: { allow: REVIEW_TOOLS },
2020
spawn: { maySpawn: false },
21+
tier: "leaf",
2122
nudge: { maxTurns: 40 },
2223
modelRole: "review",
2324
systemPrompt: `You are EmilDirector, a specialist in Corbits Code.

src/agent/directors/explore/package.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ FINDINGS SHAPE: Findings must be a scannable map — key paths, symbols, call fl
2424
OUT OF LANE → report Blockers naming the right director: build, plan, critique, greybeard, intern.`,
2525
tools: { allow: READ_TOOLS },
2626
spawn: { maySpawn: false },
27+
tier: "leaf",
2728
nudge: { maxTurns: 35 },
2829
modelRole: "explore",
2930
};

src/agent/directors/gaasbot/package.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const gaasbotPackage: DirectorPackage = {
1919
optionalSkills: ["philosophy"],
2020
tools: { allow: REVIEW_TOOLS },
2121
spawn: { maySpawn: false },
22+
tier: "leaf",
2223
nudge: { maxTurns: 35 },
2324
modelRole: "plan",
2425
systemPrompt: `You are GaasbotDirector, a specialist in Corbits Code.

0 commit comments

Comments
 (0)