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.
- package/CONTRIBUTING.md +168 -0
- package/LICENSE +21 -0
- package/README.md +387 -2
- package/agents/advisor.md +110 -0
- package/agents/centurion.md +53 -0
- package/agents/designer.md +87 -0
- package/agents/planner.md +53 -0
- package/agents/prototype.md +72 -0
- package/agents/reviewer.md +114 -0
- package/agents/scout.md +56 -0
- package/agents/worker.md +68 -0
- package/docs/contracts/esc-abort-v1.md +131 -0
- package/docs/contracts/esc-cascade-v2.md +100 -0
- package/docs/design.md +1286 -0
- 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 +2836 -0
- package/package.json +89 -4
- package/skills/pi-legion/SKILL.md +98 -0
- package/src/agents/agents.ts +742 -0
- package/src/agents/context.ts +118 -0
- package/src/agents/frontmatter.ts +237 -0
- package/src/agents/model-resolution.ts +161 -0
- package/src/agents/model-scope.ts +176 -0
- package/src/agents/overrides.ts +148 -0
- package/src/agents/paths.ts +21 -0
- package/src/agents/presets.ts +237 -0
- package/src/agents/settings.ts +793 -0
- package/src/agents/step-model.ts +153 -0
- package/src/agents/teams.ts +101 -0
- package/src/api.ts +311 -0
- package/src/backends/legacy/client.ts +638 -0
- package/src/backends/legacy/cursor-chat.ts +348 -0
- package/src/backends/legacy/runner.ts +101 -0
- package/src/backends/legacy/types.ts +138 -0
- package/src/extension/auto-doctor.ts +144 -0
- package/src/extension/blocked.ts +52 -0
- package/src/extension/budget.ts +63 -0
- package/src/extension/child-guard.ts +336 -0
- package/src/extension/event-bus.ts +263 -0
- package/src/extension/join.ts +230 -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/notice-renderer.ts +72 -0
- package/src/extension/notify.ts +413 -0
- package/src/extension/playbook.ts +44 -0
- package/src/extension/recycle.ts +23 -0
- package/src/extension/runtime.ts +1190 -0
- package/src/extension/scout-guard.ts +483 -0
- package/src/extension/slash.ts +681 -0
- package/src/extension/spawn-hints.ts +79 -0
- package/src/extension/summary.ts +772 -0
- 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 +221 -0
- package/src/runs/args.ts +270 -0
- package/src/runs/hygiene.ts +335 -0
- package/src/runs/kind.ts +260 -0
- package/src/runs/orchestrator.ts +1145 -0
- package/src/runs/owner.ts +61 -0
- package/src/runs/provision.ts +135 -0
- package/src/runs/store.ts +571 -0
- package/src/runs/verify-queue.ts +195 -0
- package/src/runs/worktree.ts +125 -0
- package/src/shared/name.ts +78 -0
- package/src/shared/nested-path.ts +80 -0
- package/src/shared/output-cap.ts +57 -0
- package/src/shared/progress.ts +97 -0
- package/src/shared/session.ts +769 -0
- package/src/shared/types.ts +710 -0
- 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 +845 -0
- 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/CONTRIBUTING.md
ADDED
|
@@ -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
|
-
|
|
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
|
-
|
|
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).
|