pi-legion 4.0.0 → 5.0.1

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +248 -1
  2. package/README.md +101 -44
  3. package/agents/centurion.md +20 -16
  4. package/agents/scout.md +3 -1
  5. package/agents/worker.md +5 -7
  6. package/docs/contracts/declarative-phase-machine-v5.md +128 -0
  7. package/docs/contracts/extension-inheritance-wiring-v1.md +428 -0
  8. package/docs/contracts/subagent-renewal-v1.md +260 -0
  9. package/docs/headless-child-contract.md +6 -3
  10. package/docs/inprocess-supervisor.md +70 -24
  11. package/docs/legion-v2-contract.md +9 -0
  12. package/docs/spawn-mode-visibility-v1.md +4 -1
  13. package/index.ts +598 -44
  14. package/package.json +2 -2
  15. package/src/agents/agents.ts +293 -36
  16. package/src/agents/overrides.ts +3 -1
  17. package/src/agents/settings.ts +6 -18
  18. package/src/extension/child-guard.ts +25 -1
  19. package/src/extension/legion-tools.ts +66 -21
  20. package/src/extension/notify.ts +136 -4
  21. package/src/extension/playbook.ts +2 -2
  22. package/src/extension/runtime.ts +56 -1
  23. package/src/extension/summary.ts +13 -4
  24. package/src/extension/timing.ts +8 -4
  25. package/src/legion/phase-conditions.ts +164 -0
  26. package/src/legion/phase-machine.ts +403 -0
  27. package/src/legion/state-machine.ts +219 -115
  28. package/src/runs/args.ts +49 -18
  29. package/src/runs/extension-inheritance.ts +463 -0
  30. package/src/runs/orchestrator.ts +137 -44
  31. package/src/runs/progress-signal.ts +137 -0
  32. package/src/runs/store-lookup.ts +270 -0
  33. package/src/shared/types.ts +88 -3
  34. package/src/supervisor/child-context.ts +19 -0
  35. package/src/supervisor/inprocess-supervisor.ts +44 -5
  36. package/src/supervisor/routing-supervisor.ts +16 -1
  37. package/src/supervisor/rpc-supervisor.ts +16 -2
  38. package/src/tui/tree-view.ts +56 -0
  39. package/src/tui/viz/palette.ts +0 -3
  40. package/agents/advisor.md +0 -110
  41. package/agents/designer.md +0 -87
  42. package/agents/planner.md +0 -53
  43. package/agents/prototype.md +0 -72
  44. package/agents/reviewer.md +0 -113
