pi-legion 0.16.0 → 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.
- package/CONTRIBUTING.md +58 -0
- package/README.md +313 -640
- package/agents/advisor.md +7 -5
- package/agents/centurion.md +53 -0
- package/agents/designer.md +33 -33
- package/agents/planner.md +1 -2
- package/agents/prototype.md +2 -2
- package/agents/reviewer.md +67 -31
- package/agents/scout.md +6 -2
- package/agents/worker.md +12 -3
- package/docs/contracts/esc-abort-v1.md +131 -0
- package/docs/contracts/esc-cascade-v2.md +100 -0
- package/docs/design.md +44 -25
- package/docs/legion-v2-contract.md +461 -0
- package/docs/legion-v2-delivery.md +62 -0
- package/docs/retire-hardening-contract.md +125 -0
- package/docs/subagents-teams.md +76 -0
- package/docs/tui-viewer-contract.md +276 -0
- package/docs/vexillatio-contract.md +162 -0
- package/index.ts +1462 -286
- package/package.json +12 -9
- package/skills/pi-legion/SKILL.md +74 -96
- package/src/agents/agents.ts +6 -11
- package/src/agents/overrides.ts +2 -1
- package/src/agents/paths.ts +1 -1
- package/src/agents/settings.ts +268 -20
- package/src/api.ts +47 -48
- package/src/{herdr → backends/legacy}/client.ts +14 -69
- package/src/{shared → backends/legacy}/cursor-chat.ts +2 -2
- package/src/{herdr → backends/legacy}/runner.ts +22 -9
- package/src/backends/legacy/types.ts +138 -0
- package/src/extension/auto-doctor.ts +144 -0
- package/src/extension/budget.ts +2 -2
- package/src/extension/child-guard.ts +214 -201
- package/src/extension/event-bus.ts +263 -0
- package/src/extension/legion-commands.ts +719 -0
- package/src/extension/legion-doctor.ts +888 -0
- package/src/extension/legion-stop.ts +150 -0
- package/src/extension/legion-tools.ts +816 -0
- package/src/extension/notify.ts +24 -19
- package/src/extension/playbook.ts +20 -89
- package/src/extension/recycle.ts +2 -7
- package/src/extension/runtime.ts +655 -105
- package/src/extension/scout-guard.ts +483 -0
- package/src/extension/slash.ts +89 -438
- package/src/extension/spawn-hints.ts +79 -0
- package/src/extension/summary.ts +17 -58
- package/src/legion/budget.ts +165 -0
- package/src/legion/db.ts +242 -0
- package/src/legion/events.ts +150 -0
- package/src/legion/mail.ts +412 -0
- package/src/legion/nodes.ts +461 -0
- package/src/legion/rebuild.ts +533 -0
- package/src/legion/state-machine.ts +313 -0
- package/src/legion/usage.ts +101 -0
- package/src/runs/acceptance.ts +82 -25
- package/src/runs/args.ts +51 -5
- package/src/runs/hygiene.ts +335 -0
- package/src/runs/orchestrator.ts +790 -1683
- package/src/runs/owner.ts +61 -0
- package/src/runs/provision.ts +135 -0
- package/src/runs/store.ts +44 -17
- package/src/runs/verify-queue.ts +195 -0
- package/src/shared/name.ts +29 -0
- package/src/shared/output-cap.ts +57 -0
- package/src/shared/progress.ts +57 -135
- package/src/shared/session.ts +243 -171
- package/src/shared/types.ts +107 -208
- package/src/supervisor/child-memory.ts +140 -0
- package/src/supervisor/proc-tree.ts +103 -0
- package/src/supervisor/rpc-supervisor.ts +1033 -0
- package/src/supervisor/types.ts +153 -0
- package/src/supervisor/ui-proxy.ts +226 -0
- package/src/tui/chat-items.ts +330 -0
- package/src/tui/live-overlay.ts +874 -0
- package/src/tui/status.ts +553 -27
- package/src/tui/tree-view.ts +808 -0
- package/src/tui/viz/canvas.ts +119 -0
- package/src/tui/viz/layout.ts +111 -0
- package/src/tui/viz/model.ts +123 -0
- package/src/tui/viz/palette.ts +188 -0
- package/src/tui/viz/panels.ts +530 -0
- package/src/tui/viz/railway.ts +313 -0
- package/src/tui/viz/rate.ts +211 -0
- package/src/tui/viz/view.ts +228 -0
- package/src/extension/parent-label.ts +0 -115
- package/src/profiles/classify.ts +0 -375
- package/src/profiles/profiles.ts +0 -730
- package/src/runs/layout.ts +0 -258
package/README.md
CHANGED
|
@@ -1,715 +1,388 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pi-legion
|
|
3
3
|
npm: "pi-legion"
|
|
4
|
-
description: "
|
|
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
5
|
---
|
|
6
6
|
|
|
7
7
|
# pi-legion
|
|
8
8
|
|
|
9
|
-
A
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
A herdr subagent is a **supervised live process**, not a one-shot function call.
|
|
13
|
-
It stays resident across turns, can be steered mid-flight, resumed after its
|
|
14
|
-
pane is gone, and stays visible to you. The parent session never has to invent
|
|
15
|
-
`herdr pane split` / `herdr agent start` — one `subagent` tool call is the
|
|
16
|
-
whole launch path.
|
|
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.
|
|
17
12
|
|
|
18
13
|
> **Name note.** This package was formerly published as
|
|
19
|
-
> `@zzjcool/pi-herdr-subagents` (through 0.14.3; renamed after an npm
|
|
20
|
-
>
|
|
21
|
-
>
|
|
22
|
-
> swarm). It is now `pi-legion`, first published as 0.16.0 (continuing
|
|
23
|
-
> the version line). To migrate: `pi uninstall` the old package, then
|
|
24
|
-
> `pi install npm:pi-legion`.
|
|
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**
|
|
25
17
|
|
|
26
18
|
## Contents
|
|
27
19
|
|
|
28
|
-
- [
|
|
29
|
-
- [Install](#install)
|
|
30
|
-
- [What the plugin does for you](#what-the-plugin-does-for-you)
|
|
20
|
+
- [Architecture](#architecture)
|
|
31
21
|
- [Quick start](#quick-start)
|
|
32
|
-
- [
|
|
33
|
-
- [
|
|
34
|
-
- [
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
37
|
-
- [
|
|
38
|
-
- [
|
|
39
|
-
- [Custom agents](#custom-agents)
|
|
40
|
-
- [Isolation](#isolation)
|
|
41
|
-
- [Success and failure](#success-and-failure)
|
|
42
|
-
- [Programmatic API](#programmatic-api)
|
|
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)
|
|
43
29
|
- [Development](#development)
|
|
44
|
-
- [Known limitations](#known-limitations)
|
|
45
|
-
|
|
46
|
-
## Requirements
|
|
47
|
-
|
|
48
|
-
- **Pi** (the coding agent that loads this package as an extension)
|
|
49
|
-
- **Herdr**, with the parent session running **inside** a Herdr pane
|
|
50
|
-
(`HERDR_ENV=1`). The plugin talks to herdr to create tabs/panes and start
|
|
51
|
-
child agents; it will not launch anything from a plain terminal Pi.
|
|
52
|
-
- Node.js 22+ if you are developing the package itself
|
|
53
30
|
|
|
54
|
-
|
|
55
|
-
The parent model must not either — the plugin blocks that ritual and checks
|
|
56
|
-
herdr on every launch.
|
|
31
|
+
## Architecture
|
|
57
32
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
62
48
|
```
|
|
63
49
|
|
|
64
|
-
|
|
50
|
+
Transport, state, decision-making, and display are separate layers:
|
|
65
51
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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.
|
|
69
61
|
|
|
70
|
-
|
|
62
|
+
## Quick start
|
|
63
|
+
|
|
64
|
+
Install and load the extension in Pi:
|
|
71
65
|
|
|
72
66
|
```bash
|
|
73
|
-
pi install
|
|
67
|
+
pi install npm:pi-legion
|
|
74
68
|
```
|
|
75
69
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
`prototype`) are available
|
|
79
|
-
with no extra files to copy — see [Bundled roles](#bundled-roles).
|
|
80
|
-
|
|
81
|
-
To update a git install: `pi update`. To switch from a path/git install to npm
|
|
82
|
-
once a release is published, remove the old package and `pi install npm:pi-legion`.
|
|
83
|
-
|
|
84
|
-
## What the plugin does for you
|
|
85
|
-
|
|
86
|
-
These used to be “please remember this in the prompt.” They are now
|
|
87
|
-
**scripts**. Neither the parent model nor the child has to cooperate for them
|
|
88
|
-
to hold.
|
|
89
|
-
|
|
90
|
-
| Concern | What happens |
|
|
91
|
-
| --- | --- |
|
|
92
|
-
| Launch | One `subagent` call creates the type tab, splits a pane, starts `pi`, and begins watching. Same agent type shares one tab (each child is a pane). |
|
|
93
|
-
| Status | Running children are painted **above the parent input** with model (`:thinking` when set), kind (if not `pi`), turn / in-flight tool, and worktree branch. Elapsed time keeps ticking after the tool call returns. Non-pi kinds (cursor, claude, …) fill the same slots from `agent get` labels / pane title when jsonl is absent. |
|
|
94
|
-
| Completion | When a turn finishes, the plugin injects `Background task completed: **name**` into the parent. If the parent is idle it wakes immediately; if it is still in a tool loop, the notice waits (`followUp`) instead of steering mid-turn. Every finish is shown in the transcript: a completed child renders as one truncated row (`✓ name · success · acceptance · size`, clamped to the live pane width), failures/stops keep the full block, and `ctrl+o` expands a collapsed row. |
|
|
95
|
-
| Recycle | Terminal children get `ctrl+d`, then pane close (and the type tab when it is empty). You do not `retire` or close panes. `blocked` is the exception — the pane stays. |
|
|
96
|
-
| Child must not ping the parent | Every child Pi loads a **child-guard** extension. `herdr agent prompt/wait/send-keys/start`, pane split, tab create/close, and reading someone else’s pane are blocked. |
|
|
97
|
-
| Task-card boilerplate | A frozen appendix is appended to every child task: no wakeup, no nested agents, end with `{"ok": true\|false, "reason": "..."}`. |
|
|
98
|
-
| Read-only roles | `acceptance.role: read-only` (scout, reviewer, …) may still have `bash` for `rg` / `git log` / `ls`, but writes (`rm`, `git commit`, `echo > file`, `npm install`, `sed -i`, …) are blocked. |
|
|
99
|
-
| Acceptance | `{"ok": true}` is only **attested**. If a required criterion lists `evidence: [verification-output]` **and** carries an explicit `command`, collect then runs that command in the child’s cwd and promotes the result to **verified** or rejects it. Without a `command` the result stays attested with the criterion surfaced as pending — the parent confirms it from the child’s pasted output; nothing is guessed. |
|
|
100
|
-
| Tool approval (`blocked`) | `onBlocked: auto-approve` (**default**; a subagent pane has no human, so an approval dialog is a deadlock) silently approves a blocked child via `send-keys y` and keeps watching. `forward` pops a **confirm** in the parent TUI (Yes → `send-keys y`, No → deny; no TUI → a notify is queued). `notify` only notifies. The default is also what cursor children launch with: `--force` (Run Everything) unless the agent sets `forward`/`notify` — set `subagents.defaultOnBlocked` in settings.json to change the default globally. |
|
|
101
|
-
| Late `collect` / `retire` | If watch already collected, `collect` returns the cached snapshot. If the pane is already gone, `retire` is a no-op that points at the session file. |
|
|
102
|
-
|
|
103
|
-
What is still a **judgment** (and must stay one):
|
|
104
|
-
|
|
105
|
-
- whether to spawn a child, which role, and what the task should say
|
|
106
|
-
- what to do with the completion (accept, `steer`, `continue`, ignore)
|
|
107
|
-
- whether a semantic checklist item like “no secrets in the diff” actually holds
|
|
108
|
-
|
|
109
|
-
## Quick start
|
|
110
|
-
|
|
111
|
-
Use the `subagent` tool. **Do not run herdr CLI or env checks first.** Do not
|
|
112
|
-
tell the child to prompt the parent.
|
|
70
|
+
Ask a `centurion` to lead a recursive squad, with a goal and concrete
|
|
71
|
+
acceptance checklist in its task:
|
|
113
72
|
|
|
114
73
|
```text
|
|
115
74
|
subagent({
|
|
116
|
-
agent: "
|
|
117
|
-
task: "
|
|
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."
|
|
118
77
|
})
|
|
119
78
|
```
|
|
120
79
|
|
|
121
|
-
The
|
|
122
|
-
|
|
123
|
-
|
|
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:
|
|
124
83
|
|
|
125
84
|
```text
|
|
126
|
-
subagent({ agent: "
|
|
127
|
-
subagent({ agent: "worker", task: "<hotfix>", worktree: false }) // edit this checkout
|
|
85
|
+
subagent({ agent: "scout", task: "Map the export code path and report relevant files and risks." })
|
|
128
86
|
```
|
|
129
87
|
|
|
130
|
-
|
|
88
|
+
No manual child-process launch is needed. Use `/legion` to inspect the tree;
|
|
89
|
+
see [Three-layer visualization](#three-layer-visualization) for navigation.
|
|
131
90
|
|
|
132
|
-
|
|
133
|
-
subagent({
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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.
|
|
139
99
|
|
|
140
|
-
|
|
100
|
+
### Stopping async subagents yourself
|
|
141
101
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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):
|
|
145
105
|
|
|
146
|
-
|
|
147
|
-
|
|
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.
|
|
148
115
|
|
|
149
|
-
|
|
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).
|
|
150
119
|
|
|
151
|
-
|
|
152
|
-
| --- | --- |
|
|
153
|
-
| *(omitted)* / `launch` | Start one child (`agent` + `task`) or several (`tasks[]`) |
|
|
154
|
-
| `steer` | Prompt a live child; it drops the current turn and takes the new message |
|
|
155
|
-
| `continue` | Prompt a live child without abandoning the current turn |
|
|
156
|
-
| `resume` | Relaunch from the session file if the pane is already gone |
|
|
157
|
-
| `status` | Show the persisted record (state, pane, session path, last execution) |
|
|
158
|
-
| `collect` | Wait for the current turn (or return the cache if watch already did) |
|
|
159
|
-
| `list` | Roles the parent can spawn, with provenance (`builtin` / `user` / `project`) |
|
|
160
|
-
| `retire` | Close the pane. After auto-recycle this is a documented no-op |
|
|
161
|
-
|
|
162
|
-
Useful launch fields: `model`, `preset`, `cwd`, `placement` (`split-down` / `split-right` /
|
|
163
|
-
`new-tab`), `worktree` (`true` = isolated branch + child opens an MR; `false` =
|
|
164
|
-
edit the current checkout; omit = role default), `agentScope` (`user` /
|
|
165
|
-
`project` / `both`), `async`.
|
|
166
|
-
|
|
167
|
-
Handles look like `reviewer-0`. Every loaded role (builtin plus
|
|
168
|
-
`~/.pi/agent/agents` and project `.pi/agents`) is injected into the parent
|
|
169
|
-
system prompt each turn — that is how the model knows to call `search` without
|
|
170
|
-
`action=list` first. `status` / `collect` take `name`.
|
|
171
|
-
|
|
172
|
-
## Bundled roles
|
|
173
|
-
|
|
174
|
-
Five role definitions ship inside the package and load with **zero setup**.
|
|
175
|
-
They are anchored to the package itself (not to `~/.pi/agent/agents`), because
|
|
176
|
-
Pi’s package resource conventions have no `agents/` slot — a manifest entry
|
|
177
|
-
alone would be silently ignored.
|
|
178
|
-
|
|
179
|
-
| Role | Purpose | Acceptance |
|
|
180
|
-
| --- | --- | --- |
|
|
181
|
-
| `scout` | Read-only reconnaissance: map code, conventions, environment. Facts only. | attested, **read-only** bash |
|
|
182
|
-
| `planner` | Turn a task into a parallelisable, verifiable plan; freeze interfaces. | attested |
|
|
183
|
-
| `worker` | Implement a frozen plan on an isolated worktree; open an MR; run tests; self-report a verdict. | **verified** via the project’s own verifier |
|
|
184
|
-
| `reviewer` | Read-only adversarial review; findings with path + severity. | attested, **read-only** bash |
|
|
185
|
-
| `advisor` | Decision counsel and final arbitration on contested options (A-vs-B rulings). Grok via cursor. | attested |
|
|
186
|
-
| `designer` | Front-end UI/visual/interaction design: design tokens, full-state matrices, and the component implementation. UI layer only — business logic stays with `worker`. | **verified** via build |
|
|
187
|
-
| `prototype` | Single-file clickable HTML prototypes to validate an interaction flow before real implementation. | attested, writer |
|
|
188
|
-
|
|
189
|
-
Bundled roles do **not** pin a vendor model. A child uses the parent session’s
|
|
190
|
-
model unless you set `subagents.defaultModel`,
|
|
191
|
-
`subagents.agentOverrides.<name>.model`, assign a named
|
|
192
|
-
[preset](#presets-kindmodelthinking), pass `model` on the tool call, or
|
|
193
|
-
[load a model profile](#model-profiles-cheap--medium--strong).
|
|
194
|
-
|
|
195
|
-
## Presets (kind+model+thinking)
|
|
196
|
-
|
|
197
|
-
Named bundles in settings, referenced from agent frontmatter (`preset: strong`),
|
|
198
|
-
from `agentOverrides.<name>.preset`, or from the tool’s `preset` param (single
|
|
199
|
-
launch and `tasks[]` both accept it):
|
|
200
|
-
|
|
201
|
-
```json
|
|
202
|
-
{
|
|
203
|
-
"subagents": {
|
|
204
|
-
"presets": {
|
|
205
|
-
"cheap": { "kind": "pi", "model": "cb/deepseek-v4.1-flash", "thinking": "low" },
|
|
206
|
-
"coder": { "kind": "pi", "model": "cb/deepseek-v4.1-flash", "thinking": "max" },
|
|
207
|
-
"medium": { "kind": "pi", "model": "cb/glm-5.3", "thinking": "high" },
|
|
208
|
-
"strong": { "kind": "pi", "model": "cb/kimi-k3", "thinking": "max" },
|
|
209
|
-
"strongest": { "kind": "cursor", "model": "grok-4.6", "thinking": "xhigh" }
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
```
|
|
120
|
+
## Legion tools and phases
|
|
214
121
|
|
|
215
|
-
|
|
216
|
-
benchmarks before adopting) aligns each role with what its model is measured
|
|
217
|
-
best at, not with price-tier folklore: cheap flash for scouting, a coder-tier
|
|
218
|
-
flash for implementation (same-card benchmarks had it out-coding flagships at
|
|
219
|
-
1/20 the price), a reasoning-strong model for adversarial review, and grok for
|
|
220
|
-
decision arbitration.
|
|
221
|
-
|
|
222
|
-
Model precedence, strongest first:
|
|
122
|
+
Child nodes receive these tree-aware tools:
|
|
223
123
|
|
|
224
124
|
```text
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
→ agentOverrides.<name>
|
|
229
|
-
→ agent frontmatter `model`
|
|
230
|
-
→ subagents.defaultModel
|
|
231
|
-
→ the dispatching (parent) session model
|
|
125
|
+
legion_mail({ to, subject, body, kind?, urgency? })
|
|
126
|
+
legion_tree({ scope?: "children" | "squad" | "subtree" | "tree", depth? })
|
|
127
|
+
legion_phase({ to, note?, evidence? })
|
|
232
128
|
```
|
|
233
129
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
`
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
the
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
```text
|
|
279
|
-
/subagents-refresh-provider-models <provider>
|
|
280
|
-
/subagents-generate-profiles <provider>
|
|
281
|
-
/subagents-load-profile <provider>.quota
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
That writes two profiles (`<provider>.quota` leans cheaper,
|
|
285
|
-
`<provider>.quality` leans stronger) under
|
|
286
|
-
`~/.pi/agent/profiles/pi-legion/` and, on load, copies
|
|
287
|
-
`agentOverrides` into `~/.pi/agent/settings.json`. Project `.pi/settings.json`
|
|
288
|
-
still wins on overlapping keys.
|
|
289
|
-
|
|
290
|
-
Other commands: `/subagents-profiles` lists saved profiles;
|
|
291
|
-
`/subagents-check-profile <name>` re-checks each assigned model against the
|
|
292
|
-
current registry (and a live probe unless you pass `--no-probe`). Refresh and
|
|
293
|
-
generate accept `--force` and `--no-probe`.
|
|
294
|
-
|
|
295
|
-
`/subagents-agents [user|project|both]` lists the roles available to the
|
|
296
|
-
`subagent` tool in the CURRENT session — the human-facing version of the
|
|
297
|
-
roster injected into the model's system prompt, plus what a launch would
|
|
298
|
-
actually resolve:
|
|
299
|
-
|
|
300
|
-
```text
|
|
301
|
-
builtin (7)
|
|
302
|
-
advisor
|
|
303
|
-
决策顾问:……
|
|
304
|
-
kind=cursor · alias: ask, consult, oracle · model: (agent CLI default)
|
|
305
|
-
file: /pkg/agents/advisor.md
|
|
306
|
-
scout
|
|
307
|
-
侦察员:……
|
|
308
|
-
model: cb/glm-5.3 (agentOverrides)
|
|
309
|
-
file: ~/.pi/agent/agents/scout.md
|
|
310
|
-
project (1)
|
|
311
|
-
deploy
|
|
312
|
-
deploys things
|
|
313
|
-
model: cb/kimi-k3 (the parent session model)
|
|
314
|
-
file: <repo>/.pi/agents/deploy.md
|
|
315
|
-
|
|
316
|
-
Directories
|
|
317
|
-
builtin: /pkg/agents
|
|
318
|
-
user: ~/.pi/agent/agents
|
|
319
|
-
project: /repo/.pi/agents
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
Each role shows its layer (`builtin` / `user` / `project`), the model a launch
|
|
323
|
-
would actually resolve — with the same provenance labels and refusal checks
|
|
324
|
-
the launch path uses (`the agent's frontmatter`, `agentOverrides`, `the
|
|
325
|
-
preset`, `the parent session model`, …; `✗ outside model scope`, `✗ cannot
|
|
326
|
-
be used with kind 'cursor'`, or `dropped at launch` for a model the target
|
|
327
|
-
CLI cannot express) — plus kind, aliases, and the definition file, so a role
|
|
328
|
-
that is not doing what you expect can be traced to the exact file that
|
|
329
|
-
defines it in one step. The optional scope argument mirrors the tool's
|
|
330
|
-
`agentScope` (`user` by default; `project` skips the user layers, the bundled
|
|
331
|
-
roles always load); it re-reads agent files on every invocation, so edits
|
|
332
|
-
show up immediately.
|
|
333
|
-
|
|
334
|
-
`/subagents-summary` renders a Markdown table of the current parent pane's
|
|
335
|
-
subagent runs, including role outcomes, turns, token usage, cost, and cumulative
|
|
336
|
-
agent-time. Use `/subagents-summary --all` to include runs from every parent
|
|
337
|
-
pane in the current working directory, or `/subagents-summary <name>` for one
|
|
338
|
-
child's detailed state, timing, usage, and session path. Child names are
|
|
339
|
-
available through command completion.
|
|
340
|
-
|
|
341
|
-
you can still hand-write the same mapping:
|
|
342
|
-
|
|
343
|
-
```json
|
|
344
|
-
{
|
|
345
|
-
"subagents": {
|
|
346
|
-
"agentOverrides": {
|
|
347
|
-
"scout": { "model": "your-provider/fast-model" },
|
|
348
|
-
"planner": { "model": "your-provider/mid-model" },
|
|
349
|
-
"worker": { "model": "your-provider/strong-coder" },
|
|
350
|
-
"reviewer": { "model": "your-provider/strong-reasoner" },
|
|
351
|
-
"designer": { "model": "your-provider/strong-model" }
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
## Master switch
|
|
358
|
-
|
|
359
|
-
Two levels, both temporary-by-design:
|
|
360
|
-
|
|
361
|
-
- **Session** — `/subagents-toggle` flips the switch for the current session
|
|
362
|
-
only. No files change; `/reload` restores whatever the settings say.
|
|
363
|
-
`/subagents-toggle` (no args) shows the current state; `on`/`off` are
|
|
364
|
-
explicit; the status line also warns how many children are still running.
|
|
365
|
-
- **Project / user settings** — `"subagents": { "enabled": false }` in
|
|
366
|
-
`<repo>/.pi/settings.json` (project wins over `~/.pi/agent/settings.json`)
|
|
367
|
-
persists the off state across sessions.
|
|
368
|
-
|
|
369
|
-
While disabled:
|
|
370
|
-
|
|
371
|
-
- New launches are refused with a pointer to `/subagents-toggle on`.
|
|
372
|
-
- The system-prompt roster/playbook injection stops (next turn), so the model
|
|
373
|
-
is not coached toward a tool that refuses.
|
|
374
|
-
- The bash guard stops blocking manual `herdr` commands — you are free to
|
|
375
|
-
drive herdr by hand.
|
|
376
|
-
- **Existing children are untouched**: they keep running and notify normally,
|
|
377
|
-
and `status`/`collect`/`wait`/`resume`/`steer` keep working, so a disable
|
|
378
|
-
mid-flight stays observable.
|
|
379
|
-
|
|
380
|
-
## Settings
|
|
381
|
-
|
|
382
|
-
Read from `~/.pi/agent/settings.json` (user) and `<project>/.pi/settings.json`
|
|
383
|
-
(project). Project wins on overlap.
|
|
384
|
-
|
|
385
|
-
> **herdr sidebar**: while async children are live, the parent pane is annotated
|
|
386
|
-
> `idle=⏳ N subagents (…)` via `pane report-metadata --state-label` (TTL'd, self
|
|
387
|
-
> clearing). herdr's own integration reports the parent `idle` the moment its
|
|
388
|
-
> turn settles — the label is the visible "waiting on subagents" cue in the
|
|
389
|
-
> sidebar. No setting; automatic when running inside herdr.
|
|
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:
|
|
390
173
|
|
|
391
174
|
```json
|
|
392
175
|
{
|
|
393
176
|
"subagents": {
|
|
394
|
-
"
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
"
|
|
399
|
-
"
|
|
400
|
-
"
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
"
|
|
407
|
-
|
|
408
|
-
"agentOverridesByProvider": {
|
|
409
|
-
"cb": { "worker": { "model": "cb/glm-5.3" } }
|
|
410
|
-
},
|
|
411
|
-
"modelScope": { "allow": ["cb/*", "openai/*"] },
|
|
412
|
-
"herdr": {
|
|
413
|
-
"defaultPlacement": "split-down",
|
|
414
|
-
"maxConcurrentAgents": 6,
|
|
415
|
-
"startRetries": 40,
|
|
416
|
-
"startRetryBackoffMs": 150,
|
|
417
|
-
"sessionRetentionDays": 7
|
|
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
|
|
418
191
|
}
|
|
419
192
|
}
|
|
420
193
|
}
|
|
421
194
|
```
|
|
422
195
|
|
|
423
|
-
|
|
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 |
|
|
424
266
|
| --- | --- |
|
|
425
|
-
| `
|
|
426
|
-
|
|
|
427
|
-
|
|
|
428
|
-
|
|
|
429
|
-
|
|
|
430
|
-
|
|
|
431
|
-
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
a
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
|
506
350
|
---
|
|
507
|
-
name:
|
|
508
|
-
description: Read-only paper/code survey
|
|
509
|
-
tools: read, grep, find, ls, bash
|
|
510
|
-
timeoutMs: 600000
|
|
511
|
-
acceptance:
|
|
512
|
-
level: attested
|
|
513
|
-
role: read-only
|
|
351
|
+
name: search
|
|
514
352
|
kind: pi
|
|
515
|
-
placement: split-down
|
|
516
|
-
steer: true
|
|
517
|
-
onBlocked: forward
|
|
518
353
|
---
|
|
519
|
-
|
|
520
|
-
You are a read-only researcher. Facts with paths. No edits.
|
|
521
354
|
```
|
|
522
355
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
`description`. Useful optional fields: `model`, `preset`, `thinking`, `tools`, `skills`,
|
|
526
|
-
`timeoutMs`, `placement`, `onBlocked` (`auto-approve` default / `forward` / `notify`),
|
|
527
|
-
`acceptance.role` (`read-only` / `writer`), `acceptance.criteria` with
|
|
528
|
-
`evidence: [verification-output]` (and optional `command:`) if collect should
|
|
529
|
-
run a check, `worktree`, `toolBudget` / `turnBudget` / `toolTimeoutMs`,
|
|
530
|
-
`fallbackModels`, `alias`, `completionGuard`, `allowNestedSubagents`.
|
|
531
|
-
|
|
532
|
-
If `subagent({ action: "list" })` prints `⚠ not enforced yet: …`, that key is
|
|
533
|
-
parsed but inert. A listed key that does nothing is worse than an unknown key
|
|
534
|
-
— if you see that warning, the field is decoration. The shipped roles currently
|
|
535
|
-
report none.
|
|
536
|
-
|
|
537
|
-
## Isolation
|
|
538
|
-
|
|
539
|
-
Herdr panes are **one trust domain**. A child can in principle `herdr pane
|
|
540
|
-
read` any pane in the session (measured: F23). We now **enforce** a subset of
|
|
541
|
-
that discipline; the rest is still on you.
|
|
542
|
-
|
|
543
|
-
**Enforced in the child process**
|
|
544
|
-
|
|
545
|
-
- `herdr agent prompt|wait|send-keys|start`
|
|
546
|
-
- `herdr pane split`, `herdr tab create|close`
|
|
547
|
-
- `herdr pane read|close` of a pane that is not this child’s
|
|
548
|
-
- for `acceptance.role: read-only`: filesystem writes, `git` mutations,
|
|
549
|
-
package-manager installs, `sed -i`, redirects onto files
|
|
550
|
-
- `toolBudget.maxToolCalls` / `turnBudget.maxTurns` (further tool calls are
|
|
551
|
-
blocked); `toolTimeoutMs` wraps bash with GNU `timeout`
|
|
552
|
-
- `worktree: true` → `git worktree add -b` under the run dir (named
|
|
553
|
-
`pi-subagent/<name>-…` branch). The pane’s cwd is that tree. The **parent**
|
|
554
|
-
decides per launch (`subagent({ worktree: true|false })`); omitting it uses
|
|
555
|
-
the role default. Writer roles (`acceptance.role: writer`, including bundled
|
|
556
|
-
`worker`) default this on so concurrent parent Pis do not share a dirty
|
|
557
|
-
checkout; the child commits on the branch and opens an MR. `worktree: false`
|
|
558
|
-
opts out. Retire leaves the tree on disk. Requires a git repository.
|
|
559
|
-
- child tabs are pinned to the **parent herdr Space**
|
|
560
|
-
(`HERDR_WORKSPACE_ID` → `tab create --workspace`). A focused Space elsewhere
|
|
561
|
-
cannot steal the child, and adopt will not reuse a same-label tab in another
|
|
562
|
-
Space.
|
|
563
|
-
|
|
564
|
-
**Still not a sandbox**
|
|
565
|
-
|
|
566
|
-
- `HERDR_SOCKET_PATH` cannot be overridden per pane (F24)
|
|
567
|
-
- a true isolated session would break the visible/steerable model (F25)
|
|
568
|
-
- never put secrets on screen in any pane while children run
|
|
569
|
-
- owner tokens: recycle only panes the tree registered; skip + warn otherwise
|
|
570
|
-
|
|
571
|
-
If a task genuinely needs isolation, run it **outside** herdr (plain `pi`, no
|
|
572
|
-
`HERDR_ENV`), rather than pretending panes are sandboxed.
|
|
573
|
-
|
|
574
|
-
## Success and failure
|
|
575
|
-
|
|
576
|
-
**herdr `agent_status` cannot tell you whether an agent succeeded.** Success,
|
|
577
|
-
model error, and kill all report `done`. The plugin derives outcome from the
|
|
578
|
-
child’s session jsonl (`stopReason` of the last turn) and from the
|
|
579
|
-
machine-readable verdict.
|
|
580
|
-
|
|
581
|
-
Two orthogonal dimensions — never conflate them:
|
|
582
|
-
|
|
583
|
-
- **Lifecycle** (`launching → working → awaiting → blocked → retired/exited`):
|
|
584
|
-
is the process alive?
|
|
585
|
-
- **Execution** (`success / failed / aborted / truncated / running`): did the
|
|
586
|
-
task succeed?
|
|
587
|
-
|
|
588
|
-
`state: retired` + `execution.status: failed` is a normal combination.
|
|
589
|
-
|
|
590
|
-
| Session signal | Execution |
|
|
591
|
-
| --- | --- |
|
|
592
|
-
| Last turn `stopReason: "stop"` | success |
|
|
593
|
-
| `stopReason: "error"` without “aborted” | failed |
|
|
594
|
-
| `stopReason: "error"` + “This operation was aborted” | aborted |
|
|
595
|
-
| Last user prompt with **no** assistant message | aborted (killed mid-turn) |
|
|
596
|
-
| Last assistant `stopReason: "toolUse"` and the agent is gone | aborted |
|
|
597
|
-
| Last assistant `toolUse` and herdr says `blocked` | not terminal — confirm / wait |
|
|
598
|
-
| `stopReason: "length"` | truncated |
|
|
599
|
-
| No messages | unknown |
|
|
600
|
-
|
|
601
|
-
On top of that:
|
|
602
|
-
|
|
603
|
-
- **Only the last turn decides execution.** A tool error in turn 1 followed by
|
|
604
|
-
a clean turn 2 is overall success. `toolErrors` is diagnostic only.
|
|
605
|
-
- **Self-report is attested, not verified.** `{"ok": true}` is the agent
|
|
606
|
-
marking its own homework. Worker’s `verification-output` criterion is the
|
|
607
|
-
exception: when it carries an explicit `command`, the plugin runs it itself,
|
|
608
|
-
with a timeout; without one, the criterion is surfaced as pending for the
|
|
609
|
-
parent to confirm from the pasted output — the plugin never guesses a
|
|
610
|
-
project-unaware command. `completionGuard:
|
|
611
|
-
true` rejects a successful turn that forgot the `{"ok":…}` verdict.
|
|
612
|
-
- **Recycle destroys live evidence** (`agent get` → `agent_not_found`). The
|
|
613
|
-
plugin collects and snapshots `execution` into `.pi-subagents/run.json`
|
|
614
|
-
**before** closing the pane. Resume uses the session file, not the pane.
|
|
615
|
-
|
|
616
|
-
## Programmatic API
|
|
617
|
-
|
|
618
|
-
Hosts that are not the Pi `subagent` tool can drive the same machinery:
|
|
619
|
-
|
|
620
|
-
```ts
|
|
621
|
-
import {
|
|
622
|
-
createHerdrClient,
|
|
623
|
-
Orchestrator,
|
|
624
|
-
loadAgentsFromDir,
|
|
625
|
-
} from "pi-legion/api";
|
|
626
|
-
|
|
627
|
-
const client = createHerdrClient();
|
|
628
|
-
const [scout] = loadAgentsFromDir("/path/to/agents", "user");
|
|
629
|
-
const orch = new Orchestrator({
|
|
630
|
-
client,
|
|
631
|
-
runDir: "/tmp/run",
|
|
632
|
-
cwd: process.cwd(),
|
|
633
|
-
});
|
|
634
|
-
const handle = await orch.launch({ agent: scout, task: "survey the repo" });
|
|
635
|
-
const result = await orch.collect(handle.name);
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
Everything re-exported from `./api` is covered by the test suite. Do not import
|
|
639
|
-
from `src/` directly.
|
|
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.
|
|
640
358
|
|
|
641
359
|
## Development
|
|
642
360
|
|
|
643
361
|
```bash
|
|
644
|
-
git clone https://github.com/zzjcool/pi-legion.git
|
|
645
|
-
cd pi-legion
|
|
646
362
|
npm install
|
|
647
363
|
npm run typecheck
|
|
648
|
-
npm test
|
|
649
|
-
npm run test:integration
|
|
364
|
+
npm test # unit tests
|
|
365
|
+
npm run test:integration # integration tests
|
|
366
|
+
npm run test:all # typecheck + unit + integration tests
|
|
650
367
|
```
|
|
651
368
|
|
|
652
|
-
|
|
653
|
-
`HERDR_ENV=1`. They are skipped otherwise.
|
|
369
|
+
### Mock LLM (offline pi / legion smoke)
|
|
654
370
|
|
|
655
|
-
|
|
656
|
-
|
|
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:
|
|
657
374
|
|
|
658
375
|
```bash
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
bash test/docker/verify-4-acceptance.sh
|
|
663
|
-
bash test/docker/verify-5-blocked.sh
|
|
664
|
-
bash test/docker/verify-6-cache.sh
|
|
665
|
-
bash test/docker/verify-7-budget.sh
|
|
666
|
-
bash test/docker/verify-8-verify-command.sh
|
|
667
|
-
bash test/docker/verify-9-worktree.sh
|
|
668
|
-
bash test/docker/verify-10-fallback.sh
|
|
669
|
-
bash test/docker/verify-11-nested-alias.sh
|
|
670
|
-
bash test/docker/verify-12-workspace.sh
|
|
671
|
-
bash test/docker/verify-13-kinds.sh # kinds + models; live CodeBuddy/Cursor when mounted
|
|
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
|
|
672
379
|
```
|
|
673
380
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
Historical “this field is parsed but inert” notes in that file may predate
|
|
681
|
-
the runtime table above — trust this README and `action=list`’s
|
|
682
|
-
`unenforcedFields` for what is actually wired.
|
|
683
|
-
|
|
684
|
-
## Known limitations
|
|
685
|
-
|
|
686
|
-
- **Not a sandbox** (F23–F25): child-guard covers bash/herdr dispatch and
|
|
687
|
-
read-only writes; it does not give you process isolation. The read-only
|
|
688
|
-
bash rules are a **text heuristic** over the command line, so they miss
|
|
689
|
-
indirect writes — `node -e "require('fs').writeFileSync(…)"`,
|
|
690
|
-
`python3 -c "open('f','w')…"` and `perl -i` all pass, as does any child whose
|
|
691
|
-
agent definition omits `acceptance.role` (the guard is keyed off it). Treat it
|
|
692
|
-
as a guard-rail against accidents, not a boundary against intent.
|
|
693
|
-
- **`agent_status` has no success/failure semantics** (F26).
|
|
694
|
-
- **Non-pi kinds degrade** (F7): same Herdr control plane (`start` → `prompt` → `wait`), but no usage and outcome is `unknown` unless the pane text includes a verdict JSON that is not just the echoed launch prompt. Cursor starts with `--trust --force` (Run Everything) unless the agent opts back into a human gate via `onBlocked: forward`/`notify`, so workspace-trust and shell-approval dialogs cannot block the first prompt. Resume depends on each CLI.
|
|
695
|
-
- **A model the kind cannot express**: `--model` is built per CLI (pi wants
|
|
696
|
-
`provider/id`, cursor wants slugs like `cursor-grok-4.6-high`). A model that
|
|
697
|
-
cannot be expressed is omitted rather than mangled. Whether that is refused or
|
|
698
|
-
allowed depends on how it was chosen — an **explicit** choice (frontmatter,
|
|
699
|
-
`agentOverrides`, a preset, a `model` param) is refused before any pane is
|
|
700
|
-
created, while an **inherited** one (`subagents.defaultModel`, the parent
|
|
701
|
-
session's model) is dropped on purpose and reported as `modelDropped`, because
|
|
702
|
-
the parent's pi model simply means nothing to another CLI.
|
|
703
|
-
- **`blocked` is screen-heuristic**: false positives exist; the confirm is
|
|
704
|
-
paired with the collect timeout as a backstop.
|
|
705
|
-
- **Verification** runs `criterion.command` when a required criterion asks
|
|
706
|
-
for `verification-output` and carries one. A criterion without a `command`
|
|
707
|
-
stays attested and is surfaced as pending for the parent — semantic `must`
|
|
708
|
-
strings are never NLP-parsed (F32 / F44) and no default command is guessed.
|
|
709
|
-
Other evidence types stay a checklist.
|
|
710
|
-
- **Panes are scarce**: cap fan-out (`maxSubagentSpawnsPerSession` /
|
|
711
|
-
`herdr.maxConcurrentAgents`). Warm-pane pooling is not worth it (~0.9s).
|
|
712
|
-
|
|
713
|
-
## License
|
|
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.
|
|
714
387
|
|
|
715
388
|
MIT. See [LICENSE](LICENSE).
|