pi-legion 0.0.0-stage → 2.0.0

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 (99) hide show
  1. package/CONTRIBUTING.md +168 -0
  2. package/LICENSE +21 -0
  3. package/README.md +387 -2
  4. package/agents/advisor.md +110 -0
  5. package/agents/centurion.md +53 -0
  6. package/agents/designer.md +87 -0
  7. package/agents/planner.md +53 -0
  8. package/agents/prototype.md +72 -0
  9. package/agents/reviewer.md +114 -0
  10. package/agents/scout.md +56 -0
  11. package/agents/worker.md +68 -0
  12. package/docs/contracts/esc-abort-v1.md +131 -0
  13. package/docs/contracts/esc-cascade-v2.md +100 -0
  14. package/docs/design.md +1286 -0
  15. package/docs/legion-v2-contract.md +461 -0
  16. package/docs/legion-v2-delivery.md +62 -0
  17. package/docs/retire-hardening-contract.md +125 -0
  18. package/docs/subagents-teams.md +76 -0
  19. package/docs/tui-viewer-contract.md +276 -0
  20. package/docs/vexillatio-contract.md +162 -0
  21. package/index.ts +2836 -0
  22. package/package.json +89 -4
  23. package/skills/pi-legion/SKILL.md +98 -0
  24. package/src/agents/agents.ts +742 -0
  25. package/src/agents/context.ts +118 -0
  26. package/src/agents/frontmatter.ts +237 -0
  27. package/src/agents/model-resolution.ts +161 -0
  28. package/src/agents/model-scope.ts +176 -0
  29. package/src/agents/overrides.ts +148 -0
  30. package/src/agents/paths.ts +21 -0
  31. package/src/agents/presets.ts +237 -0
  32. package/src/agents/settings.ts +793 -0
  33. package/src/agents/step-model.ts +153 -0
  34. package/src/agents/teams.ts +101 -0
  35. package/src/api.ts +311 -0
  36. package/src/backends/legacy/client.ts +638 -0
  37. package/src/backends/legacy/cursor-chat.ts +348 -0
  38. package/src/backends/legacy/runner.ts +101 -0
  39. package/src/backends/legacy/types.ts +138 -0
  40. package/src/extension/auto-doctor.ts +144 -0
  41. package/src/extension/blocked.ts +52 -0
  42. package/src/extension/budget.ts +63 -0
  43. package/src/extension/child-guard.ts +336 -0
  44. package/src/extension/event-bus.ts +263 -0
  45. package/src/extension/join.ts +230 -0
  46. package/src/extension/legion-commands.ts +719 -0
  47. package/src/extension/legion-doctor.ts +888 -0
  48. package/src/extension/legion-stop.ts +150 -0
  49. package/src/extension/legion-tools.ts +816 -0
  50. package/src/extension/notice-renderer.ts +72 -0
  51. package/src/extension/notify.ts +413 -0
  52. package/src/extension/playbook.ts +44 -0
  53. package/src/extension/recycle.ts +23 -0
  54. package/src/extension/runtime.ts +1190 -0
  55. package/src/extension/scout-guard.ts +483 -0
  56. package/src/extension/slash.ts +681 -0
  57. package/src/extension/spawn-hints.ts +79 -0
  58. package/src/extension/summary.ts +772 -0
  59. package/src/legion/budget.ts +165 -0
  60. package/src/legion/db.ts +242 -0
  61. package/src/legion/events.ts +150 -0
  62. package/src/legion/mail.ts +412 -0
  63. package/src/legion/nodes.ts +461 -0
  64. package/src/legion/rebuild.ts +533 -0
  65. package/src/legion/state-machine.ts +313 -0
  66. package/src/legion/usage.ts +101 -0
  67. package/src/runs/acceptance.ts +221 -0
  68. package/src/runs/args.ts +270 -0
  69. package/src/runs/hygiene.ts +335 -0
  70. package/src/runs/kind.ts +260 -0
  71. package/src/runs/orchestrator.ts +1145 -0
  72. package/src/runs/owner.ts +61 -0
  73. package/src/runs/provision.ts +135 -0
  74. package/src/runs/store.ts +571 -0
  75. package/src/runs/verify-queue.ts +195 -0
  76. package/src/runs/worktree.ts +125 -0
  77. package/src/shared/name.ts +78 -0
  78. package/src/shared/nested-path.ts +80 -0
  79. package/src/shared/output-cap.ts +57 -0
  80. package/src/shared/progress.ts +97 -0
  81. package/src/shared/session.ts +769 -0
  82. package/src/shared/types.ts +710 -0
  83. package/src/supervisor/child-memory.ts +140 -0
  84. package/src/supervisor/proc-tree.ts +103 -0
  85. package/src/supervisor/rpc-supervisor.ts +1033 -0
  86. package/src/supervisor/types.ts +153 -0
  87. package/src/supervisor/ui-proxy.ts +226 -0
  88. package/src/tui/chat-items.ts +330 -0
  89. package/src/tui/live-overlay.ts +874 -0
  90. package/src/tui/status.ts +845 -0
  91. package/src/tui/tree-view.ts +808 -0
  92. package/src/tui/viz/canvas.ts +119 -0
  93. package/src/tui/viz/layout.ts +111 -0
  94. package/src/tui/viz/model.ts +123 -0
  95. package/src/tui/viz/palette.ts +188 -0
  96. package/src/tui/viz/panels.ts +530 -0
  97. package/src/tui/viz/railway.ts +313 -0
  98. package/src/tui/viz/rate.ts +211 -0
  99. package/src/tui/viz/view.ts +228 -0