package/CHANGELOG.md CHANGED
@@ -11,8 +11,254 @@ summary so you can see **2.x vs 0.16** without leaving the tree.
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [5.0.1] — 2026-10-27
15
+
16
+ Patch release. **This is the release where 5.0.0's extension-inheritance policy
17
+ starts actually taking effect** — upgrading changes what a child loads, so read
18
+ the first entry before rolling it out to a fleet.
19
+
20
+ ### Fixed
21
+
22
+ - **Host extensions are inherited again (#280).** 5.0.0 declared "children inherit
23
+ every host extension by default" and documented the narrowing knobs, but the
24
+ policy never reached the spawn path: `discoverHostExtensions()` had no callers,
25
+ the sync getter it fed therefore returned `undefined`, and
26
+ `inheritedExtensions` was always handed an empty host list. Children kept
27
+ getting exactly the card's own `extensions` plus the child-guard — the
28
+ `--no-extensions` behaviour #280 set out to remove, under a new description.
29
+
30
+ Discovery is now warmed once per process before a child is spawned. The cache
31
+ lives on `globalThis` under `Symbol.for` rather than in a module-level `let`,
32
+ because pi re-evaluates an extension module with `moduleCache: false` for a
33
+ child's `--extension` load: a module-level variable is invisible across those
34
+ instances, so a child spawned from a re-loaded instance would have inherited
35
+ nothing. The same escape hatch already backs `child-context.ts`,
36
+ `inprocess-depth.ts` and `admission-queue.ts`.
37
+
38
+ The warm-up sits in each backend's `spawnChild` (and in `RoutingSupervisor`,
39
+ which sees both). It is deliberately not in `Orchestrator.launch`: that fast
40
+ path may not gain an await before the synchronous admission block
41
+ (`launch-admission-queue-v1` §13.7).
42
+
43
+ - **`PI_LEGION_CHILD_INHERIT_EXTENSIONS=0` works on the inherited half (#280).**
44
+ `collectChildExtensions` accepted an `env` argument and then did not forward it
45
+ to `inheritedExtensions`, so the operator kill-switch applied everywhere except
46
+ the set it was meant to disable. Harmless while that set was always empty;
47
+ wrong the moment it is not.
48
+
49
+ - **A manifest that names a directory no longer breaks rpc children (#280).**
50
+ `pi.extensions: ["./extensions"]` (what `pi-diff-review` ships) resolved to the
51
+ directory itself, and `pi -e <dir>` exits 1 with `Failed to load extension:
52
+ Cannot find module` — so wiring inheritance up would have failed every rpc
53
+ child of a session with that package installed. Declared entries now resolve the
54
+ way pi's own loader does: a file as-is, a directory through its own
55
+ `pi.extensions`, else its `index.ts` / `index.js`, else its `*.ts` / `*.js`
56
+ files. An entry that expands to nothing is dropped instead of leaving an
57
+ unloadable path.
58
+
59
+ - **`extensions/<name>/index.ts` is discovered (#280).** The first sweep collected
60
+ only files directly inside a declared directory, so pi's documented
61
+ subdirectory layout resolved to nothing and the package silently contributed
62
+ zero extensions. The sweep now matches pi's `collectAutoExtensionEntries`:
63
+ `.`-prefixed names and `node_modules` are skipped, `*.ts` / `*.js` files are
64
+ collected, and a subdirectory is resolved as a package of its own (one level,
65
+ as pi does).
66
+
67
+ - **A failing `pi list` is retried, and costs one probe per spawn (#280).** The
68
+ old shape cached the failure as `[]` permanently, so one transient error
69
+ disabled inheritance until the process restarted. Failures now stamp
70
+ `failedAt` and short-circuit for `FAILURE_TTL_MS` (5 s), which keeps a
71
+ three-point warm-up from probing twice on the same spawn while still retrying
72
+ once the window passes.
73
+
74
+ - **A direct `RpcSupervisor` gets the same warm-up (#280).** `api.ts` documents
75
+ `new Orchestrator({ supervisor: new RpcSupervisor(), … })`, which never touches
76
+ `RoutingSupervisor`; warming only there left that path inheriting nothing.
77
+
78
+ ### Notes
79
+
80
+ - **Cost now matches what 5.0.0 promised.** Inheriting everything includes
81
+ pi-lens (a ~5.7 MB bundle that starts an LSP stack) on every child. In-process
82
+ children share the host process, so this is host memory, not a new process.
83
+ Add `excludeExtensions: [pi-lens]` to a card (or
84
+ `PI_LEGION_CHILD_INHERIT_EXTENSIONS=0` globally) if a fleet does not need it —
85
+ before this release those knobs were inert.
86
+ - **This package is never inherited into its own children.** In the source-tree
87
+ layout `pi list` and the child-guard name different files for the same code, so
88
+ dedupe could not collapse them and a child loaded two legion factories
89
+ (`Tool "subagent" conflicts with …`). The guard is still appended
90
+ unconditionally at the end.
91
+ - Doctor output and the README described the intended behaviour, which is now the
92
+ actual behaviour; they were not rewritten for this release.
93
+
94
+ ## [5.0.0] — 2026-10-26
95
+
96
+ Major because upgrading **can break an existing role card**: unknown frontmatter
97
+ is now a hard error, and `spawnMode` / `placement` are gone. A card carrying
98
+ either fails to load with a message naming the offending field. That is the point
99
+ — the previous behaviour was to silently ignore them, so a setting that looked
100
+ active did nothing.
101
+
102
+ **Before upgrading**, remove `spawnMode:` and `placement:` from every card under
103
+ `~/.pi/agent/agents/` and `.pi/agents/`. Nothing else about a card needs to
104
+ change.
105
+
106
+ ### Breaking
107
+
108
+ - **`spawnMode` is gone from role cards (#290).** The backend is a property of
109
+ the launch, not of a role: a launch that needs a worktree runs over rpc, and
110
+ everything else runs in-process. Since only a `centurion` may hold a worktree
111
+ (#214), a centurion is the only role that can reach rpc. There is no global
112
+ default to configure because there is no longer a choice to make per role.
113
+ - **Unknown frontmatter is refused (#290).** A card with a field the runtime does
114
+ not honour is rejected at load, listing the bad keys and the full legal set. The
115
+ retiral of `placement` was previously "tolerated"; fourteen bundled and local
116
+ cards still carried it while it did nothing.
117
+ - **Five bundled roles removed (#301).** `planner`, `reviewer`, `designer`,
118
+ `prototype`, and `advisor` are gone; `centurion`, `worker`, and `scout` remain.
119
+ They existed because the phase machine keyed its rules on role *names* — a role
120
+ that merely existed carried workflow meaning. See the declarative machine below.
121
+ - **Children inherit every host extension by default (#280).** The old default was
122
+ `--no-extensions` plus whatever a card named, which meant a role silently lost
123
+ host capabilities (a provider extension, a guard) it never knew to name. Use
124
+ `excludeExtensions: [pi-lens]` on a card, or
125
+ `PI_LEGION_CHILD_INHERIT_EXTENSIONS=0` globally, to get the old isolation.
126
+ `inheritExtensions: false` loads only the card's own list.
127
+
128
+ ### Added
129
+
130
+ - **Declarative phase machine (#298).** A team declares its own phases on its
131
+ lead's card:
132
+ ```yaml
133
+ phases:
134
+ - id: recon
135
+ next:
136
+ - to: build
137
+ require: [contract_file]
138
+ - id: build
139
+ next:
140
+ - to: done
141
+ require: [children_settled]
142
+ ```
143
+ Omitted means the built-in `planning → implementing → reviewing → verifying`.
144
+ Conditions are a closed vocabulary (`contract_file`, `children_present`,
145
+ `children_settled`, `children_none_failed`, `verdict_ok`, `verdict_not_ok`,
146
+ `verification_passed`, `verification_failed`, `rework_available`,
147
+ `children_all_failed`, `always`) and **none of them names a role**, which is
148
+ what lets a team reshape its workflow without touching source.
149
+ - Conditions live on the **edge**, not the phase: `planning → implementing`
150
+ needs a contract while `planning → failed` must not, or a team could not fail
151
+ before it had one.
152
+ - `done` / `failed` / `aborted` are injected when a card omits them. A team that
153
+ cannot be aborted is a safety hole. Reaching them is still gated: `failed`
154
+ needs a terminal reason, `aborted` needs a parent retire, `done` stays
155
+ supervisor-only. `aborted` is deliberately exempt from phase-level
156
+ requirements — an emergency stop must not wait for "everything settled".
157
+ - An invalid machine is rejected at load, reporting every problem at once
158
+ (unknown condition, duplicate id, unknown target, self-edge, unreachable
159
+ phase, terminal start).
160
+ - **The tree shows a team's machine (#302).** Selecting a team root unfolds its
161
+ phases, the current one, and every transition with its conditions.
162
+ - **`legion_phase` refusals name the legal exits (#299).** A refused transition
163
+ reports `legal next phases from 'recon': build, done, failed, aborted`. Measured:
164
+ without it a model invented plausible-but-nonexistent transitions and burned a
165
+ turn being refused.
166
+
167
+ ### Fixed
168
+
169
+ - **In-process children enforce `toolBudget` / `turnBudget` (#295).** The parent
170
+ computed budgets into `SpawnInput.env`, which an rpc child receives as its real
171
+ environment — but an in-process child shares the *host's* `process.env`, so the
172
+ guard read the parent's (absent) values. A card with `toolBudget: {maxToolCalls:
173
+ 3}` made 12 tool calls and finished normally. Budgets now travel in the
174
+ async-local child context, the same channel already used for lineage identity,
175
+ with `process.env` kept as the rpc fallback.
176
+ - **A malformed budget is an error, not "no limit" (#293).** `{not json}` parsed to
177
+ `undefined` and `{"maxToolCalls": -3}` was accepted, so the guard would have
178
+ blocked the first call. Both now fail at load naming the file, field, and value.
179
+ - **rpc is reserved for a centurion, and in-process keeps its ledger budget
180
+ (#279).** In-process launches had been exempted from every legion budget
181
+ (`Number.MAX_SAFE_INTEGER`), which made "run it in-process" a way to escape
182
+ depth/spawn/ledger caps. Admission and reservation now share one code path, so a
183
+ launch cannot be admitted and then rejected.
184
+ - **Runtime handles are keyed by (ledger path, node id) (#283, #285, #287).** Keyed
185
+ by path alone, a second session sharing the ledger could close the parent's
186
+ database; the identity is now adopted per node.
187
+ - **Extension inheritance discovery (#280).** `pi list` reports package
188
+ *directories*, resolved the way pi's own loader does (`package.json`'s
189
+ `pi.extensions`, else `index.ts` / `index.js`). Single-file `--extension`
190
+ arguments and builtins cannot be discovered this way — name them explicitly.
191
+ - **One spelling for "unknown child" (#288).** The old message advised "pass the
192
+ same cwd used for launch", which stopped being actionable once cross-store
193
+ lookup landed.
194
+ - **Prompt surfaces match the code again (#303, #304).** The parent playbook told a
195
+ centurion to "dispatch N centurions" — an instruction the runtime refuses
196
+ (only the root may dispatch one). The roster also listed a centurion to
197
+ itself. `worker` kept `subagent` in its tool list while its text said not to
198
+ spawn, and the README described every child as an rpc process.
199
+
200
+ ### Notes
201
+
202
+ - `toolBudget` / `turnBudget` remain **optional and unused by the bundled cards**.
203
+ Time, depth, per-node child counts, active-node counts, and the memory floor are
204
+ the intended bounds; a tool budget is a scalpel for one stubborn role, not a
205
+ default.
206
+ - Child extension inheritance includes heavy extensions. pi-lens starts an LSP
207
+ stack (~1 GB per child, #43); add `excludeExtensions: [pi-lens]` where a fleet
208
+ does not need it.
209
+
14
210
  ## [4.0.0] — 2026-10-24
15
211
 
212
+ - **Renewal for a budget casualty (#270).** An agent whose turn budget (`timeoutMs`)
213
+ runs out no longer just reports `failed`. When it measurably progressed and its
214
+ role allows renewal, the completion notice ends with a ready-to-paste
215
+ `subagent({ action: "continue", … })` plus the evidence (`progressed: 47 tool
216
+ results, 12 assistant messages, 6 files changed`) and the remaining budget
217
+ (`renewals: 1/2`). Renewal reuses the existing `continue` path, so the child
218
+ resumes from its own session file with its context intact. Two dispatches in one
219
+ day motivated this: one worker was cut off with nothing on disk, another finished
220
+ its code but ran out during verification.
221
+ - **Never automatic.** An auto-renewed stuck child would hold a concurrency slot
222
+ indefinitely; the notice hands over the decision instead.
223
+ - "Progressed" = ≥ 3 new `toolResult` rows or ≥ 1 changed file since the last
224
+ baseline. `assistant` messages are shown but never count — a model looping on
225
+ the same sentence grows that number without doing work.
226
+ - New agent frontmatter: `renewable` (default **true**; a timed-out `scout` means
227
+ the task was mis-scoped) and `maxRenewals` (default **2**; `0` disables, each
228
+ renewal is a fresh full budget). Both are validated in the team-override path
229
+ like every other numeric/boolean field.
230
+ - `wait`/`collect` timeouts never propose a renewal: those return while the child
231
+ is still alive. Only a real termination does, judged by `stopReason: "toolUse"`
232
+ / missing assistant row (F29) / the host's `terminated` wording — a clean
233
+ `stop` from an ordinary retry does not spend the budget.
234
+
235
+ - **Launch admission queue (#266).** A launch that exceeds a *concurrency* gate
236
+ (`maxChildrenPerNode`, `maxActive`, ledger `max_children_per_node` /
237
+ `max_active_nodes`) no longer fails with `BUDGET_EXCEEDED`. It is enqueued,
238
+ the caller gets `▶ scout-7 (scout) supervisor=inprocess queued(3)` and treats
239
+ the work as dispatched, and the child starts automatically when a slot frees.
240
+ Previously a 12-step fan-out against `maxChildrenPerNode: 8` ran 8 children and
241
+ reported 4 errors — and nothing ever retried them, because the parent was
242
+ already inside `wait` and the freed slot had no subscriber.
243
+ - New settings: `subagents.inprocess.maxQueuedLaunches` (default **32**) and
244
+ `subagents.inprocess.launchQueueTimeoutMs` (default **900000**). Both are
245
+ shared by the in-process and RPC paths — one scheduler, not two.
246
+ - Depth, spawn-per-session, memory, name-collision and config errors are still
247
+ refused synchronously, byte-identically to before: only gates that a slot
248
+ release can satisfy are queueable. `maxChildrenPerNode: 0` is a hard refusal
249
+ (0 means "no children", so waiting can never satisfy it).
250
+ - A queued launch reserves nothing: no ledger row, no child run directory, no
251
+ worktree, no session file. `timeout`/`overflow`/`stop` are reported by name;
252
+ `stop` dequeues without ever spawning, and a parent-turn abort flushes the
253
+ queue and says how many launches it dropped.
254
+ - `subagent({action:"status"})` shows `state=queued`, `collect`/`wait` block
255
+ until admission instead of reading the placeholder session path, and
256
+ `async: false` reports `↳ <name> waited Ns in the launch queue`.
257
+ - Two new process-only events (`launch_queued`, `launch_admitted`) and a
258
+ `legion-queue: N queued` status pill.
259
+
260
+ ## [4.0.0] — 2026-10-19
261
+
16
262
  A major line — but not only the pi-only collapse. Relative to 3.1.0 this release
17
263
  removes every non-`pi` agent kind and the archived Herdr backend behind it, along
18
264
  with the settings and error codes that only its transport read. It also carries
@@ -651,7 +897,8 @@ branch — pin `pi-legion@0.16.x` if you still need that model. See
651
897
  Earlier 0.11–0.15 history (including the `pi-herdr-swarm` /
652
898
  `@zzjcool/pi-herdr-subagents` renames) lives in git tags and older Releases.
653
899
 
654
- [Unreleased]: https://github.com/zzjcool/pi-legion/compare/v4.0.0...HEAD
900
+ [Unreleased]: https://github.com/zzjcool/pi-legion/compare/v5.0.0...HEAD
901
+ [5.0.0]: https://github.com/zzjcool/pi-legion/compare/v4.0.0...v5.0.0
655
902
  [4.0.0]: https://github.com/zzjcool/pi-legion/compare/v3.1.0...v4.0.0
656
903
  [3.1.0]: https://github.com/zzjcool/pi-legion/compare/v3.0.3...v3.1.0
657
904
  [3.0.3]: https://github.com/zzjcool/pi-legion/compare/v3.0.2...v3.0.3
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: pi-legion
3
3
  npm: "pi-legion"
4
- description: "Pi Legion v2: recursive centurion trees on headless Pi RPC; shared SQLite WAL legion.db. Four-stage planning/implementing/reviewing/verifying state machine, parent/child/sibling legion_mail, three-layer /legion UI, upward MR trees, squad resume."
4
+ description: "Pi Legion v2: recursive centurion teams on Pi. Children run in-process by default (rpc only for a worktree-owning centurion); shared SQLite WAL legion.db. Declarative per-team phase machine (built-in default: planning/implementing/reviewing/verifying), parent/child/sibling legion_mail, three-layer /legion UI, upward MR trees, squad resume."
5
5
  ---
6
6
 
7
7
  # pi-legion
@@ -49,14 +49,16 @@ User (CEO) — root Pi session (TUI)
49
49
  └─ pi-legion in child mode
50
50
  ├─ LegionSupervisor for this squad
51
51
  ├─ `legion_mail` / `legion_tree` / `legion_phase` tools
52
- ├─ worker / scout / reviewer … children (`worktree: false`)
52
+ ├─ worker / scout … children (`worktree: false`)
53
53
  └─ nested centurions → recursive squads
54
54
  ```
55
55
 
56
56
  Transport, state, decision-making, and display are separate layers:
57
57
 
58
- - **Transport:** each child is a headless Pi RPC process managed by a
59
- `LegionSupervisor`; a centurion can supervise another squad recursively.
58
+ - **Transport:** a child is an `AgentSession` inside the parent process by
59
+ default; only a worktree-owning centurion runs as a separate `pi --mode rpc`
60
+ process. A `LegionSupervisor` owns both backends, and a centurion can lead
61
+ another squad recursively.
60
62
  - **State:** all nodes in the tree share the `legion.db` SQLite ledger in WAL
61
63
  mode. Per-run `run.json` records and session files remain the resume source
62
64
  for each child.
@@ -65,36 +67,70 @@ Transport, state, decision-making, and display are separate layers:
65
67
  - **Display:** the event bus feeds the resident tree, the `/legion` full view,
66
68
  and an on-demand live overlay.
67
69
 
68
- ## Spawn mode
70
+ ## Bundled roles
71
+
72
+ Three roles ship with the package:
73
+
74
+ | Role | Purpose | Tools | Default timeout |
75
+ | --- | --- | --- | --- |
76
+ | `centurion` | Team lead: freeze the contract, dispatch children, drive the phase machine, deliver a reviewable result. Holds the worktree. | pi defaults | 30 min |
77
+ | `worker` | Implementation: write code and run tests against a frozen plan, report a verdict. | read/edit/write/grep/find/ls/bash/codemode | 45 min |
78
+ | `scout` | Read-only recon: produce a fact list, no plan, no implementation. | read/grep/find/ls/bash/codemode | 10 min |
69
79
 
70
- `spawnMode` picks the backend that runs one child. Per role it comes from agent
71
- frontmatter → `subagents.agentOverrides.<role>` → team member override:
80
+ `worker` cannot dispatch nested agents (`maxSubagentDepth: 1`). Anything a worker
81
+ needs to escalate is a `verdict ok:false`, not another layer of children — one
82
+ level keeps the tree's cost and blast radius bounded.
83
+
84
+ Roles are not a fixed pipeline. Since the phase machine became declarative
85
+ (#298), nothing in the runtime keys on a role being called "worker" or
86
+ "reviewer"; a team shapes its workflow by declaring phases on its lead's card.
72
87
 
73
88
  ```yaml
74
- # agents/scout.md
75
- ---
76
- spawnMode: inprocess # or: rpc
77
- ---
89
+ # agents/centurion.md
90
+ phases:
91
+ - id: recon
92
+ next:
93
+ - to: build
94
+ require: [contract_file]
95
+ - id: build
96
+ next:
97
+ - to: done
98
+ require: [children_settled]
78
99
  ```
79
100
 
80
- **Default:** omitting `spawnMode` means `inprocess` — the child runs as an
81
- `AgentSession` inside the parent Node process (`createAgentSession`), which
82
- trades process isolation for spawn latency.
101
+ Omitted means the built-in four-phase pipeline
102
+ (`planning → implementing → reviewing → verifying → done`). Conditions are a
103
+ closed vocabulary — `contract_file`, `children_present`, `children_settled`,
104
+ `children_none_failed`, `verdict_ok`, `verdict_not_ok`, `verification_passed`,
105
+ `verification_failed`, `rework_available`, `children_all_failed`, `always` — and
106
+ `done` / `failed` / `aborted` are always available terminals so a team can
107
+ never lose the ability to complete or abort.
83
108
 
84
- **Automatic fallback to `rpc`:** when `spawnMode` is omitted *and* the resolved
85
- spawn cannot be in-process, Legion launches it over `pi --mode rpc` instead.
86
- That happens when the role needs an OS worktree (`worktree: true` — only the
87
- `centurion` role may hold this; see
88
- [Delivery and resume](#delivery-and-resume)).
109
+ ## Spawn mode
89
110
 
90
- **Explicit is strict:** `spawnMode: "inprocess"` with `worktree: true` is
91
- refused with `INVALID_PARAMS` (message prefers `set worktree: false`) rather
92
- than silently downgraded, so a role that asks for in-process never gets a
93
- different transport behind its back. Non-centurion requests for `worktree: true`
94
- are demoted to `false` with a once-per-run warning instead of throwing.
111
+ The backend is a property of the **launch**, not of a role. There is no
112
+ `spawnMode` frontmatter field — a card cannot pin itself to a process model —
113
+ and the rule is simply:
95
114
 
96
- Limits for the in-process backend are separate (`subagents.inprocess.*`) and are
97
- not shared with the `legion.*` tree budgets — see
115
+ | Launch needs a worktree? | Backend |
116
+ | --- | --- |
117
+ | yes | `rpc` — a separate `pi --mode rpc` process |
118
+ | no | in-process `AgentSession` inside the parent Node process |
119
+
120
+ **Why rpc still exists:** it is the only backend that can host a worktree
121
+ child. Only the `centurion` role may hold a worktree (see
122
+ [Delivery and resume](#delivery-and-resume)), so a centurion is the only role
123
+ that can ever run on rpc — every other role runs in-process, where spawn latency
124
+ is lower and no second process is involved.
125
+
126
+ **A non-centurion asking for a worktree** is demoted to `worktree: false` with a
127
+ once-per-run warning rather than throwing, so a stale card does not break every
128
+ launch in the tree.
129
+
130
+ **Both backends share the same budgets.** The legion tree limits
131
+ (`maxDepth` / `maxChildrenPerNode` / `maxActiveNodes`) apply to every launch; the
132
+ in-process pools (`subagents.inprocess.*`) apply on top, and the stricter gate
133
+ wins. Avoiding rpc is never a way to escape a cap. See
98
134
  [docs/inprocess-supervisor.md](docs/inprocess-supervisor.md).
99
135
 
100
136
  ## Headless child contract
@@ -129,7 +165,7 @@ subagent({
129
165
  ```
130
166
 
131
167
  The centurion plans the work, writes the squad contract, dispatches ordinary
132
- roles such as `worker`, `scout`, and `reviewer`, and coordinates the squad
168
+ roles such as `worker` and `scout`, and coordinates the squad
133
169
  through its phases. For a single leaf task, dispatch an ordinary role directly:
134
170
 
135
171
  ```text
@@ -230,11 +266,17 @@ legion_phase({ to, note?, evidence? })
230
266
  - **`legion_phase`** is available to centurions. It requests a state transition;
231
267
  the supervisor checks the transition's invariants and records the result.
232
268
 
233
- A squad moves through **planning → implementing → reviewing → verifying**.
234
- Planning produces a non-empty `.pi-legion/contract.md` with file ownership
235
- and acceptance commands. During reviewing, an independent reviewer supplies
236
- the judgment; in verifying, the supervisor runs the contract's `legion-verify`
237
- commands without an LLM. Passing verification advances the squad to `done`.
269
+ A squad moves through whatever phases its lead declares. With no declaration
270
+ that is the built-in **planning → implementing → reviewing → verifying**:
271
+ planning produces a non-empty `.pi-legion/contract.md` with file ownership and
272
+ acceptance commands; reviewing needs a settled child that returns `ok:true`; in
273
+ verifying the supervisor runs the contract's `legion-verify` commands without an
274
+ LLM. Passing verification advances the squad to `done`.
275
+
276
+ Because the machine is data, no rule names a role: "the children are settled" and
277
+ "a verdict came back ok" are conditions, not "the worker" and "the reviewer".
278
+ That is what lets a team rename, reorder, or drop phases without touching code —
279
+ and it is why the bundled roster is just `centurion`, `worker`, and `scout`.
238
280
 
239
281
  ## Three-layer visualization
240
282
 
@@ -428,20 +470,35 @@ Coverage:
428
470
 
429
471
  ### Child extensions
430
472
 
431
- Children start with `--no-extensions`, so they do not load the host's
432
- discovered extensions (settings `packages`, `~/.pi/agent/extensions`, project
433
- extensions). Each extension a child loads is a memory cost multiplied by every
434
- concurrent child; an LSP stack is ~1 GB (#43). A child still loads pi-legion
435
- itself (guard and nested spawning) and every path listed in the role's
436
- `extensions` / `subagentOnlyExtensions`.
473
+ **A child inherits every extension the parent has**, minus anything it excludes.
474
+ This is the opposite of the pre-4.0.1 default: children used to start with
475
+ `--no-extensions` and load only what a card named, which meant a role silently
476
+ lost host capabilities (a provider extension, a guard) it never knew to name.
477
+ Children now run in-process by default, so "one more extension" no longer means
478
+ another process's worth of memory.
437
479
 
438
480
  | Knob | Effect |
439
481
  | --- | --- |
440
- | role frontmatter `inheritExtensions: true` | That role loads all host extensions, as before. |
441
- | `PI_LEGION_CHILD_INHERIT_EXTENSIONS=1` | Every child loads all host extensions, as before. |
442
- | `PI_LEGION_CHILD_EXTENSIONS=/a.ts,/b.ts` | Extra extension paths every child loads explicitly, e.g. a provider extension the child's model needs. |
443
-
444
- Bundled planner, reviewer, and worker do not list `lens_*` tools. pi-lens starts an LSP stack (~1 GB per child, #43) and is not part of the default `--no-extensions` child. To give a role those tools, add the tool names to its `tools:` list and point `extensions:` / `subagentOnlyExtensions` (or `PI_LEGION_CHILD_EXTENSIONS`) at the pi-lens extension file. `/legion doctor` reports `ROLE_MISSING_LENS` when a role lists the tools without that path.
482
+ | *(default)* | Inherit every host extension. |
483
+ | role frontmatter `excludeExtensions: [pi-lens]` | Subtract specific ones. Accepts a package **name** (`pi-lens`, matches the package dir or a loose `pi-lens.ts`) or an exact **path** (`/abs/.../index.ts`, `~` expands). |
484
+ | role frontmatter `inheritExtensions: false` | Load ONLY the card's own `extensions` / `subagentOnlyExtensions`. Nothing is inherited, so `excludeExtensions` has nothing to subtract. |
485
+ | `PI_LEGION_CHILD_INHERIT_EXTENSIONS=0` | Same as `inheritExtensions: false`, for every child. `1` forces inheritance back on. |
486
+ | `PI_LEGION_CHILD_EXTENSIONS=/a.ts,/b.ts` | Extra paths every child loads explicitly. |
487
+
488
+ The child-guard is appended unconditionally and is not subject to either knob —
489
+ a child without it breaks the prompt/format contract no matter what the card
490
+ asked for.
491
+
492
+ **Discovery limit.** Host extensions are found by reading `pi list` (which
493
+ reports package *directories*) and resolving each one the way pi's own loader
494
+ does: `package.json`'s `pi.extensions`, else `index.ts`/`index.js`. Single-file
495
+ `--extension` arguments and builtins cannot be discovered this way; name them
496
+ explicitly if a child needs them.
497
+
498
+ **Cost note.** Inheriting everything includes heavy extensions. pi-lens starts an
499
+ LSP stack (~1 GB per child, #43). If a fleet of children does not need it, add
500
+ `excludeExtensions: [pi-lens]` to those cards. The bundled `worker` does not list
501
+ `lens_*` tools; give a role those tools by adding the names to its `tools:` list.
445
502
 
446
503
  ### Extra agent directories
447
504
 
@@ -492,7 +549,7 @@ another connection holds the db.
492
549
 
493
550
  ## Delivery and resume
494
551
 
495
- A centurion owns a Git worktree for its subtree; its worker/scout/reviewer
552
+ A centurion owns a Git worktree for its subtree; its worker/scout
496
553
  children work in that subtree (`worktree: false`, typically in-process), and a
497
554
  nested centurion gets a nested worktree. **Parallel isolated writers:** dispatch
498
555
  N centurions from the root (each owns one worktree); do not give every worker
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: centurion
3
- description: 百夫长/组长:携带一支团队递归派生的指挥角色,负责冻结契约、调度组员、推进四阶段状态机并整队交付。
3
+ description: 百夫长/组长:带领一支子团队,负责冻结契约、调度组员、推进阶段状态机并整队交付。(仅根会话可派发)
4
4
  kind: pi
5
5
  allowNestedSubagents: true
6
6
  maxSubagentDepth: 3
@@ -19,35 +19,39 @@ acceptance:
19
19
 
20
20
  你是 centurion,一支子树团队的负责人。只负责自己的队伍;不得越级指挥、收割或绕过其他 centurion 管理其组员。你必须通过 `legion_phase` 请求阶段迁移,通过 `legion_mail` 沟通,通过 `legion_tree` 观察状态,不得把口头声明当作机器事实。
21
21
 
22
- ## 四阶段职责
22
+ ## 阶段职责
23
23
 
24
- ### planning
24
+ 阶段由本队的 `phases:` 声明(省略则是内置四阶段:planning → implementing → reviewing → verifying → done)。**不要假定阶段名**——先看系统提示里展开的状态机,或直接用 `legion_phase` 试,被拒时拒绝信息会列出当前合法的下一步。
25
+
26
+ 不论阶段怎么命名,职责是这样分层的:
27
+
28
+ ### 规划阶段(内置 `planning`)
25
29
 
26
30
  - 先完整理解目标与验收条件,再把接口、wire 字段、文件所有权、验收清单写入 `.pi-legion/contract.md`。
27
- - 契约未落盘且非空,不得请求进入 implementing;发现契约矛盾时停止派工并上报,不得自行解释或扩大文件归属。
31
+ - 契约未落盘且非空,不得请求进入实现阶段;发现契约矛盾时停止派工并上报,不得自行解释或扩大文件归属。
28
32
  - 契约中的每个写入文件只能有一个 owner;共享文件必须串行分配。
29
33
 
30
- ### implementing
34
+ ### 实现阶段(内置 `implementing`)
31
35
 
32
36
  - 按冻结契约和文件所有权并行派发互不冲突的工作;worker 任务必须逐项列出可写文件、验证要求和交付格式。
33
37
  - 子任务只用 `subagent` 工具派发;工具的并行任务必须各自有明确边界。实现过程中监控 `legion_tree`,通过邮件解决必要的协作,不以邮件取代共享工件。
34
- - 组员声明完成后,按结果收集 session/verdict;未收齐所有 worker 结果不得请求进入 reviewing。
38
+ - 组员声明完成后,按结果收集 session/verdict;未收齐所有 worker 结果不得请求离开本阶段。
35
39
 
36
- ### reviewing
40
+ ### 审查阶段(内置 `reviewing`)
37
41
 
38
- - 对实现做多角度、对抗式 review,至少覆盖正确性/回归、测试覆盖和简洁性;reviewer 必须独立于实现者。
39
- - `reviewer` verdict `ok:true` 才能请求进入 verifying。major finding 必须退回 implementing 修复并复审;若是计划本身错误,可退回 planning,但必须通过 `legion_phase` 记录并邮件告知上级。
40
- - 每个 `failed` worker 必须先 rework 重派或由父级明确收队;不得把失败 worker 声明为已处理来解锁 reviewing。
41
- - 被 `legion_phase` 拒绝时,先按拒绝理由核实机器事实,修正条件后重试;这不是 rework,不得伪造证据或绕过状态机。
42
+ - 对抗式审查,至少覆盖正确性/回归、测试覆盖和简洁性,且必须由**未参与实现**的独立子节点做。
43
+ - 通过条件看阶段声明里的 `verdict_ok`:需要一个 settled 子节点回报 `ok:true`。major finding 退回实现阶段修复并复审;若是计划本身错误,可退回规划阶段,但必须通过 `legion_phase` 记录并邮件告知上级。
44
+ - 每个 `failed` 子节点必须先 rework 重派或由父级明确收队;不得把失败节点声明为已处理来解锁审查阶段。
45
+ - 被 `legion_phase` 拒绝时,按拒绝理由核实机器事实,修正条件后重试;这不是 rework,不得伪造证据或绕过状态机。
42
46
 
43
- ### verifying 与收队
47
+ ### 验证与收队(内置 `verifying` / `done`)
44
48
 
45
- - verifying 由 supervisor 自动运行契约中的 `legion-verify` 命令。全绿后状态机自动进入 done;不要自行宣称验证成功,也不要手动改数据库。
46
- - done 后汇总全部 worker/reviewer verdict、验证结果和未决问题;把子树工作 squash 成一个 MR/PR,开给父节点/目标分支审查,并把链接写入最终 verdict。绝不在本级自行合并。
49
+ - 验证由 supervisor 自动运行契约中的 `legion-verify` 命令。全绿后状态机自动进入 done;不要自行宣称验证成功,也不要手动改数据库。
50
+ - done 后汇总全部子节点 verdict、验证结果和未决问题;把子树工作 squash 成一个 MR/PR,开给父节点/目标分支审查,并把链接写入最终 verdict。绝不在本级自行合并。
47
51
  - 收队时保证组员结果已落盘;由父级 collect 本节点结果后 retire。worktree/session 是 resume 凭据,未获根级清理授权前不得删除。
48
52
 
49
53
  ## 止损与安全
50
54
 
51
- - 同一问题重试不超过 2 次;两次仍失败,立即通过 `advisor` 咨询止损。advisor 必须是 `kind: pi`。咨询要附已尝试方案及失败输出、当前假设、涉及文件,并按 advisor 裁决执行。
52
- - advisor 裁决回滚时回到上一个检查点并记录原因;不得硬扛第三次,不得为了绿灯绕过测试或修改冻结契约。
55
+ - 同一问题重试不超过 2 次;两次仍失败,停止并向上报告(附已尝试方案、失败输出、当前假设、涉及文件),不要硬扛第三次,也不得为了绿灯绕过测试或修改冻结契约。
56
+ - 回滚时回到上一个检查点并记录原因。
53
57
  - 只使用允许的工具和团队边界;不读写其他队的文件,不直接访问 legion.db,不使用越级邮件。
package/agents/scout.md CHANGED
@@ -8,6 +8,9 @@ inheritProjectContext: true
8
8
  inheritSkills: false
9
9
  async: true
10
10
  timeoutMs: 600000
11
+ # A timed-out scout means the question was too broad, not that work needs
12
+ # finishing — renewing it would just re-run an over-scoped search (#270 D5).
13
+ renewable: false
11
14
  acceptance:
12
15
  level: attested
13
16
  role: read-only
@@ -17,7 +20,6 @@ acceptance:
17
20
  evidence: [facts, open-questions]
18
21
  severity: required
19
22
  kind: pi
20
- placement: split-down
21
23
  steer: true
22
24
  maxSubagentDepth: 1
23
25
  ---
package/agents/worker.md CHANGED
@@ -1,13 +1,12 @@
1
1
  ---
2
2
  name: worker
3
3
  description: 实现者:按冻结计划写代码、跑测试,完成后自报 verdict
4
- tools: read, edit, write, grep, find, ls, bash, subagent, codemode
4
+ tools: read, edit, write, grep, find, ls, bash, codemode
5
5
  systemPromptMode: replace
6
6
  inheritProjectContext: true
7
7
  inheritSkills: false
8
8
  async: true
9
9
  timeoutMs: 2700000
10
- allowNestedSubagents: true
11
10
  acceptance:
12
11
  level: verified
13
12
  role: writer
@@ -17,10 +16,9 @@ acceptance:
17
16
  evidence: [verification-output]
18
17
  severity: required
19
18
  kind: pi
20
- placement: split-down
21
19
  steer: true
22
20
  worktree: false
23
- maxSubagentDepth: 3
21
+ maxSubagentDepth: 1
24
22
  ---
25
23
 
26
24
  你是 worker,一个实现 agent。你按已冻结的计划把代码写出来并证明它能跑。
@@ -36,8 +34,8 @@ maxSubagentDepth: 3
36
34
  - **严格按计划**:接口/签名已被冻结,不要擅自更改。发现计划有错时,**停下来报告**(verdict `ok: false`,reason 写明冲突),不要自行"顺手修正"。
37
35
  - **只碰计划授权的文件**。其他模块的文件一律不动。
38
36
  - **跑不动的命令**(交互式、需要确认)走默认放行,不要挂起等待。
39
- - **禁止开新的 worker/写操作子 agent**。你唯一允许的嵌套派生是只读的 advisor,
40
- 且仅限失败升级场景(见 AGENTS.md「失败止损」:重试 2 次仍失败时咨询,按裁决执行)。
37
+ - **不派任何子 agent**。失败止损靠自己重试与向上报告(verdict `ok: false` 写明冲突),
38
+ 不要为了"咨询"再开一层。
41
39
  - **工作区**:在 centurion 子树 worktree(或父会话 cwd)**原地**改代码;隔离与 MR 由 centurion(或根会话)收队。不要本地 merge,不要 push 到 main/master。
42
40
  - **bugfix 先红后绿**:修 bug 类任务,第一步先写一个能复现该 bug 的失败测试并跑出失败,修复后同一测试必须转绿;两次输出都原样贴进报告。不复现就修复的,视为未验收。
43
41
 
@@ -47,7 +45,7 @@ maxSubagentDepth: 3
47
45
  2. **用项目自己的方式验证,分两档,别每轮都跑全量**:
48
46
  - 先发现项目怎么验证:看 `.pi/verify.sh`、Makefile/justfile target、CI 配置(.github/workflows 等)、package.json scripts、项目 README/CONTRIBUTING。项目明确的验证入口就是唯一权威,不要自己发明替代命令。
49
47
  - 每轮改动结束:跑项目的**轻量档**(typecheck/lint/vet 级别,如 `make lint`、`npm run typecheck`、`go vet ./...`);秒级反馈,失败直接修,不计入重试升级。
50
- - 全部步骤完成、交活前才跑一次项目的**全量档**(完整测试套件/CI 等效命令);交活后插件会自动再验证一遍判定验收,不需要中途反复跑全量。全量失败按「失败止损」处理:重试不超过 2 次,仍失败升级咨询 advisor。
48
+ - 全部步骤完成、交活前才跑一次项目的**全量档**(完整测试套件/CI 等效命令);交活后插件会自动再验证一遍判定验收,不需要中途反复跑全量。全量失败按「失败止损」处理:重试不超过 2 次,仍失败则 `ok: false` 上报。
51
49
  - 项目找不到任何验证入口时,在报告里写明依据(查过哪些地方),不要自己编命令冒充。
52
50
  3. 把最终那次 full 验证输出**原样**(含命令与结果)放进最终报告。
53
51
  4. 交活前逐文件重读完整 diff:检查遗留的调试代码、console.log/print、注释掉的老逻辑、无关改动混入;发现问题先修。