pi-crew 0.11.57 → 0.11.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased] — parent-context R5: cap census — replay layer already capped, no src change; budget pinned by test (2026-11-05)
4
+
5
+ **Investigated (R5, adaptive context diet — parent conversation context: ALREADY capped; src untouched).** Census of every layer that replays parent-session conversation into worker prompts found exactly ONE producer — `buildParentContext` (`src/extension/team-tool/context.ts:91`), called at all three spawn seams (`team-tool.ts:992`, `team-tool/run.ts:777`/`:935`) — and it bounds the output three independent ways: a 20-entry window (`branch.slice(-20)`, `:95`), per-message truncation + noise filtering (assistant messages >500c → 200c + ellipsis `:117`; file-dump/`ls`-shaped content skipped `:115`), and a TOTAL character budget `MAX_PARENT_CONTEXT_CHARS = 12_000` with most-recent-first retention — drop-oldest (`:124`-`131`). Every embed site consumes that capped string verbatim and is gated by `runtime.inheritContext === true` (default true): the child-worker task (`src/runtime/child-pi/child-pi.ts:747`-`748` effectiveTask) and the live-session subagent prompt (`src/runtime/live-session/live-session-runtime.ts:1327`-`1328` effectivePrompt); the mock-success text (`src/runtime/live-session/live-session-runtime.ts:593`) embeds the same string, diagnostics only. NOT conversation replay, verified out of scope: `loadContextFiles`/`noContextFiles` (`:811`/`:840`) gate pi context FILES (AGENTS.md class), and `formatChainHistory` is structured per-step chain summaries (own runner-capped layer). The goal judge disables inheritance outright (`goal-evaluator.ts:231`-`233`). **Verdict: NO uncapped replay layer drove the 41K→73K Σin growth** — worst-case embed ceiling ≈ 12.2Kc (12,000 budget + ~123c header + ≤38c join separators); real-user live sample (this wave's own adaptive run, measured from the worker task file): 1,388c / 9 turns — ~11% of cap. Consequence: no knob added (per forensics verdict — measure/report only); if the diet ever needs a smaller parent context, the single lever is lowering the 12_000 constant (one site, all paths). Tests: `test/unit/extension/core/build-parent-context.test.ts` +2 — budget/drop-oldest BEHAVIOR was already covered, but nothing pinned the numeric constant (every assertion imports it, so a silent value change stayed green): now `MAX_PARENT_CONTEXT_CHARS === 12_000` is pinned, plus an end-to-end bound — ~50Kc of in-window input renders ≤ budget + fixed wrapper overhead, oldest dropped, newest kept.
6
+
7
+ ## [Unreleased] — prompt-builder R2.2: task-tree scope falls back to retrieval suggestedFiles when a step declares no reads (2026-11-05)
8
+
9
+ **Changed (R2.2, adaptive context diet — reads-less steps still get a scoped tree).** R2 scoped the worker's workspace tree to a subtree around `step.reads`, but steps that declare NO `reads` (the common case — e.g. every step of the builtin `fast-fix` workflow) kept the whole-workspace walk even though retrieval had ALREADY computed per-task suggested files for the prompt (the `# Suggested files to read` block): the signal existed but only the rendered block was carried, the paths were dropped at the seam. Plumbing (`src/runtime/task-runner/prompt-builder.ts`): `StableComponents` + `CachedStableIO` gain `suggestedFiles: string[]` (the retrieval paths, relative to the task cwd), populated at the fresh-compute site (`retrieval.files.map(f => f.path)`) and returned by BOTH cache read sites — the per-run `stableComponentCache` fast path and the cross-run `stableIOCache` reconstruction, which previously rebuilt only the two strings (a hit that dropped the paths would have silently de-scoped the tree back to the whole workspace). **Cache keys unchanged** (pinned decision): the paths derive from the same `runRetrievalCycle(step.task, goal, cwd)` inputs the keys already cover — per-run key `cwd|step.task|runId` (goal is run-fixed) and the BR-06 goal-scoped cross-run io key — so no new invalidation surface; the goal-scoping of the new field is pinned by extending the BR-06 regression test. Scope derivation at the `buildTaskWorkspaceTree` call: `treeFocusPaths(step.reads, suggestedFiles)` — declared reads (non-empty strings) WIN; `reads: false` normalizes to "no reads" (same convention as task-packet.ts/task-output-context.ts) and falls through; junk entries (non-strings/whitespace) are filtered; no usable signal ⇒ `undefined` ⇒ the pre-R2.2 whole-tree fail-safe (out-of-workspace/nonexistent paths keep their NIT-2/dir-refinement semantics in the resolver — unchanged). ARCH-3 untouched: suggestions are per-STEP (cwd+step.task+goal), not per-task identity, and the tree stays in the dynamic suffix. Measured (same fast-fix smoke, knob=2000, run `team_20261105163749` vs R1.1's `team_20261105161814`): `dynamic.workspaceTree` for the reads-less `02_execute` **3,666c/917 tok → 859c/215 tok (−77%)** — retrieval's top-1 (`pi-crew/docs/real-test/reports/…`) now roots the walk (`(subtree: pi-crew/docs/real-test/reports)`); `01_explore` intentionally unchanged at 3,666c (its top-3 suggestions span `pi-crew/` + `source/babysitter/` — no common subtree ⇒ conservative whole-tree, by design); `03_verify` unchanged (retrieval returned nothing ⇒ fail-safe). Tests: `test/unit/runtime/task-runner/prompt-tree-scope.test.ts` +4 (reads-less + suggestions → scoped; both empty + junk-only → whole-tree; reads WIN over suggestions; end-to-end real-retrieval narrowing incl. the per-run cached sibling render), `test/unit/prompt-builder-cov.test.ts` +2 describe (fresh compute exposes relative paths; cross-run TTL cache-hit reconstruction returns them — mutation-proven RED when the reconstruction drops the field), `test/unit/runtime/task-runner/prompt-builder-cache-goal.test.ts` extended (BR-06 goal scoping pins the new paths array).
10
+
11
+ ## [Unreleased] — knowledge-injection R1.1: session-log section-index bound + knob-gated whole-block cap (2026-11-05)
12
+
13
+ **Fixed (R1.1, adaptive context diet — the real knowledge-block growth bug).** Forensics on the R1 baseline run (`team_20261105144923`, knob=2000): `stable.knowledge` was 12,704c, of which the session-log section-INDEX comment was 10,423c (130 entries, 82% of the block) — the index lists every session-log header in `.crew/knowledge.md` and was growing linearly with file history, UNCAPPED by `PI_CREW_KNOWLEDGE_MAX_BYTES` (the knob only bounds matched bodies + conventions; `MAX_INDEX_ENTRIES` did not exist). The index is now bounded (`MAX_INDEX_ENTRIES = 15`, `src/extension/knowledge-injection.ts`): the 15 most recent entries are listed (keep-tail in file order — the session-log is chronological, newest appended last) under a `… N earlier session-log sections omitted (index capped at 15 most recent) — use \`read\` …` counter line; the wrapper first line and `Full file:` notice are unchanged. **Default-change scope (precise):** the index bound applies with the knob UNSET — this is the growth fix, and it is the ONLY place unset output changes; when unset, injected CONTENT (conventions + matched session-log bodies + their truncation notices) stays byte-identical (pinned by full-content unit tests), and projects with ≤15 session-log sections get byte-identical index output too (the counter line only appears past the bound). The knob-gated whole-block backstop: when `PI_CREW_KNOWLEDGE_MAX_BYTES=N` is set, the FINAL joined query-path block is capped at N×3 bytes (3 knobbled budgets — conventions N + session-log N + no-query head N — plus notices/index riding on top); over-budget ⇒ tail cut + `knowledge block truncated at N*3 bytes (whole-block cap)` notice. Unset = no whole-block cap (pre-R1.1 behavior). Measured (same fast-fix smoke, knob=2000, run `team_20261105161814`): `stable.knowledge` 12,705c → **3,788c** (−70%; est-tokens 947), identical across all 3 workers — framing 275c + matched `Test env gotcha` body 1,980c byte-identical (full body, zero truncation notices, fits the 2000 budget), index 10,423c → 1,506c (15 entries + counter), whole-block cap correctly NOT triggered (3,788 < 6,000 = N×3). Tests: `test/unit/extension/core/knowledge-injection-env-cap.test.ts` +3 (bounded-index byte-exact incl. counter; unset >15-section content byte-identical; N=100 whole-block cap fires with exact 3N+notice length, unset never fires).
14
+
15
+ ## [Unreleased] — adaptive context diet R1/R2/R3 + R7 handoff-budget doc-drift sweep + R6 role-declaration drift (2026-11-04)
16
+
17
+ **Follow-up polish (2026-11-05, review round).** (1) R6 residual closed: `agents/reviewer.md` no longer advertises `bash` — decision record in-file (reviewer is read-only by design and has never been in `WRITE_ROLES`). (2) NIT-2: a top-level directory-valued focus read (`reads: ["pi-crew"]`) now scopes the task tree to that directory — refinement lives at the FS layer (`buildTaskWorkspaceTree`), `resolveTaskTreeScope` stays pure; nonexistent paths, globs, and dir+root-file mixes keep the fail-safe whole-workspace fallback (4 new tests). (3) NIT-3: `sectionCache` keys parsed sections by file identity only (mtime:size) — a mid-process `PI_CREW_KNOWLEDGE_MAX_BYTES` change re-truncates but never re-parses.
18
+
19
+ **Fixed (R7, doc drift — handoff budget unit of measure).** Every live description of `runtime.handoffBudgetTokens` / `PI_CREW_HANDOFF_BUDGET_TOKENS` still said "est-token (chars/4)"; the budget has been measured in EFFECTIVE tokens since R5-7/U10 — the CJK-aware `countTokens` estimator (`utils/token-counter.ts`, floored by the measured-output anchor) — and the old wording under-described CJK ~2-3x. Corrected at all 8 drift sites: the team-tool schema description + its input-interface comment (`src/schema/team-tool-schema.ts`), `src/schema/config-schema.ts`, `src/config/types.ts`, `src/config/config-validation.ts` (ceiling comment + parse-helper doc), the `CREW_ENV_VARS` registry doc (`src/config/env-vars.ts`), and the render-site comment (`src/runtime/task-runner/pre-execution.ts`). The tool description now also states the full truth: out-of-range values are ignored at the tool/config layer, env `PI_CREW_HANDOFF_BUDGET_TOKENS` overrides, and a RESOLVED value ≤0 or >1_000_000 disables the trim. No behavior change — comments/schema description text only (0 tests asserted the old strings). `src/runtime/model/pi-args.ts` was re-audited per packet: NO drift found there (the :568 anchor was stale).
20
+
21
+ **Added (R1, adaptive context diet).** New env `PI_CREW_KNOWLEDGE_MAX_BYTES` — a single knob capping how much `.crew/knowledge.md` content is injected, overriding EVERY knowledge-injection cap at once: the worker/query-path caps (`MAX_SESSION_LOG_BYTES` 5000 + `MAX_CONVENTIONS_BYTES` 2048) and the no-query main-session head cap (`MAX_KNOWLEDGE_HEAD_BYTES` 2000), all resolved in `src/extension/knowledge-injection.ts` via `getCrewEnv`. Decision + rationale (recorded in-code): one knob over all three caps because they are per-path bounds of the SAME injection surface — one dial for "max bytes of knowledge per agent" across host session and worker prompts; the worker path (the context-diet target) spans two caps that must move together. Semantics mirror `PI_CREW_HANDOFF_BUDGET_TOKENS`: unset/non-integer → per-path defaults with output BYTE-IDENTICAL to pre-R1 behavior (HARD RULE, pinned by full-string-equality unit tests, and the no-query content-cache key now carries the effective cap so same-process env changes can't serve a stale truncation); `N ≥ 1` → each cap becomes `N` (raising above a default is allowed); `N ≤ 0` → off-switch, NO knowledge injected. Registered in `CREW_ENV_VARS` + the C-1 forwarding decision table (kind `gap`, same class as `PI_CREW_PROMPT_SKILLS` — read in the host session and the async background runner via prompt-builder; deliberately not forwarded to workers, which receive knowledge through prompt text). New tests: `test/unit/extension/core/knowledge-injection-env-cap.test.ts` (9 cases); registry snapshot updated intentionally.
22
+
23
+ **Changed (R2, adaptive context diet — per-task workspace tree).** The worker prompt's workspace tree used to be the SAME whole-workspace walk for every sibling worker (measured 26% of a 01_discover artifact, ~0.9K est-tok/worker). The tree is now scoped to a subtree AROUND the task's working files: `resolveTaskTreeScope` (`src/runtime/workspace-tree.ts`, new, pure — no FS access) roots the walk at the deepest directory that is an ancestor of every in-workspace focus path (`step.reads`, which follow file-path semantics; a directory-valued read contributes its parent and narrows nothing), and `buildTaskWorkspaceTree` performs the fail-safe walk — no focus paths / all outside the workspace / scope directory does not exist ⇒ legacy whole-workspace tree, byte-identical fallback. Per-task content must not ride the byte-stable shared prefix (ARCH-3), so the block moved to the END of the dynamic suffix and left `StableComponents`/`stableIOCache` entirely (caching a per-task tree under the shared `(cwd, step, goal)` keys would leak one task's subtree into siblings — the internal per-root TTL cache keeps siblings sharing a scope on one walk). Header shows `(subtree: <rel>)` when scoped; breakdown key renames `stable.workspaceTree` → `dynamic.workspaceTree` with a before/after mapping comment at the render site. Tests: `test/unit/runtime/core/workspace-tree.test.ts` (+10) and new `test/unit/runtime/task-runner/prompt-tree-scope.test.ts` (placement/ARCH-3, fallback, key mapping); `test/unit/prompt-builder-cov.test.ts` dropped the stale `treeBlock`-in-stable assertion. The coalesced-path tree (`run-coalesced-task-group.ts`) intentionally keeps the legacy whole-workspace walk.
24
+
25
+ **Changed (R3, adaptive context diet — single canonical task body).** The `# Task Packet` JSON block re-embedded the full `objective` — the same text as the canonical `Task:` narrative — a second time (the "task body ×3" finding; the third copy is the parent-context embed in `child-pi.ts`, outside this seam). `renderTaskPacket(packet, { omitObjective })` now renders a pointer (`"(see the 'Task:' section below…)"`) in the prompt seam while the default (no options) keeps the full objective so non-prompt consumers and the direct unit contract (`task-handoff-template.test.ts`) are unchanged. The substitution is render-only: `TaskPacket` state and `validateTaskPacket` still see the real objective.
26
+
27
+ **Fixed (R6, role-declaration drift — docs-only).** The builtin role declarations for `oracle`/`designer`/`orchestrator` still advertised tools the runtime never granted: all three resolve `read_only` via FIND-12 default-deny (`src/runtime/role-permission.ts` — none is in `WRITE_ROLES`), the S-7 tool-tier strip (`resolveToolPolicy`, `src/agents/agent-config.ts`) removes `bash`/`edit`/`write` regardless of frontmatter, and `delegate` spawns for read-only roles are refused at call time. Frontmatter corrected to the enforced surface `read, grep, find, ls, glob, ask` (`agents/oracle.md` dropped `bash`; `agents/designer.md` dropped `bash`/`edit`/`write`; `agents/orchestrator.md` dropped `bash`/`delegate`), and the open R28-M2/R28-M3 contradictions are CLOSED as option (b) with in-file decision records; `agents/oracle.md` gains its missing R28-class enforcement note. Evidence recorded in-file: none of the three has EVER been in `WRITE_ROLES` (only write window = pre-v0.9.45 FIND-12 permissive default, closed 2026-07-20 as a privilege-escalation bug), and zero shipped workflows consume these roles, so the `writer`-precedent test for WRITE_ROLES expansion fails. No code change; effective tool surface unchanged (S-7 already stripped at runtime). Known residual (follow-up, not this task): `agents/reviewer.md` still advertises `bash` — same drift class, reviewer is a READ_ONLY role. *(Resolved 2026-11-05 in the follow-up polish entry above.)*
28
+
3
29
  ## [0.11.56] — hướng A broker-signed respond + FINDING D runner-hosted broker + R6-5 + steer.force + spawn-trace (2026-11-01)
4
30
 
5
31
  Wave theme: **the broker-host (detached runner) becomes the trust anchor of async runs** — the dispatcher dies, the broker lives; the signer is foreign, the broker signs. Four commits, every layer live-proven.
@@ -9641,3 +9667,8 @@ the repo. Two genuine wins, plus an informational CI job that never blocks.
9641
9667
  - `runWorker` slot-wait timing: acquire→fn-start >5s logs
9642
9668
  `[spawn.trace] worker-slot wait=…ms` — separates slot starvation from in-child phases
9643
9669
  in the next hang's trail.
9670
+ ## [0.11.58]
9671
+ ### Retention — stop unbounded `.crew` garbage accumulation (measured: 330 MB, 97% never pruned)
9672
+ - **FIX 1 — health snapshot lifecycle**: `removeHealthSnapshot()` + conservative orphan sweep (`sweepOrphanHealthSnapshots`, ENOENT-only absent-run proof, bounded 5k). Wired into both prune paths, ghost-run removal, `team forget`, and session-start (both crew roots). Evidence: 2322 orphaned snapshots (9.3 MB, oldest 2026-06-18) on the dev box; 2312 sweepable on first run.
9673
+ - **FIX 2 — metric label release (LABEL-GC)**: `Metric.releaseLabelValues(predicate)` on Counter/Gauge (Histogram untouched, `cardinalityEvictions` semantics preserved) + `MetricRegistry.releaseLabels()`. Heartbeat watcher releases a run's series once it leaves `running|blocked`; prune paths release on the LIVE session registry (awaits observability init first — a fresh registry's release would be a no-op lie). Fixes unbounded `crew.heartbeat.*` label cardinality (measured: 101 series / 33 runs in ONE day = 58% of every 60s snapshot line; 84 MB/day of metrics).
9674
+ - **FIX 3 — subagent record TTL**: `prunePersistedSubagentRecords()` — terminal-status records past `PI_CREW_SUBAGENT_RECORD_TTL_DAYS` (default 7, 0 disables; registered env var) are pruned at session-start, keep-50 most-recent shield, running/blocked/queued NEVER deleted (blocked drives `blockedSignalFromSubagentDir`). Evidence: 1229 records Jun–Oct; ~1117 prunable on first run.
@@ -5,7 +5,7 @@ model: false
5
5
  systemPromptMode: replace
6
6
  inheritProjectContext: true
7
7
  inheritSkills: false
8
- tools: read, grep, find, ls, glob, bash, edit, write, ask
8
+ tools: read, grep, find, ls, glob, ask
9
9
  useWhen: "UI/UX design and implementation for web and terminal"
10
10
  avoidWhen: "backend or data logic"
11
11
  cost: expensive
@@ -35,28 +35,33 @@ When handing off to implementers:
35
35
  - Visual excellence first, code perfection second — but never ship broken interactions.
36
36
  - Implement only the design scope you were given; surface adjacent issues in NOTES.
37
37
 
38
- ## Enforcement note (R28-M2 — capability/gate contradiction, documented not resolved)
38
+ ## Enforcement note (R28-M2 — resolved 2026-11-04, option (b): declarations aligned to enforcement)
39
39
 
40
- This persona is **not** in `WRITE_ROLES` (`src/runtime/role-permission.ts:18`), so
40
+ This persona is **not** in `WRITE_ROLES` (`src/runtime/role-permission.ts:20`), so
41
41
  `permissionForRole("designer")` resolves to **`read_only`** by default-deny. The
42
42
  consequences at HEAD are mechanical:
43
43
 
44
44
  - the runner injects the **read-only contract** block into your system prompt
45
- (`src/runtime/task-runner/prompt-builder.ts:29`),
45
+ ("READ-ONLY ROLE CONTRACT", `src/runtime/task-runner/prompt-builder.ts`),
46
46
  - the **plan-approval gate is bypassed** for this role
47
47
  (`src/runtime/plan-approval.ts:65`),
48
48
  - **MCP tools are cut** (`src/runtime/mcp-proxy.ts:136`),
49
49
  - a design-tier task's grandchild spawn tier is floored to `read_only`
50
- (`src/runtime/spawn-policy.ts:184`).
50
+ (`src/runtime/spawn-policy.ts:184`),
51
+ - the S-7 tool-tier strip (`resolveToolPolicy`, `src/agents/agent-config.ts`)
52
+ removes `bash`/`edit`/`write` from the allowlist regardless of frontmatter.
51
53
 
52
- Meanwhile this file's frontmatter advertises `edit`/`write`/`bash` and `useWhen`
53
- claims implementation. That mismatch is a **known open contradiction** — do not
54
- assume the read-only contract is the enforced one for *tool availability*, and do
55
- not assume edit/write are sanctioned. Until the leader signs off either
56
- (a) adding `designer` to `WRITE_ROLES` (a CODE change) or (b) dropping
57
- `edit`/`write`/`bash` from the frontmatter (a resource-metadata change), treat
58
- designer as **design + review + handoff**, and route implementation to `executor`
59
- or `test-engineer`.
54
+ **Decision record (2026-11-04):** `edit`/`write`/`bash` are dropped from this
55
+ file's frontmatter — the declaration now matches the enforced read-only
56
+ surface, closing R28-M2 as option (b). Evidence: `designer` has never been in
57
+ `WRITE_ROLES` — its only write capability was the pre-FIND-12 (v0.9.45,
58
+ 2026-07-20) permissive default that FIND-12 closed as a privilege-escalation
59
+ bug — and no shipped workflow consumes `designer`, so the `writer`-precedent
60
+ test for WRITE_ROLES expansion (real workflow-usage evidence) fails. Treat
61
+ designer as **design + review + handoff**, and route implementation to
62
+ `executor` or `test-engineer`. Option (a) — adding `designer` to `WRITE_ROLES` —
63
+ remains a CODE change requiring leader sign-off plus an actual workflow that
64
+ consumes file deliverables from this role.
60
65
 
61
66
  ## Output format
62
67
 
package/agents/oracle.md CHANGED
@@ -5,7 +5,7 @@ model: false
5
5
  systemPromptMode: replace
6
6
  inheritProjectContext: true
7
7
  inheritSkills: false
8
- tools: read, grep, find, ls, glob, bash, ask
8
+ tools: read, grep, find, ls, glob, ask
9
9
  useWhen: "architecture decisions, hard-bug hypothesis ranking, simplification review"
10
10
  avoidWhen: "routine decisions, plan hole-finding"
11
11
  cost: expensive
@@ -14,6 +14,25 @@ category: strategy
14
14
 
15
15
  You are an oracle — a strategic technical advisor. You are the ESCALATION tier for decisions that are expensive to get wrong: architecture choices, root-cause hypotheses for hard bugs, and simplification opportunities. You advise; you never implement.
16
16
 
17
+ ## Enforcement note (capability is read-only — matches the stance above)
18
+
19
+ `oracle` is in neither `READ_ONLY_ROLES` nor `WRITE_ROLES`
20
+ (`src/runtime/role-permission.ts:12,20`), so `permissionForRole("oracle")`
21
+ resolves to **`read_only`** via FIND-12 default-deny (`role-permission.ts:44`).
22
+ Enforcement is mechanical and multi-layer: the S-7 tool-tier strip
23
+ (`resolveToolPolicy`, `src/agents/agent-config.ts`) removes `bash`/`edit`/`write`
24
+ from the resolved allowlist regardless of frontmatter, `delegate` spawns are
25
+ refused (`checkSubagentSpawnPermission`, `role-permission.ts:51-60`, enforced at
26
+ the call site in `src/extension/registration/subagent-tools.ts`), and MCP is cut
27
+ (`src/runtime/mcp-proxy.ts:136`). The frontmatter tool list mirrors this enforced
28
+ surface (`bash` dropped 2026-11-04; precedent: explorer's enforced allowlist,
29
+ `src/config/role-tools.ts`). Evidence that read-only is the settled state:
30
+ oracle has never been in `WRITE_ROLES` — its only write capability was the
31
+ pre-FIND-12 (v0.9.45, 2026-07-20) permissive default for unknown roles, which
32
+ FIND-12 closed as a privilege-escalation bug — and no shipped workflow consumes
33
+ `oracle`, so the `writer`-precedent test for WRITE_ROLES expansion (real
34
+ workflow-usage evidence) fails.
35
+
17
36
  ## Boundary vs neighboring roles (routing)
18
37
  - analyst → requirement ambiguity and hidden constraints (input side)
19
38
  - critic → hole-finding in a PLAN pre-execution
@@ -5,7 +5,7 @@ model: false
5
5
  systemPromptMode: replace
6
6
  inheritProjectContext: true
7
7
  inheritSkills: false
8
- tools: read, grep, find, ls, glob, bash, ask, delegate
8
+ tools: read, grep, find, ls, glob, ask
9
9
  useWhen: "delegated end-to-end orchestration of multi-agent work"
10
10
  avoidWhen: "main session can orchestrate directly, single-task execution"
11
11
  cost: expensive
@@ -17,22 +17,35 @@ You are an orchestrator — a delegated orchestration specialist. You own a mult
17
17
  ## Canonical routing source (READ THIS)
18
18
  Lane routing tables are NOT duplicated in this body. The discovered resources guidance (teams, workflows, agents with `useWhen`/`avoidWhen`/`cost`/`category`) injected at session start — or `team action='list'` when you need it live — is the SINGLE canonical source. When this body and the discovered guidance disagree, the guidance wins; report the discrepancy rather than routing from memory.
19
19
 
20
- ## Enforcement note (R28-M3 — the `delegate` tool is DENIED for this role)
20
+ ## Enforcement note (R28-M3 — resolved 2026-11-04: read-only surface, `delegate` denied)
21
21
 
22
- This file's frontmatter advertises `delegate`, but **`orchestrator` is not in
23
- `WRITE_ROLES`** (`src/runtime/role-permission.ts:18`), so
24
- `permissionForRole("orchestrator")` is `read_only` and
22
+ **`orchestrator` is not in `WRITE_ROLES`** (`src/runtime/role-permission.ts:20`),
23
+ so `permissionForRole("orchestrator")` is `read_only` and
25
24
  `checkSubagentSpawnPermission` refuses the spawn at call time
26
- (`src/runtime/role-permission.ts:51-61`, enforced in
27
- `src/extension/registration/subagent-tools.ts:129-135`): *"Role 'orchestrator' is
28
- read-only and cannot spawn additional subagents."*
25
+ (`src/runtime/role-permission.ts:51-60`, enforced in
26
+ `src/extension/registration/subagent-tools.ts`): *"Role 'orchestrator' is
27
+ read-only and cannot spawn additional subagents."* The S-7 tool-tier strip
28
+ (`resolveToolPolicy`, `src/agents/agent-config.ts`) likewise removes `bash`/
29
+ `edit`/`write` regardless of frontmatter.
29
30
 
30
31
  Practical consequence: you **cannot** spawn subagents yourself. Route work by
31
32
  returning a routing/dispatch plan in your output (the leader or root session
32
33
  dispatches it), or by using the `team` tool actions that are not subagent spawns.
33
- Do not advertise nested orchestration you cannot perform. (De-advertising the
34
- frontmatter `delegate` entry, or adding `orchestrator` to `WRITE_ROLES`, is an
35
- open decision requiring leader sign-off — both are behaviour-affecting.)
34
+ Do not advertise nested orchestration you cannot perform.
35
+
36
+ **Decision record (2026-11-04):** `delegate` and `bash` are dropped from this
37
+ file's frontmatter — the declaration now matches enforcement, closing R28-M3 as
38
+ option (b). Evidence: `orchestrator` has never been in `WRITE_ROLES` — its only
39
+ write capability was the pre-FIND-12 (v0.9.45, 2026-07-20) permissive default
40
+ that FIND-12 closed as a privilege-escalation bug — and no shipped workflow
41
+ consumes `orchestrator`, so the `writer`-precedent test for WRITE_ROLES expansion
42
+ (real workflow-usage evidence) fails. Option (a) — adding `orchestrator` to
43
+ `WRITE_ROLES` just to re-enable `delegate` — would simultaneously reclassify
44
+ every gate keyed on `permissionForRole` (MCP cut `mcp-proxy.ts:136`,
45
+ plan-approval `plan-approval.ts:65`, grandchild spawn tier
46
+ `spawn-policy.ts:184`, read-only contract) from `read_only` to
47
+ `workspace_write`, violating least-privilege. That remains a CODE change
48
+ requiring leader sign-off.
36
49
 
37
50
  ## Workflow phases
38
51
  1. **Route** — classify each unit of work against the discovered lanes (useWhen/avoidWhen/cost). Prefer the cheapest sufficient lane. Escalation-tier lanes (expensive) only when the decision cost justifies them.
@@ -5,7 +5,7 @@ model: false
5
5
  systemPromptMode: replace
6
6
  inheritProjectContext: true
7
7
  inheritSkills: false
8
- tools: read, grep, find, ls, glob, bash, ask
8
+ tools: read, grep, find, ls, glob, ask
9
9
  useWhen: "post-execution correctness review of implemented code"
10
10
  avoidWhen: "pre-execution plan critique, security threat modeling"
11
11
  cost: cheap
@@ -46,3 +46,15 @@ REVIEW_ATTEMPT: <X of 3>
46
46
  - DO NOT bikeshed style nits at MAJOR severity.
47
47
  - DO NOT approve without running the tests (or confirming they ran successfully per dependency-context).
48
48
  - DO NOT expand scope to features not in the diff.
49
+
50
+ ## Tool surface (decision record, 2026-11-05)
51
+
52
+ `reviewer` is **not** in `WRITE_ROLES` (`src/runtime/role-permission.ts:20`), so
53
+ `permissionForRole("reviewer")` resolves **read_only** by default-deny
54
+ (FIND-12), and the S-7 tool-tier strip (`resolveToolPolicy`,
55
+ `src/agents/agent-config.ts`) removes `bash` from the allowlist regardless of
56
+ this frontmatter. `bash` is therefore dropped from the declared tools — the
57
+ declaration now matches the enforced surface (`read, grep, find, ls, glob,
58
+ ask`), same drift class as the R6 fix for oracle/designer/orchestrator.
59
+ Evidence: `reviewer` has never been in `WRITE_ROLES`; review work is
60
+ deliberately read-only ("you review, you do not rewrite").