@@ -0,0 +1,168 @@
1
+ # Contributing
2
+
3
+ This package is a Pi extension. The public surface is `index.ts` (the
4
+ `subagent` tool) plus `src/api.ts` (programmatic imports). Please keep those
5
+ two as the only things a downstream should need.
6
+
7
+ ## Project workflow
8
+
9
+ **Issues first.** A bug or feature found while dogfooding starts as a GitHub
10
+ issue. Include a repro, the expected behaviour, the actual behaviour, and the
11
+ environment (Node version, OS, and the Pi / pi-legion version).
12
+
13
+ **One concern per pull request.** Link the issue. Open the PR as a draft.
14
+ Mark it **Ready for review** when the change is ready and you expect the
15
+ required check `ci` to pass.
16
+
17
+ **Merge gate.** Alice (the maintainer bot) merges only after code review has
18
+ passed and the required GitHub Actions check `ci` is green. Do not push
19
+ directly to `master`. Implementers do not merge their own pull requests.
20
+
21
+ **Before you push.** Run:
22
+
23
+ ```bash
24
+ npm run typecheck
25
+ npm test
26
+ ```
27
+
28
+ Add the unit tests the change needs. Prefer **TDD** (see [Checks](#checks)):
29
+ a failing test in `test/unit` or `test/integration` first, then the production
30
+ code.
31
+
32
+ **Regression tests.** When you fix a bug, add a unit or integration test in
33
+ the same PR when that is feasible: failing first, then passing. Do not
34
+ `unref()` a timer that must keep the Node test runner alive. `unref()` drops
35
+ the timer from the event-loop refcount, so `node --test` can exit before the
36
+ callback runs. That is the rpc-supervisor retire-timer lesson (`waitForExit`
37
+ and the descendant-sweep delay in `src/supervisor/rpc-supervisor.ts`): a
38
+ timeout the caller is awaiting stays referenced, as with the wait timer in
39
+ `src/extension/runtime.ts`.
40
+
41
+ **npm release.** A `v*.*.*` tag runs the release workflow
42
+ (`.github/workflows/release.yml`). Publish from that workflow with
43
+ `npm stage publish` and a stage-only token. A maintainer promotes the staged
44
+ version on npm with 2FA. Do not use plain `npm publish` in GitHub Actions.
45
+
46
+ **Communication.** Coordinate in the team channel, or with Alice. Do not ping
47
+ the maintainer user for a routine review or merge ask.
48
+
49
+ ## Setup
50
+
51
+ ```bash
52
+ git clone https://github.com/zzjcool/pi-legion.git
53
+ cd pi-legion
54
+ npm install
55
+ ```
56
+
57
+ Requires Node.js 22+ (`node --experimental-strip-types` is how tests run;
58
+ there is no compile step to ship).
59
+
60
+ ## Checks
61
+
62
+ ```bash
63
+ npm run typecheck
64
+ npm test # test/unit/*.test.ts
65
+ npm run test:integration # fake herdr, no live binary
66
+ npm run test:all # typecheck + unit + integration
67
+ ```
68
+
69
+ `npm run test:live` talks to a real herdr. It skips itself unless `HERDR_ENV=1`
70
+ and `herdr` is on `PATH`. Do not fail CI on it.
71
+
72
+ Prefer **TDD** for behaviour changes: a failing test in `test/unit` or
73
+ `test/integration` first, then the production code. Integration tests should
74
+ go through `createHerdrClient(createFakeRunner(fake))` so argv construction
75
+ and JSON parsing stay covered.
76
+
77
+ ## Mock LLM
78
+
79
+ For offline Pi / legion smoke without provider quota, start the scriptable
80
+ OpenAI-compatible mock (see [`test/mock-llm/README.md`](test/mock-llm/README.md)):
81
+
82
+ ```bash
83
+ npm run mock-llm # default scenario=echo on :8765
84
+ npm run test:mock-llm # engine + HTTP + optional pi --print smoke
85
+ ```
86
+
87
+ `test/docker/mock-llm.mjs` launches the same CLI (default scenario `tool-once`
88
+ for legacy inside-*.sh). Point Pi at it with `writeMockPiHome({ baseUrl })` or
89
+ the `providers.mock` block in that README. Never commit real API keys; the
90
+ dummy key is always the literal `mock`.
91
+
92
+ ## Docker verification
93
+
94
+ `test/docker/` runs the package inside `pi-herdr-sandbox:latest` with a copy
95
+ of the **host** Pi config (`~/.pi/agent/settings.json`, `models.json`,
96
+ `auth.json` if present). The copy is disposable (`/tmp/pi-herdr-docker-home`
97
+ by default); the container cannot write back to your real `~/.pi`.
98
+
99
+ ```bash
100
+ # image must already exist
101
+ docker image inspect pi-herdr-sandbox:latest >/dev/null
102
+
103
+ bash test/docker/verify-1-child-guard.sh # child herdr interceptor
104
+ bash test/docker/verify-2-task-appendix.sh # frozen task appendix
105
+ bash test/docker/verify-3-readonly.sh # read-only bash denylist
106
+ bash test/docker/verify-4-acceptance.sh # collect → verified
107
+ bash test/docker/verify-5-blocked.sh # onBlocked forward
108
+ bash test/docker/verify-6-cache.sh # collect cache / retire no-op
109
+ bash test/docker/verify-7-budget.sh # tool/turn budget + bash timeout
110
+ bash test/docker/verify-8-verify-command.sh # criterion.command + timeout
111
+ bash test/docker/verify-9-worktree.sh # git worktree cwd
112
+ bash test/docker/verify-10-fallback.sh # fallbackModels
113
+ bash test/docker/verify-11-nested-alias.sh # nested + alias + completionGuard
114
+ bash test/docker/verify-12-workspace.sh # pi install + child pinned to parent Space
115
+ bash test/docker/verify-13-kinds.sh # kind/model matrix; live CodeBuddy when mounted
116
+ ```
117
+
118
+ Or all at once:
119
+
120
+ ```bash
121
+ npm run test:docker
122
+ ```
123
+
124
+ `verify-13-kinds.sh` is **not** in `npm run test:docker`: it bind-mounts host
125
+ `codebuddy` / `cursor-agent` and (unless `LIVE_KIND_PROBES=0`) calls live
126
+ CodeBuddy. The numbered 1–12 scripts stay offline/mock.
127
+
128
+ Override paths with `HOST_PI_AGENT`, `DOCKER_PI_HOME`, `PI_DOCKER_IMAGE`.
129
+
130
+ Each `inside-N-*.sh` is what actually runs **in** the container. Keep those
131
+ scripts self-contained: they should still make sense if you `docker exec`
132
+ them by hand.
133
+
134
+ ## Layout
135
+
136
+ | Path | Role |
137
+ | --- | --- |
138
+ | `index.ts` | Pi extension entry: parent tool + child-guard branch |
139
+ | `src/runs/orchestrator.ts` | launch / collect / retire |
140
+ | `src/extension/child-guard.ts` | child process interceptor + task appendix |
141
+ | `src/extension/budget.ts` | tool/turn/timeout env + bash wrap |
142
+ | `src/runs/worktree.ts` | `git worktree` isolation for `worktree: true` |
143
+ | `src/extension/runtime.ts` | status widget, watch, auto-recycle |
144
+ | `src/extension/blocked.ts` | `onBlocked` confirm / auto-approve / notify |
145
+ | `src/runs/acceptance.ts` | `verification-output` command |
146
+ | `test/mock-llm/` | Scriptable OpenAI mock for offline pi / legion |
147
+ | `agents/*.md` | bundled roles |
148
+ | `docs/design.md` | measured herdr findings (F1–F46) |
149
+ | `README.md` | what is actually wired today |
150
+
151
+ Do not import from `src/` in downstream packages — use
152
+ `pi-legion/api`. If a helper needs to be public, re-export
153
+ it there and add a test that imports from the API path.
154
+
155
+ ## Agent definitions
156
+
157
+ A frontmatter key that is parsed but ignored is listed on
158
+ `subagent({ action: "list" })` as `⚠ not enforced yet`. If you start honouring
159
+ a field, **remove it from `UNENFORCED_FIELDS`** in `src/agents/agents.ts` in
160
+ the same change, and add a test that it is no longer reported.
161
+
162
+ ## Style
163
+
164
+ - TypeScript strict, `noUncheckedIndexedAccess`
165
+ - Imports use the `.ts` extension
166
+ - Node stdlib only at runtime (plus `typebox` for the tool schema)
167
+ - No `git commit` / `git push` unless the maintainer asked for it in that
168
+ conversation
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zhijiezheng
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,388 @@
1
- # Temporary Holding Version
1
+ ---
2
+ name: pi-legion
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."
5
+ ---
2
6
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
7
+ # pi-legion
8
+
9
+ A Pi extension for building recursive, resumable agent teams. A `centurion`
10
+ leads each squad; Pi RPC runs its children as headless processes, while a
11
+ shared SQLite ledger records the whole tree.
12
+
13
+ > **Name note.** This package was formerly published as
14
+ > `@zzjcool/pi-herdr-subagents` (through 0.14.3; renamed after an npm name
15
+ > collision), then as `pi-herdr-swarm` (0.15.0), before becoming `pi-legion`
16
+ > in 0.16.0. **2.0.0: the legion era — recursive centurion tree on pi RPC transport; herdr pane execution archived to legacy**
17
+
18
+ ## Contents
19
+
20
+ - [Architecture](#architecture)
21
+ - [Quick start](#quick-start)
22
+ - [Legion tools and phases](#legion-tools-and-phases)
23
+ - [Three-layer visualization](#three-layer-visualization)
24
+ - [Settings and budget gates](#settings-and-budget-gates)
25
+ - [Team 配置](#team-配置)
26
+ - [Parent/child context conventions](#parentchild-context-conventions)
27
+ - [Delivery and resume](#delivery-and-resume)
28
+ - [Herdr transition and role migration](#herdr-transition-and-role-migration)
29
+ - [Development](#development)
30
+
31
+ ## Architecture
32
+
33
+ ```text
34
+ User (CEO) — root Pi session (TUI)
35
+ ├─ pi-legion extension
36
+ │ ├─ LegionSupervisor (root)
37
+ │ │ ├─ RpcClient × N → headless `pi --mode rpc` child processes
38
+ │ │ ├─ mail postman (inbox delivery) + four-phase state machine
39
+ │ │ └─ orphan scan + child lifecycle / usage accounting
40
+ │ ├─ `legion.db` (one shared SQLite/WAL tree ledger)
41
+ │ └─ LegionEventBus → resident tree | `/legion` full view | live overlay
42
+ └─ centurion child (RpcClient; cwd = its subtree worktree)
43
+ └─ pi-legion in child mode
44
+ ├─ LegionSupervisor for this squad
45
+ ├─ `legion_mail` / `legion_tree` / `legion_phase` tools
46
+ ├─ worker / scout / reviewer … children (`worktree: false`)
47
+ └─ nested centurions → recursive squads
48
+ ```
49
+
50
+ Transport, state, decision-making, and display are separate layers:
51
+
52
+ - **Transport:** each child is a headless Pi RPC process managed by a
53
+ `LegionSupervisor`; a centurion can supervise another squad recursively.
54
+ - **State:** all nodes in the tree share the `legion.db` SQLite ledger in WAL
55
+ mode. Per-run `run.json` records and session files remain the resume source
56
+ for each child.
57
+ - **Decision:** centurions and squad members do the LLM work. The supervisor
58
+ owns machine state and validates phase transitions.
59
+ - **Display:** the event bus feeds the resident tree, the `/legion` full view,
60
+ and an on-demand live overlay.
61
+
62
+ ## Quick start
63
+
64
+ Install and load the extension in Pi:
65
+
66
+ ```bash
67
+ pi install npm:pi-legion
68
+ ```
69
+
70
+ Ask a `centurion` to lead a recursive squad, with a goal and concrete
71
+ acceptance checklist in its task:
72
+
73
+ ```text
74
+ subagent({
75
+ agent: "centurion",
76
+ task: "Goal: implement the account export flow. Acceptance: contract is recorded; implementation is reviewed; the verification commands pass; return a squash MR and verdict."
77
+ })
78
+ ```
79
+
80
+ The centurion plans the work, writes the squad contract, dispatches ordinary
81
+ roles such as `worker`, `scout`, and `reviewer`, and coordinates the squad
82
+ through its phases. For a single leaf task, dispatch an ordinary role directly:
83
+
84
+ ```text
85
+ subagent({ agent: "scout", task: "Map the export code path and report relevant files and risks." })
86
+ ```
87
+
88
+ No manual child-process launch is needed. Use `/legion` to inspect the tree;
89
+ see [Three-layer visualization](#three-layer-visualization) for navigation.
90
+
91
+ The `subagent` control actions include event-driven `wait` and graceful `stop`:
92
+ `subagent({ action: "wait", name: "worker-0" })` returns as soon as the child
93
+ settles (or the parent turn is interrupted with Esc). Aborting a parent turn
94
+ with Esc gracefully retires its tracked children, including those being waited
95
+ on or collected; the cascade is silent. To stop children explicitly, use
96
+ `subagent({ action: "stop", name: "worker-0" })` or
97
+ `subagent({ action: "stop", all: true })`; this runs the normal graceful-retire
98
+ sequence for each selected child.
99
+
100
+ ### Stopping async subagents yourself
101
+
102
+ Async children keep running after the main turn ends, so Esc-abort no longer
103
+ reaches them. Three user controls run the same graceful `stop` (a stopped
104
+ parent retires its own subtree on shutdown):
105
+
106
+ - **`/legion stop`** stops every live subagent; **`/legion stop <name>`** stops
107
+ one (`all` is an alias for no name). Names complete from the live children;
108
+ nested agents complete as `parent/name` and are reported as owned by their
109
+ parent (stop the parent to retire them).
110
+ - **Esc** while the main agent is idle, the editor is empty, no dialog or
111
+ overlay is open and subagents are live asks "Stop N running subagents?".
112
+ In every other state Esc keeps pi's normal behaviour.
113
+ - **`x`** on the selected live agent in the `/legion` view or its live
114
+ overlay asks `y` to confirm in place, then stops it.
115
+
116
+ While the main agent is idle and subagents are live, the railway header shows
117
+ a dim `esc stop all · /legion stop [name]` hint (shortened on narrow terminals,
118
+ hidden while the main agent is working).
119
+
120
+ ## Legion tools and phases
121
+
122
+ Child nodes receive these tree-aware tools:
123
+
124
+ ```text
125
+ legion_mail({ to, subject, body, kind?, urgency? })
126
+ legion_tree({ scope?: "children" | "squad" | "subtree" | "tree", depth? })
127
+ legion_phase({ to, note?, evidence? })
128
+ ```
129
+
130
+ - **`legion_mail`** sends asynchronous messages over permitted tree edges:
131
+ parent, children, or same-parent siblings. `to` may be a short name, node ID,
132
+ `parent`, `children`, or `squad`. Cross-squad direct mail and bypassing the
133
+ parent are refused; route cross-squad requests through the relevant
134
+ centurions. Keep large artifacts in the shared worktree and mail a file
135
+ pointer. The body has a 4 KB soft limit and a 32 KB hard limit.
136
+ - **`legion_tree`** is a read-only view of machine-recorded node state. It does
137
+ not interrupt the nodes being observed.
138
+ - **`legion_phase`** is available to centurions. It requests a state transition;
139
+ the supervisor checks the transition's invariants and records the result.
140
+
141
+ A squad moves through **planning → implementing → reviewing → verifying**.
142
+ Planning produces a non-empty `.pi-legion/contract.md` with file ownership
143
+ and acceptance commands. During reviewing, an independent reviewer supplies
144
+ the judgment; in verifying, the supervisor runs the contract's `legion-verify`
145
+ commands without an LLM. Passing verification advances the squad to `done`.
146
+
147
+ ## Three-layer visualization
148
+
149
+ 1. **Railway strip above the input:** one git-graph lane per live subagent
150
+ (max 4, 3 below 80 columns; oldest/shallowest first, `+N` for the rest).
151
+ When an agent ends its lane merges back and lower lanes shift up. The
152
+ header shows the live count and tok/s (trailing 60s average). Hidden when
153
+ no subagent is live. Set `PI_LEGION_STRIP=tree` for the classic roster.
154
+ 2. **`/legion` full view:** galaxy graph of the agent tree, stacked tok/s
155
+ chart by agent type (peak marker, now/avg/peak/total, per-role share), and
156
+ a scrollable `log --graph` history. Tab switches panes, arrows scroll or
157
+ move the selection, Enter selects an agent (its own rate line, tok/s,
158
+ tokens and share are overlaid), `o` opens its live overlay, `x` stops it
159
+ (confirm with `y`), Esc clears the
160
+ selection, `q`/Esc closes. Layouts: wide (≥140 cols), stacked, and portrait
161
+ (<80 cols, or tall and <100 cols). `/legion --tree` opens the classic tree
162
+ (`j`/`k`, Enter/Space fold, `/` search, PageUp/PageDown).
163
+ 3. **Live overlay:** press `o` on the selected node, use a component-routed
164
+ mouse click in the strip or classic tree, or open it directly with `/legion <name>`.
165
+ The overlay subscribes to that node's live feed only while open; Esc closes
166
+ it (returning to `/legion` when opened from there) and unsubscribes; `x`
167
+ stops the agent after a `y` confirmation. OSC 8 links are visual anchors only, not overlay links.
168
+
169
+ ## Settings and budget gates
170
+
171
+ Add the v2 `legion` block to `~/.pi/agent/settings.json` or the project
172
+ `.pi/settings.json`, merging it with any existing `subagents` keys:
173
+
174
+ ```json
175
+ {
176
+ "subagents": {
177
+ "legion": {
178
+ "maxDepth": 4,
179
+ "maxChildrenPerNode": 8,
180
+ "maxActiveNodes": 30,
181
+ "phaseTimeouts": {
182
+ "planning": 900000,
183
+ "implementing": 3600000,
184
+ "reviewing": 1800000,
185
+ "verifying": 900000
186
+ },
187
+ "verifyCommandTimeoutMs": 600000,
188
+ "mailRatePer5Min": 10,
189
+ "usagePollMs": 30000,
190
+ "orphanStaleMs": 90000
191
+ }
192
+ }
193
+ }
194
+ ```
195
+
196
+ The three launch budget gates are enforced against the tree ledger:
197
+
198
+ | Gate | Setting | Default | Limit |
199
+ | --- | --- | ---: | --- |
200
+ | Depth | `maxDepth` | 4 | Maximum tree depth; the hard cap is 4 (`MAX_NESTED_PATH_ENTRIES`). |
201
+ | Fan-out | `maxChildrenPerNode` | 8 | Direct children for one node. |
202
+ | Concurrency | `maxActiveNodes` | 30 | Whole-tree `running` + `starting` nodes. |
203
+
204
+ The effective tree depth cap is bounded by the operator override,
205
+ `subagents.legion.maxDepth`, and the hard cap of 4. **INCL semantics:** a
206
+ launched role's `maxSubagentDepth = R` counts generations in that role's
207
+ subtree, including itself; when launched by a parent at depth `d`, it inherits
208
+ `C_child = min(C_parent, d + R, 4)` (`R = 1` makes it a leaf), so it can tighten
209
+ the inherited cap but cannot widen it.
210
+
211
+ ### Team 配置
212
+
213
+ Use `subagents.team` to select the default named team from `subagents.teams`.
214
+ The `PI_SUBAGENTS_TEAM` environment variable takes precedence over
215
+ `subagents.team`; if neither selects a named team, the full discovered roster
216
+ is used. Team definitions have a description and an ordered `members` list;
217
+ each member can be a role-name string or an object with an `agent` name and
218
+ per-member overrides. Object member overrides (for example `model` or
219
+ `thinking`) take precedence over those fields in the role card's frontmatter;
220
+ omitted override fields continue to use their normal role/configuration
221
+ values.
222
+
223
+ See [docs/subagents-teams.md](docs/subagents-teams.md) for four editable
224
+ frontend/backend × premium/value team templates. The model IDs in that template
225
+ are placeholders for your provider, not required defaults.
226
+
227
+ ### Parent/child context conventions
228
+
229
+ Settings can provide `subagents.parentContext` and `subagents.childContext`
230
+ inline or through `@path` references. If either setting is omitted, Legion
231
+ looks for `subagents-parent-context.md` or `subagents-child-context.md`
232
+ respectively in the directory containing that settings file. These are
233
+ convention-based fallbacks: an explicit setting takes precedence, and setting
234
+ either field to `false` disables its context. The convention filenames are
235
+ `PARENT_CONTEXT_CONVENTION` and `CHILD_CONTEXT_CONVENTION` in
236
+ [`src/agents/settings.ts`](src/agents/settings.ts#L30-L31); missing convention
237
+ files simply contribute no context. The repository examples are
238
+ [`examples/subagents-parent-context.md`](examples/subagents-parent-context.md)
239
+ and [`examples/subagents-child-context.md`](examples/subagents-child-context.md).
240
+
241
+ ### Self-diagnosis (`/legion doctor`)
242
+
243
+ When something about subagents misbehaves (model not found, spawn exit, memory
244
+ gate, missing lens tools, orphan temps, …), ask Legion rather than guessing —
245
+ same idea as `pi list` / `pi auth check`.
246
+
247
+ - `/legion doctor` — full report in the chat (works outside TUI too).
248
+ - `/legion doctor <section>` — one of: `summary`, `extensions`, `models`,
249
+ `spawn`, `memory`, `tools`, `storage`, `runtime`, `next`.
250
+ - `subagent({ action: "diagnose" })` — same full report as tool output so the
251
+ main agent can call it without waiting for you.
252
+
253
+ The report is structured for the model: each section ends in **Issues** /
254
+ **Next steps** with copy-pasteable fixes (env exports, role frontmatter,
255
+ `/legion stop`, …). Spawn failures that look like a missing model also append
256
+ a short Hint automatically. **Gated auto-doctor:** on model-not-found,
257
+ `INSUFFICIENT_MEMORY`, extension load failures, and child exit during
258
+ startup / first prompt, the tool error itself includes a focused doctor
259
+ snippet (relevant sections + Next steps) so the main agent can self-fix
260
+ without anyone typing `/legion doctor`. Full `/legion doctor` remains for
261
+ manual deep dives.
262
+
263
+ Coverage:
264
+
265
+ | Section | What it checks |
266
+ | --- | --- |
267
+ | extensions | Host `packages`, resolved `--extension` paths, `--no-extensions` / inherit knobs, per-role effective extension list |
268
+ | models | Host vs child model lists, which package restores providers, team/roster gaps |
269
+ | spawn | Sample child argv, recent spawn failures + stderr, nested-spawn flags |
270
+ | memory | Free RAM, memory gate, child V8 flags, verify concurrency slots in use |
271
+ | tools | `/legion` commands, stop / idle-Esc / strip hints, tool names |
272
+ | storage | `legion.db` sizes, orphan `rpc-child-*` temps, last hygiene sweep marker |
273
+ | runtime | Live children, tree statuses, acceptance-ish / active nodes |
274
+
275
+ ### Child extensions
276
+ ### Child extensions
277
+
278
+ Children start with `--no-extensions`, so they do not load the host's
279
+ discovered extensions (settings `packages`, `~/.pi/agent/extensions`, project
280
+ extensions). Each extension a child loads is a memory cost multiplied by every
281
+ concurrent child; an LSP stack is ~1 GB (#43). A child still loads pi-legion
282
+ itself (guard and nested spawning) and every path listed in the role's
283
+ `extensions` / `subagentOnlyExtensions`.
284
+
285
+ | Knob | Effect |
286
+ | --- | --- |
287
+ | role frontmatter `inheritExtensions: true` | That role loads all host extensions, as before. |
288
+ | `PI_LEGION_CHILD_INHERIT_EXTENSIONS=1` | Every child loads all host extensions, as before. |
289
+ | `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. |
290
+
291
+ ### Child memory controls
292
+
293
+ | Env | Default | Effect |
294
+ | --- | ---: | --- |
295
+ | `PI_LEGION_CHILD_SEMI_SPACE_MB` | 1 | Child pi `--max-semi-space-size` (via `NODE_OPTIONS`). `0` = off. |
296
+ | `PI_LEGION_CHILD_MAX_OLD_SPACE_MB` | 1024 | Soft child heap cap `--max-old-space-size`. `0` = off. A host `NODE_OPTIONS` value wins. |
297
+ | `PI_LEGION_MIN_FREE_MEM_MB` | 1024 | Memory gate: a spawn is refused (`INSUFFICIENT_MEMORY`, plus a UI notice) when available memory minus reservations falls below this. `0` = off. |
298
+ | `PI_LEGION_CHILD_RESERVE_MB` | 160 | Per-child reservation for children still starting, so a parallel burst cannot all pass one reading. |
299
+ | `PI_LEGION_VERIFY_CONCURRENCY` | 2 | Machine-wide limit on concurrent acceptance / legion-verify commands, using slot lock files. `0` = unlimited. |
300
+ | `PI_LEGION_VERIFY_QUEUE_MAX_MS` | 1800000 | Overall queue-wait cap. After it expires the command runs without a slot. Queue wait does not count toward the command timeout. |
301
+ | `PI_LEGION_VERIFY_LOCK_DIR` | `$TMPDIR/pi-legion-verify-<uid>` | Slot directory. Set it to scope the limit more narrowly. |
302
+
303
+ The child restores the host `NODE_OPTIONS` when its extension loads, so bash
304
+ commands it runs (`tsc`, `npm test`) keep the default heap.
305
+
306
+ ### Storage hygiene
307
+
308
+ Each root pi session runs one deferred, best-effort sweep of `.pi-subagents/`
309
+ at startup. A step only touches state whose owner process is gone: runs and
310
+ `rpc-child-*` temp dirs record the owner pid (`owner.json` / `.owner`). Set any
311
+ limit to `0` to disable that step.
312
+
313
+ | Env | Default | Effect |
314
+ | --- | ---: | --- |
315
+ | `PI_LEGION_HYGIENE` | on | `0`/`off` disables the whole sweep. |
316
+ | `PI_LEGION_RUN_RETENTION_DAYS` | 14 | Delete run dirs idle this long. Runs with a `worktrees/` entry are kept as resume credentials. |
317
+ | `PI_LEGION_STALE_NODE_HOURS` | 72 | Non-root ledger nodes still `starting`/`running`/`blocked` this long after their owner died are marked `failed` (`node_abandoned` event). |
318
+ | `PI_LEGION_STALE_TEMP_HOURS` | 24 | Age threshold for legacy `rpc-child-*` dirs without an owner marker. |
319
+ | `PI_LEGION_EVENT_RETENTION_DAYS` | 30 | Drop ledger events of settled/failed nodes older than this (`orphan_detected`, `budget_refused`, `node_abandoned` are kept). |
320
+ | `PI_LEGION_EVENT_MAX_ROWS` | 200000 | Soft cap on the events table. Oldest terminal-node rows are deleted first. |
321
+
322
+ The live `legion.db` gets event retention and a `wal_checkpoint(TRUNCATE)` on
323
+ session shutdown. A large delete is followed by `VACUUM`, which is skipped when
324
+ another connection holds the db.
325
+
326
+ ## Delivery and resume
327
+
328
+ A centurion owns a Git worktree for its subtree; its worker/scout/reviewer
329
+ children work in that subtree, and a nested centurion gets a nested worktree.
330
+ Each subtree is delivered as a squash MR to its parent level. MRs move upward
331
+ through the tree until the root reviews and merges into the default branch.
332
+
333
+ Resuming follows the tree's saved sessions and node records. If a supervisor
334
+ process ends, its child processes do not become detached workers; resuming the
335
+ parent restores the squad, while already-settled children are not restarted.
336
+ Keep the run records and session files while the tree may need to resume.
337
+
338
+ ## Herdr transition and role migration
339
+
340
+ Version **0.16.x is the final Herdr-era line**. Pi Legion 2.x is RPC-only:
341
+ child execution uses headless `pi --mode rpc` processes, and the legacy backend
342
+ is archived under `src/backends/legacy/`, not offered as a v2 runtime option.
343
+ Pin to `pi-legion` 0.16.x if a workflow must stay on the former Herdr-based
344
+ execution model.
345
+
346
+ Pi Legion 2.x supports `kind: pi` only. Migrate a custom role that uses another
347
+ CLI kind by changing its frontmatter, for example:
348
+
349
+ ```yaml
350
+ ---
351
+ name: search
352
+ kind: pi
353
+ ---
354
+ ```
355
+
356
+ A non-Pi kind is rejected by v2. If that role must continue to run through
357
+ another CLI, keep it on the 0.16.x line or replace it with a Pi role.
358
+
359
+ ## Development
360
+
361
+ ```bash
362
+ npm install
363
+ npm run typecheck
364
+ npm test # unit tests
365
+ npm run test:integration # integration tests
366
+ npm run test:all # typecheck + unit + integration tests
367
+ ```
368
+
369
+ ### Mock LLM (offline pi / legion smoke)
370
+
371
+ A scriptable OpenAI-compatible server lives under [`test/mock-llm/`](test/mock-llm/README.md).
372
+ It registers with Pi like any local gateway (`models.json` → `api: "openai-completions"`),
373
+ so spawn / stop / doctor / verify paths can run without real API keys:
374
+
375
+ ```bash
376
+ npm run mock-llm # http://127.0.0.1:8765/v1 scenario=echo
377
+ npm run test:mock-llm # unit + integration smoke for the mock
378
+ MOCK_LLM_SCENARIO=spawn-scout npm run mock-llm
379
+ ```
380
+
381
+ Use `writeMockPiHome()` from `test/mock-llm` to generate an isolated
382
+ `PI_CODING_AGENT_DIR` with dummy `apiKey: "mock"` only — never copy host
383
+ `auth.json` into fixtures.
384
+
385
+ See [CONTRIBUTING.md](CONTRIBUTING.md#project-workflow) for the project
386
+ workflow: issues, pull requests, the merge gate, and npm releases.
387
+
388
+ MIT. See [LICENSE](LICENSE).