pi-legion 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -0
- package/CONTRIBUTING.md +51 -18
- package/README.md +101 -18
- package/agents/planner.md +1 -1
- package/agents/reviewer.md +2 -3
- package/agents/worker.md +2 -3
- package/docs/contracts/esc-abort-v1.md +2 -0
- package/docs/contracts/esc-cascade-v2.md +5 -1
- package/docs/contracts/esc-no-retire-v3.md +57 -0
- package/docs/design.md +37 -18
- package/docs/vexillatio-contract.md +1 -1
- package/index.ts +145 -134
- package/package.json +8 -4
- package/skills/pi-legion/SKILL.md +22 -8
- package/src/agents/agents.ts +60 -5
- package/src/api.ts +2 -0
- package/src/extension/legion-commands.ts +64 -10
- package/src/extension/legion-doctor.ts +128 -41
- package/src/extension/legion-status.ts +172 -0
- package/src/extension/legion-stop.ts +5 -37
- package/src/extension/playbook.ts +4 -4
- package/src/extension/runtime.ts +97 -22
- package/src/extension/scout-guard.ts +12 -3
- package/src/runs/args.ts +69 -13
- package/src/runs/orchestrator.ts +28 -3
- package/src/runs/provider-pin.ts +59 -0
- package/src/runs/provision.ts +7 -4
- package/src/runs/role-tool-gates.ts +103 -0
- package/src/runs/worktree.ts +32 -0
- package/src/shared/session.ts +37 -2
- package/src/shared/types.ts +8 -0
- package/src/tui/status.ts +12 -14
- package/src/tui/viz/canvas.ts +5 -0
- package/src/tui/viz/panels.ts +16 -8
- package/src/tui/viz/railway.ts +34 -11
- package/src/tui/viz/rate.ts +41 -9
- package/src/tui/viz/view.ts +18 -11
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
Published GitHub Release notes remain available at
|
|
9
|
+
https://github.com/zzjcool/pi-legion/releases — this file is the in-repo
|
|
10
|
+
summary so you can see **2.x vs 0.16** without leaving the tree.
|
|
11
|
+
|
|
12
|
+
## [Unreleased]
|
|
13
|
+
|
|
14
|
+
## [2.0.2] — 2026-10-16
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Child worktree launches are suppressed when the parent checkout is itself a
|
|
19
|
+
linked git worktree (`git-dir` ≠ `git-common-dir`): role defaults and unset
|
|
20
|
+
launch flags collapse to `worktree: false`, matching the centurion-child
|
|
21
|
+
precedent. Explicit `launch: true` / per-child `step` flags still win.
|
|
22
|
+
- `worktreeBootstrap: "npm-install"` no longer fires `npm install` on a stray
|
|
23
|
+
`package.json` without a lockfile (observed: Java/Maven repos carrying
|
|
24
|
+
package.json for docs/tooling); without a lockfile the mode records the
|
|
25
|
+
marker and skips, so non-npm worktrees launch instead of failing after the
|
|
26
|
+
10-minute bootstrap budget.
|
|
27
|
+
|
|
28
|
+
### Fixed in 2.0.1 (inherited from prior entries)
|
|
29
|
+
|
|
30
|
+
- Railway / `/legion` header "now" tok/s is 0 when no agent is running; only live nodes count toward now (chart buckets still use the full trailing window) (#136).
|
|
31
|
+
- Narrow/stacked `/legion` header truncates long run + selection badges without overwriting `⚔ /legion`; galaxy legend reserves room for failed/retired glyphs before roles (#125).
|
|
32
|
+
- Galaxy legend `statusBudget` counts grapheme cells (not `String.length`); selection badge min width unified for portrait + landscape (#128).
|
|
33
|
+
- Dispatched children now receive `--provider` pinned from the parent's model
|
|
34
|
+
registry, so a bare model id (`workbuddy/…`) that also exists under a static
|
|
35
|
+
auth-less provider in `models.json` can no longer hijack the child start
|
|
36
|
+
(`No API key found for …`) (#123).
|
|
37
|
+
- Scout-guard now counts first-class `grep` / `find` / `ls` tool calls as reads (#87).
|
|
38
|
+
- Doctor diagnose help no longer repeats itself; storage section shows hygiene sweep state (#86).
|
|
39
|
+
- Bundled roles no longer advertise unloaded `lens_*` tools (#75).
|
|
40
|
+
- Non-interactive `pi --print` waits for async children before cascade retire (#77).
|
|
41
|
+
- Roles load `builtin:codemode`; doctor diagnose shows a dedicated section (#89 / #90 → #93).
|
|
42
|
+
- Doctor role-tool→extension gates are data-driven / extensible (#97 / #99).
|
|
43
|
+
- Launch honors `name=` so `/legion status <name>` matches the railway label (#101 / #104).
|
|
44
|
+
- Galaxy legend shows failed/retired glyphs only when those states are present (#102 / #103).
|
|
45
|
+
- Live child status leads with `alive · quiet Ns`; long quiet emphasizes living over a stale execution snapshot (#81 / #92 / #105).
|
|
46
|
+
- npm `package.json` description/keywords aligned with Legion 2.x RPC (no Herdr panes) (#106 / #111).
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- `/legion status` human-readable summary; doctor slash list covers team / agents / on|off / summary (#76).
|
|
51
|
+
- README documents scout-guard settings and `/scout-guard` / `/no-scout-guard` (#74 / #71).
|
|
52
|
+
- Scout-guard mock-llm e2e: notify ×1, cooldown, and dispatch clear (#108 / #113).
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- Esc no longer retires children; only `/legion stop` or session exit retires (esc-no-retire-v3, #134).
|
|
57
|
+
- Status strip shows a persistent `/legion stop` hint while children are live (railway + tree; width may truncate) (#134).
|
|
58
|
+
- Prefer `PI_LEGION_EXTRA_AGENT_DIRS` over legacy `PI_HERDR_SUBAGENTS_*` (alias; new name wins if both set) (#109 / #114).
|
|
59
|
+
- CONTRIBUTING/CI docs aligned with 2.x RPC; verify-12/13 paths point at `test/legacy/docker/` (#95 / #100, #107 / #112).
|
|
60
|
+
- README: remove duplicate Child extensions heading (#94 / #98).
|
|
61
|
+
- `design.md` historical banner; landed F38–F46 marked ✅ / Historical (#80 / #83 → #91).
|
|
62
|
+
- CI PR job runs `test:integration` (#79 / #88).
|
|
63
|
+
|
|
64
|
+
## [2.0.0] - 2026-10-14
|
|
65
|
+
|
|
66
|
+
First **Pi Legion** line on headless Pi RPC. Relative to the final Herdr-era
|
|
67
|
+
**0.16.x** packages, this is a breaking transport and packaging change.
|
|
68
|
+
|
|
69
|
+
### Breaking
|
|
70
|
+
|
|
71
|
+
- **RPC-only transport:** children run as headless `pi --mode rpc` processes
|
|
72
|
+
under `LegionSupervisor`. Herdr pane execution is archived under
|
|
73
|
+
`src/backends/legacy/` and is **not** a v2 runtime option.
|
|
74
|
+
- **Kinds:** only `kind: pi` is supported. Migrate non-Pi roles or stay on
|
|
75
|
+
`pi-legion@0.16.x`.
|
|
76
|
+
- **Shared tree ledger:** squad state lives in SQLite WAL `legion.db` (phases,
|
|
77
|
+
mail, resume), not pane-centric session bookkeeping alone.
|
|
78
|
+
- Bundled agent playbooks aligned with local RPC practice (#66).
|
|
79
|
+
|
|
80
|
+
### Added
|
|
81
|
+
|
|
82
|
+
- Recursive centurion trees, four-phase
|
|
83
|
+
planning / implementing / reviewing / verifying machine, `legion_mail`,
|
|
84
|
+
three-layer `/legion` UI (resident tree, full view, live overlay), railway
|
|
85
|
+
strip, tok/s / galaxy / log panels.
|
|
86
|
+
- Scout-guard: remind the main session after a streak of read-class tools (#64).
|
|
87
|
+
- Storage hygiene sweep on startup: prune idle runs, orphan temp dirs, abandoned
|
|
88
|
+
nodes, event retention, WAL checkpoint (#37 / #38 / #40 → #47).
|
|
89
|
+
- `/legion doctor` (+ diagnose) self-check; auto-doctor snippets on gated spawn
|
|
90
|
+
failures (#52).
|
|
91
|
+
- Child memory gate (`NODE_OPTIONS` V8 flags, pre-spawn gate) and tree-wide
|
|
92
|
+
verify concurrency limit (#50).
|
|
93
|
+
- Children default to `--no-extensions` with explicit/role escape hatches (#43 / #48).
|
|
94
|
+
- User stop controls for async children (`/legion stop`, idle Esc, `x` in view) (#41).
|
|
95
|
+
- Mock LLM server for offline pi / legion smoke (`test/mock-llm`, #54 / #56).
|
|
96
|
+
- GitHub Actions CI (typecheck + unit + integration) and stage-only npm publish
|
|
97
|
+
on tag (#65 / #68).
|
|
98
|
+
- Project workflow docs in CONTRIBUTING (#69).
|
|
99
|
+
|
|
100
|
+
### Changed
|
|
101
|
+
|
|
102
|
+
- Slash commands consolidated under `/legion` (#57).
|
|
103
|
+
- Status bar shows current-turn elapsed beside lifetime total (#62).
|
|
104
|
+
- tok/s uses output tokens / generationMs (decode throughput) (#61).
|
|
105
|
+
|
|
106
|
+
### Fixed
|
|
107
|
+
|
|
108
|
+
- Railway: pin live spawn verticals when the strip scrolls left (#67); retire /
|
|
109
|
+
downward spawn connector regressions (#46).
|
|
110
|
+
- Live worker no longer marked retired on a transient Connection blip (#63).
|
|
111
|
+
- Session JSONL streamed + incrementally parsed (no full-file `readFileSync`) (#36 / #45).
|
|
112
|
+
- Per-stream child output byte cap; kill on overflow (#35 / #44).
|
|
113
|
+
- Bun SQLite `.get()` null treated as miss on root insert (#53).
|
|
114
|
+
- Vexillatio sibling naming scoped to the current parent (#42 / #51).
|
|
115
|
+
|
|
116
|
+
### Security / robustness (design.md F38–F46, already on master)
|
|
117
|
+
|
|
118
|
+
- Model-scope globs use non-RegExp matching (ReDoS).
|
|
119
|
+
- Session create uses `writeFileSync` with `wx` (no fd leak / TOCTOU).
|
|
120
|
+
- Session decode swallows scalar JSON lines as torn.
|
|
121
|
+
- `tasks[]` / blank task validated before launch.
|
|
122
|
+
- Per-agent `timeoutMs`, pending criteria, nested YAML acceptance parsing;
|
|
123
|
+
`UNENFORCED_FIELDS` empty.
|
|
124
|
+
|
|
125
|
+
## [0.16.x] - Herdr-era (final)
|
|
126
|
+
|
|
127
|
+
Last line that runs children in Herdr panes. Not developed further on this
|
|
128
|
+
branch — pin `pi-legion@0.16.x` if you still need that model. See
|
|
129
|
+
[Herdr transition](README.md#herdr-transition-and-role-migration) in the README.
|
|
130
|
+
|
|
131
|
+
Earlier 0.11–0.15 history (including the `pi-herdr-swarm` /
|
|
132
|
+
`@zzjcool/pi-herdr-subagents` renames) lives in git tags and older Releases.
|
|
133
|
+
|
|
134
|
+
[Unreleased]: https://github.com/zzjcool/pi-legion/compare/v2.0.0...HEAD
|
|
135
|
+
[2.0.0]: https://github.com/zzjcool/pi-legion/releases/tag/v2.0.0
|
|
136
|
+
[0.16.x]: https://github.com/zzjcool/pi-legion/releases
|
package/CONTRIBUTING.md
CHANGED
|
@@ -23,8 +23,14 @@ directly to `master`. Implementers do not merge their own pull requests.
|
|
|
23
23
|
```bash
|
|
24
24
|
npm run typecheck
|
|
25
25
|
npm test
|
|
26
|
+
npm run test:integration # or npm run test:all
|
|
26
27
|
```
|
|
27
28
|
|
|
29
|
+
The required GitHub Actions check `ci` runs typecheck, unit, integration, and
|
|
30
|
+
mock-llm. Run `npm run test:integration` (or `npm run test:all`) locally
|
|
31
|
+
before you push, especially when the change touches `src/runs/`, `src/legion/`,
|
|
32
|
+
or `src/extension/runtime.ts`.
|
|
33
|
+
|
|
28
34
|
Add the unit tests the change needs. Prefer **TDD** (see [Checks](#checks)):
|
|
29
35
|
a failing test in `test/unit` or `test/integration` first, then the production
|
|
30
36
|
code.
|
|
@@ -59,20 +65,36 @@ there is no compile step to ship).
|
|
|
59
65
|
|
|
60
66
|
## Checks
|
|
61
67
|
|
|
68
|
+
The required GitHub Actions check `ci` (`.github/workflows/ci.yml`, on pull
|
|
69
|
+
requests and pushes to `master`) runs, in order:
|
|
70
|
+
|
|
71
|
+
1. `npm run typecheck`
|
|
72
|
+
2. `npm test` (unit)
|
|
73
|
+
3. `npm run test:integration`
|
|
74
|
+
4. `npm run test:mock-llm`
|
|
75
|
+
|
|
76
|
+
A failing integration step fails `ci`, so nesting and orchestrator regressions
|
|
77
|
+
are blocked before merge. `test:live` and `test:docker` stay out of that job.
|
|
78
|
+
|
|
62
79
|
```bash
|
|
63
80
|
npm run typecheck
|
|
64
81
|
npm test # test/unit/*.test.ts
|
|
65
|
-
npm run test:integration #
|
|
82
|
+
npm run test:integration # FakeSupervisor / nested fakes; no live `pi` binary
|
|
66
83
|
npm run test:all # typecheck + unit + integration
|
|
84
|
+
npm run test:mock-llm # also part of the required ci check
|
|
67
85
|
```
|
|
68
86
|
|
|
69
|
-
`npm run test:live`
|
|
70
|
-
|
|
87
|
+
`npm run test:live` is a **pi RPC live** smoke (`test/live/rpc-live.test.ts`).
|
|
88
|
+
It skips itself when the `pi` binary is missing from `PATH` (see the probe at
|
|
89
|
+
the top of that file). Do not fail CI on it.
|
|
71
90
|
|
|
72
91
|
Prefer **TDD** for behaviour changes: a failing test in `test/unit` or
|
|
73
92
|
`test/integration` first, then the production code. Integration tests should
|
|
74
|
-
|
|
75
|
-
|
|
93
|
+
drive the orchestrator through `FakeSupervisor` (`test/helpers/fake-supervisor.ts`)
|
|
94
|
+
so nesting / launch / collect stay covered without a live `pi`. The herdr-era
|
|
95
|
+
helpers `createHerdrClient` / `createFakeRunner` remain as **deprecated legacy**
|
|
96
|
+
exports (see `src/api.ts` and `test/legacy/`); use them only when touching the
|
|
97
|
+
archived Herdr backend.
|
|
76
98
|
|
|
77
99
|
## Mock LLM
|
|
78
100
|
|
|
@@ -91,16 +113,20 @@ dummy key is always the literal `mock`.
|
|
|
91
113
|
|
|
92
114
|
## Docker verification
|
|
93
115
|
|
|
94
|
-
`test/docker/` runs the package inside
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
`test/docker/` runs the package inside the RPC / pi-legion sandbox image.
|
|
117
|
+
Default image and staging path names are **historical** (`pi-herdr-sandbox:latest`,
|
|
118
|
+
`/tmp/pi-herdr-docker-home`; override with `PI_DOCKER_IMAGE` / `DOCKER_PI_HOME`).
|
|
119
|
+
Content is the 2.x RPC sandbox, not a live herdr pane host. The scripts copy
|
|
120
|
+
the **host** Pi config (`~/.pi/agent/settings.json`, `models.json`, `auth.json`
|
|
121
|
+
if present) into that disposable home; the container cannot write back to your
|
|
122
|
+
real `~/.pi`.
|
|
98
123
|
|
|
99
124
|
```bash
|
|
100
|
-
# image must already exist
|
|
125
|
+
# image must already exist (historical default name; do not rename a local tag
|
|
126
|
+
# just to match docs — override with PI_DOCKER_IMAGE if yours differs)
|
|
101
127
|
docker image inspect pi-herdr-sandbox:latest >/dev/null
|
|
102
128
|
|
|
103
|
-
bash test/docker/verify-1-child-guard.sh # child
|
|
129
|
+
bash test/docker/verify-1-child-guard.sh # RPC child-guard / ledger block
|
|
104
130
|
bash test/docker/verify-2-task-appendix.sh # frozen task appendix
|
|
105
131
|
bash test/docker/verify-3-readonly.sh # read-only bash denylist
|
|
106
132
|
bash test/docker/verify-4-acceptance.sh # collect → verified
|
|
@@ -111,19 +137,26 @@ bash test/docker/verify-8-verify-command.sh # criterion.command + timeout
|
|
|
111
137
|
bash test/docker/verify-9-worktree.sh # git worktree cwd
|
|
112
138
|
bash test/docker/verify-10-fallback.sh # fallbackModels
|
|
113
139
|
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
140
|
```
|
|
117
141
|
|
|
118
|
-
Or all at once:
|
|
142
|
+
Or all at once (RPC mainline **1–11** only):
|
|
119
143
|
|
|
120
144
|
```bash
|
|
121
145
|
npm run test:docker
|
|
122
146
|
```
|
|
123
147
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
148
|
+
**Legacy Herdr / Space / kind checks** live under `test/legacy/docker/` (need
|
|
149
|
+
pi-legion **0.16.x** Herdr-era setup). They are **not** part of
|
|
150
|
+
`npm run test:docker`:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
bash test/legacy/docker/verify-12-workspace.sh # Herdr Space pin (legacy)
|
|
154
|
+
bash test/legacy/docker/verify-13-kinds.sh # kind/model matrix; live CodeBuddy when mounted
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`verify-13-kinds.sh` also bind-mounts host `codebuddy` / `cursor-agent` and
|
|
158
|
+
(unless `LIVE_KIND_PROBES=0`) calls live CodeBuddy. The numbered 1–11 scripts
|
|
159
|
+
under `test/docker/` stay offline/mock.
|
|
127
160
|
|
|
128
161
|
Override paths with `HOST_PI_AGENT`, `DOCKER_PI_HOME`, `PI_DOCKER_IMAGE`.
|
|
129
162
|
|
|
@@ -145,7 +178,7 @@ them by hand.
|
|
|
145
178
|
| `src/runs/acceptance.ts` | `verification-output` command |
|
|
146
179
|
| `test/mock-llm/` | Scriptable OpenAI mock for offline pi / legion |
|
|
147
180
|
| `agents/*.md` | bundled roles |
|
|
148
|
-
| `docs/design.md` |
|
|
181
|
+
| `docs/design.md` | design notes / F1–F46 (incl. historical herdr-era measurements; see file banner) |
|
|
149
182
|
| `README.md` | what is actually wired today |
|
|
150
183
|
|
|
151
184
|
Do not import from `src/` in downstream packages — use
|
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ shared SQLite ledger records the whole tree.
|
|
|
14
14
|
> `@zzjcool/pi-herdr-subagents` (through 0.14.3; renamed after an npm name
|
|
15
15
|
> collision), then as `pi-herdr-swarm` (0.15.0), before becoming `pi-legion`
|
|
16
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
|
+
> **What changed in 2.x?** See [CHANGELOG.md](CHANGELOG.md) (Keep a Changelog). Published notes also land on [GitHub Releases](https://github.com/zzjcool/pi-legion/releases).
|
|
17
19
|
|
|
18
20
|
## Contents
|
|
19
21
|
|
|
@@ -22,10 +24,12 @@ shared SQLite ledger records the whole tree.
|
|
|
22
24
|
- [Legion tools and phases](#legion-tools-and-phases)
|
|
23
25
|
- [Three-layer visualization](#three-layer-visualization)
|
|
24
26
|
- [Settings and budget gates](#settings-and-budget-gates)
|
|
27
|
+
- [Scout guard](#scout-guard)
|
|
25
28
|
- [Team 配置](#team-配置)
|
|
26
29
|
- [Parent/child context conventions](#parentchild-context-conventions)
|
|
27
30
|
- [Delivery and resume](#delivery-and-resume)
|
|
28
31
|
- [Herdr transition and role migration](#herdr-transition-and-role-migration)
|
|
32
|
+
- [Changelog](CHANGELOG.md)
|
|
29
33
|
- [Development](#development)
|
|
30
34
|
|
|
31
35
|
## Architecture
|
|
@@ -87,35 +91,64 @@ subagent({ agent: "scout", task: "Map the export code path and report relevant f
|
|
|
87
91
|
|
|
88
92
|
No manual child-process launch is needed. Use `/legion` to inspect the tree;
|
|
89
93
|
see [Three-layer visualization](#three-layer-visualization) for navigation.
|
|
94
|
+
`/legion status` prints that tree in the chat, and `/legion status <name>`
|
|
95
|
+
prints one node — see [Status](#status-legion-status).
|
|
90
96
|
|
|
91
97
|
The `subagent` control actions include event-driven `wait` and graceful `stop`:
|
|
92
98
|
`subagent({ action: "wait", name: "worker-0" })` returns as soon as the child
|
|
93
|
-
settles (or the parent turn is interrupted with Esc).
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
settles (or the parent turn is interrupted with Esc). Esc only ends the parent
|
|
100
|
+
wait — children keep running and settle still notifies; retire them with
|
|
101
|
+
`/legion stop` or by exiting the session. Launch stays async by default. Under
|
|
102
|
+
`pi --print` or JSON mode the process would otherwise exit at the end of the
|
|
103
|
+
parent turn and retire those children before they write an assistant message.
|
|
104
|
+
That shutdown waits for each live async child to settle, bounded by the
|
|
105
|
+
child's turn timeout, and only then retires whatever is still running. A
|
|
106
|
+
child killed by that shutdown is recorded as aborted because the parent
|
|
107
|
+
session shut down, not because the model produced no reply. To include the
|
|
108
|
+
child's result in the same print turn, call `wait` or `collect` before the
|
|
109
|
+
turn ends, or launch with `async: false`. To stop children explicitly, use
|
|
96
110
|
`subagent({ action: "stop", name: "worker-0" })` or
|
|
97
111
|
`subagent({ action: "stop", all: true })`; this runs the normal graceful-retire
|
|
98
112
|
sequence for each selected child.
|
|
99
113
|
|
|
100
114
|
### Stopping async subagents yourself
|
|
101
115
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
116
|
+
Esc never retires children (see `docs/contracts/esc-no-retire-v3.md`). Two user
|
|
117
|
+
controls run graceful `stop` (a stopped parent retires its own subtree on
|
|
118
|
+
shutdown); process exit / session shutdown also retires:
|
|
105
119
|
|
|
106
120
|
- **`/legion stop`** stops every live subagent; **`/legion stop <name>`** stops
|
|
107
121
|
one (`all` is an alias for no name). Names complete from the live children;
|
|
108
122
|
nested agents complete as `parent/name` and are reported as owned by their
|
|
109
123
|
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
124
|
- **`x`** on the selected live agent in the `/legion` view or its live
|
|
114
125
|
overlay asks `y` to confirm in place, then stops it.
|
|
115
126
|
|
|
116
|
-
While
|
|
117
|
-
|
|
118
|
-
|
|
127
|
+
While subagents are live, the railway header can show a dim
|
|
128
|
+
`/legion stop [name|all]` hint (shortened on narrow terminals; idle gating /
|
|
129
|
+
tree-strip coverage is a TUI follow-up).
|
|
130
|
+
|
|
131
|
+
### Status (`/legion status`)
|
|
132
|
+
|
|
133
|
+
The railway and `/legion` view are TUI surfaces. `/legion status` is the chat
|
|
134
|
+
snapshot of the same tree:
|
|
135
|
+
|
|
136
|
+
- `/legion status` prints every node with the same lines as `legion_tree`
|
|
137
|
+
(`id role=… status=… phase=… model=… depth=…`), plus a count of each status.
|
|
138
|
+
Live jobs that are not in the tree yet are listed under that summary.
|
|
139
|
+
- `/legion status <name>` prints one node with the same text as
|
|
140
|
+
`subagent({ action: "status", name })`. Living children lead with
|
|
141
|
+
`alive · quiet Ns` (session JSONL mtime); after ~30s of silence the line
|
|
142
|
+
becomes `alive · quiet Ns (living)` so 「活着但安静」is obvious. Run-store
|
|
143
|
+
execution/acceptance fields are labeled snapshots, and collect-deadline /
|
|
144
|
+
transient-connection reasons are omitted from the snapshot line while the
|
|
145
|
+
child is alive so they do not steal attention from quiet age. A ledger-only
|
|
146
|
+
node still uses the same renderer with the fields the tree has.
|
|
147
|
+
- Quiet age is not a heartbeat: there is no child-side progress ping. Minutes
|
|
148
|
+
of silence can still mean the model is reasoning (especially high-thinking
|
|
149
|
+
turns). Quiet alone does not mean the child is stuck; pair it with `alive`.
|
|
150
|
+
- Both forms work outside the TUI. A short name that matches more than one
|
|
151
|
+
node needs the full node id (`/legion status <node-id>`).
|
|
119
152
|
|
|
120
153
|
## Legion tools and phases
|
|
121
154
|
|
|
@@ -208,6 +241,38 @@ subtree, including itself; when launched by a parent at depth `d`, it inherits
|
|
|
208
241
|
`C_child = min(C_parent, d + R, 4)` (`R = 1` makes it a leaf), so it can tighten
|
|
209
242
|
the inherited cap but cannot widen it.
|
|
210
243
|
|
|
244
|
+
### Scout guard
|
|
245
|
+
|
|
246
|
+
`subagents.scoutGuard` watches the main session. After `threshold` consecutive
|
|
247
|
+
read-like tool calls with no `subagent` launch or `vexillatio` call in
|
|
248
|
+
between, it injects one follow-up reminder and then waits out `cooldownMs`.
|
|
249
|
+
It is on by default. Set it in `~/.pi/agent/settings.json` or the project
|
|
250
|
+
`.pi/settings.json`; a project file overrides the same fields from the user
|
|
251
|
+
file.
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{
|
|
255
|
+
"subagents": {
|
|
256
|
+
"scoutGuard": {
|
|
257
|
+
"enabled": true,
|
|
258
|
+
"threshold": 8,
|
|
259
|
+
"cooldownMs": 120000
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
| Field | Default | Meaning |
|
|
266
|
+
| --- | ---: | --- |
|
|
267
|
+
| `enabled` | `true` | Count and remind. `false` skips the guard until it is turned back on. |
|
|
268
|
+
| `threshold` | `8` | Consecutive read-like calls before one reminder. Positive integer. |
|
|
269
|
+
| `cooldownMs` | `120000` | Quiet period after a reminder, in milliseconds. Integer ≥ 0. |
|
|
270
|
+
|
|
271
|
+
`/scout-guard` turns the guard on for this session and `/no-scout-guard`
|
|
272
|
+
turns it off. Both override `enabled` in memory only; settings files stay
|
|
273
|
+
unchanged. Child sessions do not register these commands. Behavior in full
|
|
274
|
+
is in [docs/design.md §6.4.1](docs/design.md#641-scout-guard-subagentsscoutguard).
|
|
275
|
+
|
|
211
276
|
### Team 配置
|
|
212
277
|
|
|
213
278
|
Use `subagents.team` to select the default named team from `subagents.teams`.
|
|
@@ -245,10 +310,11 @@ gate, missing lens tools, orphan temps, …), ask Legion rather than guessing
|
|
|
245
310
|
same idea as `pi list` / `pi auth check`.
|
|
246
311
|
|
|
247
312
|
- `/legion doctor` — full report in the chat (works outside TUI too).
|
|
248
|
-
- `/legion doctor
|
|
313
|
+
- `/legion doctor [section]` — one of: `summary`, `extensions`, `models`,
|
|
249
314
|
`spawn`, `memory`, `tools`, `storage`, `runtime`, `next`.
|
|
250
315
|
- `subagent({ action: "diagnose" })` — same full report as tool output so the
|
|
251
|
-
main agent can call it without waiting for you.
|
|
316
|
+
main agent can call it without waiting for you. The tool has no section
|
|
317
|
+
field; filter with `/legion doctor [section]`.
|
|
252
318
|
|
|
253
319
|
The report is structured for the model: each section ends in **Issues** /
|
|
254
320
|
**Next steps** with copy-pasteable fixes (env exports, role frontmatter,
|
|
@@ -268,11 +334,10 @@ Coverage:
|
|
|
268
334
|
| models | Host vs child model lists, which package restores providers, team/roster gaps |
|
|
269
335
|
| spawn | Sample child argv, recent spawn failures + stderr, nested-spawn flags |
|
|
270
336
|
| memory | Free RAM, memory gate, child V8 flags, verify concurrency slots in use |
|
|
271
|
-
| tools | `/legion`
|
|
272
|
-
| storage | `legion.db` sizes, orphan `rpc-child-*` temps,
|
|
337
|
+
| tools | `/legion` slash list (status, team, agents, summary, on/off, scout-guard, doctor, stop), idle-Esc / strip hints, tool names |
|
|
338
|
+
| storage | `legion.db` sizes, orphan `rpc-child-*` temps, effective hygiene limits (default vs env), last sweep marker plus a hint when it is missing or stale |
|
|
273
339
|
| runtime | Live children, tree statuses, acceptance-ish / active nodes |
|
|
274
340
|
|
|
275
|
-
### Child extensions
|
|
276
341
|
### Child extensions
|
|
277
342
|
|
|
278
343
|
Children start with `--no-extensions`, so they do not load the host's
|
|
@@ -288,6 +353,20 @@ itself (guard and nested spawning) and every path listed in the role's
|
|
|
288
353
|
| `PI_LEGION_CHILD_INHERIT_EXTENSIONS=1` | Every child loads all host extensions, as before. |
|
|
289
354
|
| `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
355
|
|
|
356
|
+
Bundled planner, reviewer, and worker do not list `lens_*` tools. pi-lens starts an LSP stack (~1 GB per child, #43) and is not part of the default `--no-extensions` child. To give a role those tools, add the tool names to its `tools:` list and point `extensions:` / `subagentOnlyExtensions` (or `PI_LEGION_CHILD_EXTENSIONS`) at the pi-lens extension file. `/legion doctor` reports `ROLE_MISSING_LENS` when a role lists the tools without that path.
|
|
357
|
+
|
|
358
|
+
### Extra agent directories
|
|
359
|
+
|
|
360
|
+
PATH-style directories scanned for agent `*.md` definitions. They load as
|
|
361
|
+
user-level, **below** the real user agents dir in precedence (a same-name file
|
|
362
|
+
in `~/.pi/agent/agents` still wins). Useful for hermetic installs (Nix store,
|
|
363
|
+
container, read-only mount).
|
|
364
|
+
|
|
365
|
+
| Env | Effect |
|
|
366
|
+
| --- | --- |
|
|
367
|
+
| `PI_LEGION_EXTRA_AGENT_DIRS` | Canonical name. Colon-/semicolon-separated absolute dirs (`path.delimiter`). |
|
|
368
|
+
| `PI_HERDR_SUBAGENTS_EXTRA_AGENT_DIRS` | Deprecated compatibility alias (kept for at least one minor version). If **both** are set, `PI_LEGION_EXTRA_AGENT_DIRS` wins and the legacy value is ignored (one stderr warning). |
|
|
369
|
+
|
|
291
370
|
### Child memory controls
|
|
292
371
|
|
|
293
372
|
| Env | Default | Effect |
|
|
@@ -341,7 +420,8 @@ Version **0.16.x is the final Herdr-era line**. Pi Legion 2.x is RPC-only:
|
|
|
341
420
|
child execution uses headless `pi --mode rpc` processes, and the legacy backend
|
|
342
421
|
is archived under `src/backends/legacy/`, not offered as a v2 runtime option.
|
|
343
422
|
Pin to `pi-legion` 0.16.x if a workflow must stay on the former Herdr-based
|
|
344
|
-
execution model.
|
|
423
|
+
execution model. A short breaking-change summary lives in
|
|
424
|
+
[CHANGELOG.md](CHANGELOG.md#200---2026-10-14).
|
|
345
425
|
|
|
346
426
|
Pi Legion 2.x supports `kind: pi` only. Migrate a custom role that uses another
|
|
347
427
|
CLI kind by changing its frontmatter, for example:
|
|
@@ -385,4 +465,7 @@ Use `writeMockPiHome()` from `test/mock-llm` to generate an isolated
|
|
|
385
465
|
See [CONTRIBUTING.md](CONTRIBUTING.md#project-workflow) for the project
|
|
386
466
|
workflow: issues, pull requests, the merge gate, and npm releases.
|
|
387
467
|
|
|
468
|
+
Version history for consumers: [CHANGELOG.md](CHANGELOG.md). Tag-by-tag
|
|
469
|
+
release bodies: [GitHub Releases](https://github.com/zzjcool/pi-legion/releases).
|
|
470
|
+
|
|
388
471
|
MIT. See [LICENSE](LICENSE).
|
package/agents/planner.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: planner
|
|
3
3
|
description: 规划员:把任务拆成可并行/可验收的实施计划,冻结接口,指定文件归属
|
|
4
|
-
tools: read, grep, find, ls, bash, codemode
|
|
4
|
+
tools: read, grep, find, ls, bash, codemode
|
|
5
5
|
systemPromptMode: replace
|
|
6
6
|
inheritProjectContext: true
|
|
7
7
|
inheritSkills: false
|
package/agents/reviewer.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: reviewer
|
|
3
3
|
description: 审查员:只读对抗式审查一个指定范围的改动——对照需求查漏、查正确性与测试覆盖,输出带路径与严重级别的发现
|
|
4
|
-
tools: read, grep, find, ls, bash, codemode
|
|
4
|
+
tools: read, grep, find, ls, bash, codemode
|
|
5
5
|
systemPromptMode: replace
|
|
6
6
|
inheritProjectContext: true
|
|
7
7
|
inheritSkills: false
|
|
@@ -31,9 +31,8 @@ maxSubagentDepth: 1
|
|
|
31
31
|
|
|
32
32
|
## 工具纪律(读代码的正确姿势)
|
|
33
33
|
|
|
34
|
-
- 你有 pi-lens 工具:看模块结构用 `module_report`,读单个函数/类的完整源码用 `read_symbol`(比 grep 片段读快且省 token),找符号位置用 `symbol_search`,改完的类型/错误检查用 `lens_diagnostics`。
|
|
35
34
|
- `grep`/`find` 工具底层就是 ripgrep/fd,优先于 bash 管道。只在需要管道(如 `git show X | grep`)时才用 bash。
|
|
36
|
-
- **禁止对同一文件碎片化 grep 超过 3
|
|
35
|
+
- **禁止对同一文件碎片化 grep 超过 3 次**——超过就用 `read` 把相关段落整段读完。
|
|
37
36
|
- 多个独立的读操作(批量读文件、批量检查)用 `codemode` 的 `Promise.allSettled` 并行发,不要串行。
|
|
38
37
|
|
|
39
38
|
## 先确定范围
|
package/agents/worker.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: worker
|
|
3
3
|
description: 实现者:按冻结计划写代码、跑测试,完成后自报 verdict
|
|
4
|
-
tools: read, edit, write, grep, find, ls, bash, subagent, codemode
|
|
4
|
+
tools: read, edit, write, grep, find, ls, bash, subagent, codemode
|
|
5
5
|
systemPromptMode: replace
|
|
6
6
|
inheritProjectContext: true
|
|
7
7
|
inheritSkills: false
|
|
@@ -27,9 +27,8 @@ maxSubagentDepth: 3
|
|
|
27
27
|
|
|
28
28
|
## 工具纪律(读代码的正确姿势)
|
|
29
29
|
|
|
30
|
-
- 你有 pi-lens 工具:看模块结构用 `module_report`,读单个函数/类的完整源码用 `read_symbol`(比 grep 片段读快且省 token),找符号位置用 `symbol_search`,改完后对涉及文件跑 `lens_diagnostics` 查类型/错误。
|
|
31
30
|
- `grep`/`find` 工具底层就是 ripgrep/fd,优先于 bash 管道。只在需要管道(如 `git diff | grep`)时才用 bash。
|
|
32
|
-
- **禁止对同一文件碎片化 grep 超过 3
|
|
31
|
+
- **禁止对同一文件碎片化 grep 超过 3 次**——超过就用 `read` 把相关段落整段读完。
|
|
33
32
|
- 多个独立的读操作(批量读文件、批量检查)用 `codemode` 的 `Promise.allSettled` 并行发,不要串行。
|
|
34
33
|
|
|
35
34
|
## 职责边界
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> **v2 修订(2026-10-13)**:ESC / 父 turn abort 的语义已改为级联优雅关闭由该 turn 拥有的 children,推翻 v1 下文的「ESC ≠ 杀 children」决定。下文保留 v1 冻结原文,仅供历史考古;当前语义以 `docs/contracts/esc-cascade-v2.md` 为准。
|
|
4
4
|
|
|
5
|
+
> **v3 说明(2026-10-15)**:v3(`esc-no-retire-v3.md`)再次回到 v1 的「ESC ≠ 杀 children」原则;与 v1 不同的是保留 session_shutdown drain/归因与显式 `/legion stop`。v2 级联语义已推翻。
|
|
6
|
+
|
|
5
7
|
## 目标
|
|
6
8
|
|
|
7
9
|
1. 主 pi 按 ESC(或 RPC abort)打断当前 turn 时,正在执行的 `subagent` 工具调用必须尽快感知并优雅收场,不能钉住事件循环。
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# ESC 级联关闭契约(v2,2026-10-13 冻结)
|
|
2
2
|
|
|
3
|
+
> **v3 推翻(2026-10-15)**:ESC / 父 turn abort **不再**级联 retire children。当前语义以 `docs/contracts/esc-no-retire-v3.md` 为准。下文保留 v2 冻结原文,仅供历史考古。
|
|
4
|
+
|
|
5
|
+
|
|
3
6
|
## 背景
|
|
4
7
|
|
|
5
8
|
v1(esc-abort-v1.md,已随 e66c85f 合入)冻结了「ESC ≠ 杀 children」语义。用户裁决推翻:turn 是 children 的 owner,turn 被 abort 时 children 应级联关闭,否则是资源泄漏。
|
|
@@ -57,7 +60,8 @@ vexillatio 工具 execute 也要把 signal 传进来(找到它的 registerTool
|
|
|
57
60
|
## 语义边界(不变)
|
|
58
61
|
|
|
59
62
|
- **stop action 不变**:显式停止仍走全链。
|
|
60
|
-
- **主 pi 正常退出(quit
|
|
63
|
+
- **主 pi 正常退出(quit)不变**:交互 TUI 的 session_shutdown 仍立即级联 retire。
|
|
64
|
+
- **非交互 print/JSON(#72)**:`ctx.mode` 为 `print` 或 `json` 时,session_shutdown 先等仍活着的 async 子 agent 结算(上限为各子的 turn timeout),再 retire 尚未结束的。避免 `--print` 在首包前把刚 spawn 的子进程记成 `no assistant message for the last prompt`。父会话关机导致的无 assistant abort 原因改为 parent session shutdown,与模型自己没写出 assistant 区分。
|
|
61
65
|
- **SIGKILL/崩溃不覆盖**:孤儿扫描 + resume 语义不变(这是既定接受的风险)。
|
|
62
66
|
- **children 的 children**:retire 链本身递归(孙进程 sweep 在 rpc-supervisor 的 retireChild 里),无需额外处理。
|
|
63
67
|
- **blocked children**:一并 retire(stop 语义一致)。
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# ESC 不再 retire children(v3,2026-10-15 冻结)
|
|
2
|
+
|
|
3
|
+
## 背景
|
|
4
|
+
|
|
5
|
+
v2(`esc-cascade-v2.md`)把「父 turn abort → 级联 retire children」冻结为产品语义。2026-10-15 实测事故:主会话异步派了多个 child 后用 `wait` 阻塞,用户按 Esc 打断父 turn,级联 retire 把整棵仍在生长的 legion 树抹掉(含尚未派生子 agent 的 centurion)。Esc 是高频误触键,后果不可恢复。
|
|
6
|
+
|
|
7
|
+
用户裁决推翻 v2:**Esc 只打断父 turn 的等待,不杀 children;停止 subagent 只有两条路——显式 `/legion stop`,或主进程退出。**
|
|
8
|
+
|
|
9
|
+
## 冻结决定
|
|
10
|
+
|
|
11
|
+
### A. Esc ≠ retire
|
|
12
|
+
|
|
13
|
+
1. **Idle Esc stop 删除**:不再有 `createIdleEscStop` / `onTerminalInput` 接线。裸 Esc 在 idle 时也不再 stop all。
|
|
14
|
+
2. **父 turn abort 不再级联**:工具 execute 各 `signal?.aborted` 分支只返回 interrupted 文案,**不**调用 `cascadeRetireAll`。`cascadeRetireAll` / `cascadePromise` / `agent_settled{aborted}` 级联钩子全部删除。
|
|
15
|
+
3. **`abandonLaunchedChild`**:spawn 后 abort 时 **不 retire**;`followLaunched(..., { watch: true })`,child 继续跑,settle 仍通知父会话。
|
|
16
|
+
4. **`runtime.waitOne` WAIT_INTERRUPTED**:**必须 re-arm watch**(`consumedByTool = false` + `watch`),否则 child 完成后无人收割、无完成通知。
|
|
17
|
+
|
|
18
|
+
### B. 中断文案
|
|
19
|
+
|
|
20
|
+
统一为:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
interrupted (parent turn ended; children still running)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
不再使用 `interrupted (parent turn aborted)`(易误导为已 abort children)。
|
|
27
|
+
|
|
28
|
+
### C. 保留的 retire 路径(不变)
|
|
29
|
+
|
|
30
|
+
- 显式 `stop` action / `/legion stop` / `/legion` 视图 `x`
|
|
31
|
+
- `session_shutdown` → `shutdownRetireAll`(交互立刻 retire;`--print` / JSON 先 `drainBeforeShutdown` 再 retire)
|
|
32
|
+
- SIGINT / 进程退出 = 主进程退出 = retire
|
|
33
|
+
|
|
34
|
+
### D. TUI 提示
|
|
35
|
+
|
|
36
|
+
有 live child 时,aboveEditor 状态栏常驻 `/legion stop [name|all]`(短版 `/legion stop`):railway header 在宽度挤压下可截断摘要以保短 hint;`PI_LEGION_STRIP=tree` 在 header 下另起一行短提示。无 idle 门控。
|
|
37
|
+
|
|
38
|
+
## 语义边界
|
|
39
|
+
|
|
40
|
+
| 动作 | children |
|
|
41
|
+
|---|---|
|
|
42
|
+
| Esc 打断 wait / 父 turn | 继续跑;re-arm watch;settle 通知 |
|
|
43
|
+
| `/legion stop` / `stop` action | graceful retire |
|
|
44
|
+
| 主进程退出 / session_shutdown | drain(print/json)+ retire |
|
|
45
|
+
| SIGINT | 进程退出 → retire |
|
|
46
|
+
|
|
47
|
+
## 与 v1 / v2 的关系
|
|
48
|
+
|
|
49
|
+
- **v1**(`esc-abort-v1.md`)主张「ESC ≠ 杀 children」——v3 回到这一原则,但保留 v1 没有的 drain / shutdown 归因与显式 stop。
|
|
50
|
+
- **v2**(`esc-cascade-v2.md`)级联 retire —— **v3 推翻**;原文保留供考古。
|
|
51
|
+
|
|
52
|
+
## 验收锚点
|
|
53
|
+
|
|
54
|
+
- 无 `cascadeRetireAll` / idle Esc 接线
|
|
55
|
+
- wait abort → children still alive + watching;完成通知仍达
|
|
56
|
+
- launch abort mid-spawn → child watched, not retired
|
|
57
|
+
- print-shutdown / session_shutdown / stop 回归绿
|