pi-legion 3.1.0 → 5.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/CHANGELOG.md +382 -2
- package/CONTRIBUTING.md +4 -17
- package/README.md +127 -66
- package/agents/centurion.md +20 -16
- package/agents/scout.md +3 -1
- package/agents/worker.md +6 -8
- package/docs/contracts/declarative-phase-machine-v5.md +128 -0
- package/docs/contracts/display-liveness-v1.md +373 -0
- package/docs/contracts/drop-non-pi-kinds-v4.md +198 -0
- package/docs/contracts/esc-abort-v1.md +4 -4
- package/docs/contracts/esc-no-retire-v3.md +2 -2
- package/docs/contracts/launch-admission-queue-v1.md +839 -0
- package/docs/contracts/status-panel-v1.md +174 -0
- package/docs/contracts/subagent-renewal-v1.md +260 -0
- package/docs/headless-child-contract.md +6 -3
- package/docs/inprocess-supervisor.md +106 -27
- package/docs/legion-v2-contract.md +27 -2
- package/docs/performance-2026-10-19.md +199 -0
- package/docs/spawn-mode-visibility-v1.md +4 -1
- package/docs/vexillatio-contract.md +3 -1
- package/index.ts +1155 -120
- package/package.json +5 -3
- package/skills/pi-legion/SKILL.md +2 -3
- package/src/agents/agents.ts +310 -46
- package/src/agents/overrides.ts +3 -2
- package/src/agents/presets.ts +14 -40
- package/src/agents/settings.ts +32 -95
- package/src/agents/step-model.ts +7 -22
- package/src/api.ts +1 -35
- package/src/extension/child-guard.ts +25 -1
- package/src/extension/event-bus.ts +80 -8
- package/src/extension/legion-commands.ts +41 -17
- package/src/extension/legion-doctor.ts +1 -1
- package/src/extension/legion-stop.ts +39 -13
- package/src/extension/legion-tools.ts +87 -25
- package/src/extension/liveness.ts +200 -0
- package/src/extension/notify.ts +136 -4
- package/src/extension/playbook.ts +4 -4
- package/src/extension/recycle.ts +4 -0
- package/src/extension/runtime.ts +435 -16
- package/src/extension/slash.ts +0 -21
- package/src/extension/summary.ts +14 -5
- package/src/extension/timing.ts +8 -4
- package/src/legion/db.ts +165 -1
- package/src/legion/events.ts +81 -16
- package/src/legion/nodes.ts +13 -2
- package/src/legion/phase-conditions.ts +164 -0
- package/src/legion/phase-machine.ts +403 -0
- package/src/legion/rebuild.ts +7 -1
- package/src/legion/state-machine.ts +219 -115
- package/src/legion/usage.ts +58 -4
- package/src/runs/args.ts +32 -18
- package/src/runs/extension-inheritance.ts +220 -0
- package/src/runs/orchestrator.ts +1292 -96
- package/src/runs/progress-signal.ts +137 -0
- package/src/runs/store-lookup.ts +270 -0
- package/src/runs/worktree.ts +190 -21
- package/src/shared/ignored-kind.ts +57 -0
- package/src/shared/progress.ts +11 -0
- package/src/shared/session.ts +199 -19
- package/src/shared/types.ts +138 -69
- package/src/supervisor/admission-queue.ts +516 -0
- package/src/supervisor/child-context.ts +19 -0
- package/src/supervisor/inprocess-supervisor.ts +137 -38
- package/src/supervisor/proc-memory.ts +67 -0
- package/src/supervisor/routing-supervisor.ts +60 -2
- package/src/supervisor/rpc-supervisor.ts +2 -2
- package/src/tui/session-messages-file.ts +100 -0
- package/src/tui/status.ts +91 -18
- package/src/tui/tree-view.ts +84 -3
- package/src/tui/viz/layout.ts +54 -3
- package/src/tui/viz/model.ts +67 -14
- package/src/tui/viz/palette.ts +11 -3
- package/src/tui/viz/panels.ts +222 -8
- package/src/tui/viz/view.ts +110 -23
- package/agents/advisor.md +0 -110
- package/agents/designer.md +0 -87
- package/agents/planner.md +0 -53
- package/agents/prototype.md +0 -72
- package/agents/reviewer.md +0 -113
- package/src/backends/legacy/client.ts +0 -638
- package/src/backends/legacy/cursor-chat.ts +0 -348
- package/src/backends/legacy/runner.ts +0 -101
- package/src/backends/legacy/types.ts +0 -138
- package/src/runs/kind.ts +0 -260
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,382 @@ summary so you can see **2.x vs 0.16** without leaving the tree.
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [5.0.0] — 2026-10-26
|
|
15
|
+
|
|
16
|
+
Major because upgrading **can break an existing role card**: unknown frontmatter
|
|
17
|
+
is now a hard error, and `spawnMode` / `placement` are gone. A card carrying
|
|
18
|
+
either fails to load with a message naming the offending field. That is the point
|
|
19
|
+
— the previous behaviour was to silently ignore them, so a setting that looked
|
|
20
|
+
active did nothing.
|
|
21
|
+
|
|
22
|
+
**Before upgrading**, remove `spawnMode:` and `placement:` from every card under
|
|
23
|
+
`~/.pi/agent/agents/` and `.pi/agents/`. Nothing else about a card needs to
|
|
24
|
+
change.
|
|
25
|
+
|
|
26
|
+
### Breaking
|
|
27
|
+
|
|
28
|
+
- **`spawnMode` is gone from role cards (#290).** The backend is a property of
|
|
29
|
+
the launch, not of a role: a launch that needs a worktree runs over rpc, and
|
|
30
|
+
everything else runs in-process. Since only a `centurion` may hold a worktree
|
|
31
|
+
(#214), a centurion is the only role that can reach rpc. There is no global
|
|
32
|
+
default to configure because there is no longer a choice to make per role.
|
|
33
|
+
- **Unknown frontmatter is refused (#290).** A card with a field the runtime does
|
|
34
|
+
not honour is rejected at load, listing the bad keys and the full legal set. The
|
|
35
|
+
retiral of `placement` was previously "tolerated"; fourteen bundled and local
|
|
36
|
+
cards still carried it while it did nothing.
|
|
37
|
+
- **Five bundled roles removed (#301).** `planner`, `reviewer`, `designer`,
|
|
38
|
+
`prototype`, and `advisor` are gone; `centurion`, `worker`, and `scout` remain.
|
|
39
|
+
They existed because the phase machine keyed its rules on role *names* — a role
|
|
40
|
+
that merely existed carried workflow meaning. See the declarative machine below.
|
|
41
|
+
- **Children inherit every host extension by default (#280).** The old default was
|
|
42
|
+
`--no-extensions` plus whatever a card named, which meant a role silently lost
|
|
43
|
+
host capabilities (a provider extension, a guard) it never knew to name. Use
|
|
44
|
+
`excludeExtensions: [pi-lens]` on a card, or
|
|
45
|
+
`PI_LEGION_CHILD_INHERIT_EXTENSIONS=0` globally, to get the old isolation.
|
|
46
|
+
`inheritExtensions: false` loads only the card's own list.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- **Declarative phase machine (#298).** A team declares its own phases on its
|
|
51
|
+
lead's card:
|
|
52
|
+
```yaml
|
|
53
|
+
phases:
|
|
54
|
+
- id: recon
|
|
55
|
+
next:
|
|
56
|
+
- to: build
|
|
57
|
+
require: [contract_file]
|
|
58
|
+
- id: build
|
|
59
|
+
next:
|
|
60
|
+
- to: done
|
|
61
|
+
require: [children_settled]
|
|
62
|
+
```
|
|
63
|
+
Omitted means the built-in `planning → implementing → reviewing → verifying`.
|
|
64
|
+
Conditions are a closed vocabulary (`contract_file`, `children_present`,
|
|
65
|
+
`children_settled`, `children_none_failed`, `verdict_ok`, `verdict_not_ok`,
|
|
66
|
+
`verification_passed`, `verification_failed`, `rework_available`,
|
|
67
|
+
`children_all_failed`, `always`) and **none of them names a role**, which is
|
|
68
|
+
what lets a team reshape its workflow without touching source.
|
|
69
|
+
- Conditions live on the **edge**, not the phase: `planning → implementing`
|
|
70
|
+
needs a contract while `planning → failed` must not, or a team could not fail
|
|
71
|
+
before it had one.
|
|
72
|
+
- `done` / `failed` / `aborted` are injected when a card omits them. A team that
|
|
73
|
+
cannot be aborted is a safety hole. Reaching them is still gated: `failed`
|
|
74
|
+
needs a terminal reason, `aborted` needs a parent retire, `done` stays
|
|
75
|
+
supervisor-only. `aborted` is deliberately exempt from phase-level
|
|
76
|
+
requirements — an emergency stop must not wait for "everything settled".
|
|
77
|
+
- An invalid machine is rejected at load, reporting every problem at once
|
|
78
|
+
(unknown condition, duplicate id, unknown target, self-edge, unreachable
|
|
79
|
+
phase, terminal start).
|
|
80
|
+
- **The tree shows a team's machine (#302).** Selecting a team root unfolds its
|
|
81
|
+
phases, the current one, and every transition with its conditions.
|
|
82
|
+
- **`legion_phase` refusals name the legal exits (#299).** A refused transition
|
|
83
|
+
reports `legal next phases from 'recon': build, done, failed, aborted`. Measured:
|
|
84
|
+
without it a model invented plausible-but-nonexistent transitions and burned a
|
|
85
|
+
turn being refused.
|
|
86
|
+
|
|
87
|
+
### Fixed
|
|
88
|
+
|
|
89
|
+
- **In-process children enforce `toolBudget` / `turnBudget` (#295).** The parent
|
|
90
|
+
computed budgets into `SpawnInput.env`, which an rpc child receives as its real
|
|
91
|
+
environment — but an in-process child shares the *host's* `process.env`, so the
|
|
92
|
+
guard read the parent's (absent) values. A card with `toolBudget: {maxToolCalls:
|
|
93
|
+
3}` made 12 tool calls and finished normally. Budgets now travel in the
|
|
94
|
+
async-local child context, the same channel already used for lineage identity,
|
|
95
|
+
with `process.env` kept as the rpc fallback.
|
|
96
|
+
- **A malformed budget is an error, not "no limit" (#293).** `{not json}` parsed to
|
|
97
|
+
`undefined` and `{"maxToolCalls": -3}` was accepted, so the guard would have
|
|
98
|
+
blocked the first call. Both now fail at load naming the file, field, and value.
|
|
99
|
+
- **rpc is reserved for a centurion, and in-process keeps its ledger budget
|
|
100
|
+
(#279).** In-process launches had been exempted from every legion budget
|
|
101
|
+
(`Number.MAX_SAFE_INTEGER`), which made "run it in-process" a way to escape
|
|
102
|
+
depth/spawn/ledger caps. Admission and reservation now share one code path, so a
|
|
103
|
+
launch cannot be admitted and then rejected.
|
|
104
|
+
- **Runtime handles are keyed by (ledger path, node id) (#283, #285, #287).** Keyed
|
|
105
|
+
by path alone, a second session sharing the ledger could close the parent's
|
|
106
|
+
database; the identity is now adopted per node.
|
|
107
|
+
- **Extension inheritance discovery (#280).** `pi list` reports package
|
|
108
|
+
*directories*, resolved the way pi's own loader does (`package.json`'s
|
|
109
|
+
`pi.extensions`, else `index.ts` / `index.js`). Single-file `--extension`
|
|
110
|
+
arguments and builtins cannot be discovered this way — name them explicitly.
|
|
111
|
+
- **One spelling for "unknown child" (#288).** The old message advised "pass the
|
|
112
|
+
same cwd used for launch", which stopped being actionable once cross-store
|
|
113
|
+
lookup landed.
|
|
114
|
+
- **Prompt surfaces match the code again (#303, #304).** The parent playbook told a
|
|
115
|
+
centurion to "dispatch N centurions" — an instruction the runtime refuses
|
|
116
|
+
(only the root may dispatch one). The roster also listed a centurion to
|
|
117
|
+
itself. `worker` kept `subagent` in its tool list while its text said not to
|
|
118
|
+
spawn, and the README described every child as an rpc process.
|
|
119
|
+
|
|
120
|
+
### Notes
|
|
121
|
+
|
|
122
|
+
- `toolBudget` / `turnBudget` remain **optional and unused by the bundled cards**.
|
|
123
|
+
Time, depth, per-node child counts, active-node counts, and the memory floor are
|
|
124
|
+
the intended bounds; a tool budget is a scalpel for one stubborn role, not a
|
|
125
|
+
default.
|
|
126
|
+
- Child extension inheritance includes heavy extensions. pi-lens starts an LSP
|
|
127
|
+
stack (~1 GB per child, #43); add `excludeExtensions: [pi-lens]` where a fleet
|
|
128
|
+
does not need it.
|
|
129
|
+
|
|
130
|
+
## [4.0.0] — 2026-10-24
|
|
131
|
+
|
|
132
|
+
- **Renewal for a budget casualty (#270).** An agent whose turn budget (`timeoutMs`)
|
|
133
|
+
runs out no longer just reports `failed`. When it measurably progressed and its
|
|
134
|
+
role allows renewal, the completion notice ends with a ready-to-paste
|
|
135
|
+
`subagent({ action: "continue", … })` plus the evidence (`progressed: 47 tool
|
|
136
|
+
results, 12 assistant messages, 6 files changed`) and the remaining budget
|
|
137
|
+
(`renewals: 1/2`). Renewal reuses the existing `continue` path, so the child
|
|
138
|
+
resumes from its own session file with its context intact. Two dispatches in one
|
|
139
|
+
day motivated this: one worker was cut off with nothing on disk, another finished
|
|
140
|
+
its code but ran out during verification.
|
|
141
|
+
- **Never automatic.** An auto-renewed stuck child would hold a concurrency slot
|
|
142
|
+
indefinitely; the notice hands over the decision instead.
|
|
143
|
+
- "Progressed" = ≥ 3 new `toolResult` rows or ≥ 1 changed file since the last
|
|
144
|
+
baseline. `assistant` messages are shown but never count — a model looping on
|
|
145
|
+
the same sentence grows that number without doing work.
|
|
146
|
+
- New agent frontmatter: `renewable` (default **true**; a timed-out `scout` means
|
|
147
|
+
the task was mis-scoped) and `maxRenewals` (default **2**; `0` disables, each
|
|
148
|
+
renewal is a fresh full budget). Both are validated in the team-override path
|
|
149
|
+
like every other numeric/boolean field.
|
|
150
|
+
- `wait`/`collect` timeouts never propose a renewal: those return while the child
|
|
151
|
+
is still alive. Only a real termination does, judged by `stopReason: "toolUse"`
|
|
152
|
+
/ missing assistant row (F29) / the host's `terminated` wording — a clean
|
|
153
|
+
`stop` from an ordinary retry does not spend the budget.
|
|
154
|
+
|
|
155
|
+
- **Launch admission queue (#266).** A launch that exceeds a *concurrency* gate
|
|
156
|
+
(`maxChildrenPerNode`, `maxActive`, ledger `max_children_per_node` /
|
|
157
|
+
`max_active_nodes`) no longer fails with `BUDGET_EXCEEDED`. It is enqueued,
|
|
158
|
+
the caller gets `▶ scout-7 (scout) supervisor=inprocess queued(3)` and treats
|
|
159
|
+
the work as dispatched, and the child starts automatically when a slot frees.
|
|
160
|
+
Previously a 12-step fan-out against `maxChildrenPerNode: 8` ran 8 children and
|
|
161
|
+
reported 4 errors — and nothing ever retried them, because the parent was
|
|
162
|
+
already inside `wait` and the freed slot had no subscriber.
|
|
163
|
+
- New settings: `subagents.inprocess.maxQueuedLaunches` (default **32**) and
|
|
164
|
+
`subagents.inprocess.launchQueueTimeoutMs` (default **900000**). Both are
|
|
165
|
+
shared by the in-process and RPC paths — one scheduler, not two.
|
|
166
|
+
- Depth, spawn-per-session, memory, name-collision and config errors are still
|
|
167
|
+
refused synchronously, byte-identically to before: only gates that a slot
|
|
168
|
+
release can satisfy are queueable. `maxChildrenPerNode: 0` is a hard refusal
|
|
169
|
+
(0 means "no children", so waiting can never satisfy it).
|
|
170
|
+
- A queued launch reserves nothing: no ledger row, no child run directory, no
|
|
171
|
+
worktree, no session file. `timeout`/`overflow`/`stop` are reported by name;
|
|
172
|
+
`stop` dequeues without ever spawning, and a parent-turn abort flushes the
|
|
173
|
+
queue and says how many launches it dropped.
|
|
174
|
+
- `subagent({action:"status"})` shows `state=queued`, `collect`/`wait` block
|
|
175
|
+
until admission instead of reading the placeholder session path, and
|
|
176
|
+
`async: false` reports `↳ <name> waited Ns in the launch queue`.
|
|
177
|
+
- Two new process-only events (`launch_queued`, `launch_admitted`) and a
|
|
178
|
+
`legion-queue: N queued` status pill.
|
|
179
|
+
|
|
180
|
+
## [4.0.0] — 2026-10-19
|
|
181
|
+
|
|
182
|
+
A major line — but not only the pi-only collapse. Relative to 3.1.0 this release
|
|
183
|
+
removes every non-`pi` agent kind and the archived Herdr backend behind it, along
|
|
184
|
+
with the settings and error codes that only its transport read. It also carries
|
|
185
|
+
everything else that landed on `master` while that removal was held back: a
|
|
186
|
+
launch admission queue that queues instead of refusing, one liveness verdict
|
|
187
|
+
shared by every display surface, a status panel built on pid / rss /
|
|
188
|
+
context-token data, and ten measured performance fixes.
|
|
189
|
+
|
|
190
|
+
### Breaking
|
|
191
|
+
|
|
192
|
+
- **Kinds collapse to `pi`.** `AGENT_KINDS` is now `["pi"]`; the
|
|
193
|
+
`cursor` / `claude` / `codex` / `gemini` and other third-party CLI kinds are
|
|
194
|
+
gone, and with them the `cursorModel` / `nativeModelFor` / `planKindStart`
|
|
195
|
+
model translation and the "model dropped, runs on its own default" notice
|
|
196
|
+
(a `pi` child expresses every model, so the case no longer exists). `kind` is
|
|
197
|
+
still a legal frontmatter key, but `pi` is the only accepted value: a non-`pi`
|
|
198
|
+
value is ignored — one note on stderr per role (deduplicated, because the
|
|
199
|
+
catalog is re-read on several UI paths) naming the role and the original
|
|
200
|
+
value — and the role launches as `kind: pi`. The same applies to
|
|
201
|
+
`subagents.presets.<role>.kind` and `subagents.agentOverrides.<role>.kind`,
|
|
202
|
+
the three places a role's `kind` can come from. No legacy prefix aliases.
|
|
203
|
+
- **Herdr backend removed.** `src/backends/legacy/` and `test/legacy/` are
|
|
204
|
+
deleted, together with the deprecated `pi-legion/api` re-exports that only
|
|
205
|
+
served them (`createHerdrClient`, `parseHerdrResponse`, `mapHerdrErrorCode`,
|
|
206
|
+
`readPaneDiagnostic`, `createCommandRunner`, `resolveHerdrBin`, `HerdrClient`
|
|
207
|
+
and their types). `pi-legion@0.16.x` stays the pin for the Herdr-era
|
|
208
|
+
execution model; the `spawnMode: rpc` / `inprocess` backends are unaffected —
|
|
209
|
+
those are two ways of running **Pi**, not third-party transports.
|
|
210
|
+
- **Dead `herdr.*` settings removed.** `subagents.herdr.*` is no longer parsed
|
|
211
|
+
(a leftover block is silently ignored, as it has been ignored since 2.0.0),
|
|
212
|
+
the seven `herdr` constants are gone from `DEFAULTS`, and
|
|
213
|
+
`ErrorCodes.HERDR_UNAVAILABLE` / `ErrorCodes.PANE_BUSY` are removed.
|
|
214
|
+
`RunRecord.herdr` and `StatusEntry.herdrStatus` stay: they carry live
|
|
215
|
+
`{supervisor, node_id}` state, not backend selection.
|
|
216
|
+
|
|
217
|
+
### Added
|
|
218
|
+
|
|
219
|
+
- **Launch admission queue (#266).** A launch that exceeds a *concurrency* gate
|
|
220
|
+
(`maxChildrenPerNode`, `maxActive`, ledger `max_children_per_node` /
|
|
221
|
+
`max_active_nodes`) no longer fails with `BUDGET_EXCEEDED`. It is enqueued,
|
|
222
|
+
the caller gets `▶ scout-7 (scout) supervisor=inprocess queued(3)` and treats
|
|
223
|
+
the work as dispatched, and the child starts automatically when a slot frees.
|
|
224
|
+
Previously a 12-step fan-out against `maxChildrenPerNode: 8` ran 8 children and
|
|
225
|
+
reported 4 errors — and nothing ever retried them, because the parent was
|
|
226
|
+
already inside `wait` and the freed slot had no subscriber.
|
|
227
|
+
- New settings: `subagents.inprocess.maxQueuedLaunches` (default **32**) and
|
|
228
|
+
`subagents.inprocess.launchQueueTimeoutMs` (default **900000**). Both are
|
|
229
|
+
shared by the in-process and RPC paths — one scheduler, not two.
|
|
230
|
+
- Depth, spawn-per-session, memory, name-collision and config errors are still
|
|
231
|
+
refused synchronously, byte-identically to before: only gates that a slot
|
|
232
|
+
release can satisfy are queueable. `maxChildrenPerNode: 0` is a hard refusal
|
|
233
|
+
(0 means "no children", so waiting can never satisfy it).
|
|
234
|
+
- A queued launch reserves nothing: no ledger row, no child run directory, no
|
|
235
|
+
worktree, no session file. `timeout`/`overflow`/`stop` are reported by name;
|
|
236
|
+
`stop` dequeues without ever spawning, and a parent-turn abort flushes the
|
|
237
|
+
queue and says how many launches it dropped.
|
|
238
|
+
- `subagent({action:"status"})` shows `state=queued`, `collect`/`wait` block
|
|
239
|
+
until admission instead of reading the placeholder session path, and
|
|
240
|
+
`async: false` reports `↳ <name> waited Ns in the launch queue`.
|
|
241
|
+
- Two new process-only events (`launch_queued`, `launch_admitted`) and a
|
|
242
|
+
`legion-queue: N queued` status pill.
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
- **One liveness verdict for every display surface (#liveness).** The list, the
|
|
246
|
+
text view and the railway chart each decided "is this agent still working?"
|
|
247
|
+
their own way, and the railway one compared two clocks that no single writer
|
|
248
|
+
owned: `eventOverrides` vetoed an event against `node.updatedAt`, but the
|
|
249
|
+
event row is stamped by this process's event bus while `updatedAt` is stamped
|
|
250
|
+
by the child's heartbeat — and only `isAlive` children are heartbeaten, so a
|
|
251
|
+
dead child's `updatedAt` freezes. The comparison ranked two clocks and the
|
|
252
|
+
losing side was the truthful one: a finished node was repainted `running`,
|
|
253
|
+
forever, because the frozen row never moved again. `src/extension/liveness.ts`
|
|
254
|
+
replaces all three with one `liveness(facts)` in TOTAL ORDER — `live` /
|
|
255
|
+
`dead` / `unknown` — with no clock, no fs and no timer of its own. Unknown or
|
|
256
|
+
unrecognized states now render `unknown` instead of being guessed into
|
|
257
|
+
`retired`, and `queued` is a first-class display state.
|
|
258
|
+
- Contract: `docs/contracts/display-liveness-v1.md` (frozen first, then
|
|
259
|
+
implemented; D1-a and D5-a record two amendments found during the work).
|
|
260
|
+
|
|
261
|
+
- **A status panel on the `/legion` board (#241, #245).** A fifth pane carries
|
|
262
|
+
the `/legion status` table onto the full-screen viewer — name · role · state ·
|
|
263
|
+
supervisor · ctx · rss · turns — graded down by absolute column count
|
|
264
|
+
(72/52/36/20) so a narrow board keeps name · state instead of a column of
|
|
265
|
+
stubs. A portrait layout gets a zero-area rect and Tab skips it rather than
|
|
266
|
+
painting a fifth pane that cannot fit. Only an `rpc` child owns a process, so
|
|
267
|
+
only it shows rss; an `inprocess` child shows `—` rather than a share of the
|
|
268
|
+
host figure it does not own, because a fabricated number would get used.
|
|
269
|
+
- `pid`, `spawnMode`, `contextTokens` and `rss` are optional on `VizNode` and
|
|
270
|
+
projected conditionally: a missing mode renders `unknown`, never a guessed
|
|
271
|
+
`rpc`. No new `legion.db` column and no schema-version bump — pid is
|
|
272
|
+
host-scoped and never persisted, so a rebuild from a `ChildRecord` invents
|
|
273
|
+
none.
|
|
274
|
+
- Context tokens are absolute only. Session JSONL carries no context window,
|
|
275
|
+
so no percentage is computed and none is displayed.
|
|
276
|
+
- `/legion status` also stopped dropping `spawnMode` and now supplies `owner`.
|
|
277
|
+
|
|
278
|
+
- **`/legion stop --force` (#179).** The TUI slash command can now hard-retire a
|
|
279
|
+
child, equivalent to the tool's `force: true`; `--force` / `-f` is listed in
|
|
280
|
+
the doctor help and the README. This closes the "optional follow-up" that the
|
|
281
|
+
docs had been carrying.
|
|
282
|
+
|
|
283
|
+
- **`vexillatio` accepts `tasks[]` on its own.** `task` was required, so a
|
|
284
|
+
`{tasks: [...]}` call was rejected by the host's typebox pre-validation before
|
|
285
|
+
Legion ever saw it, and callers had to send a dummy `task` and retry. It is
|
|
286
|
+
optional now; a `tasks[]`-only launch is legal.
|
|
287
|
+
|
|
288
|
+
### Changed
|
|
289
|
+
|
|
290
|
+
- **WAL is now an enforced startup precondition (#236).** `openLegionDb` used to
|
|
291
|
+
discard the value returned by `PRAGMA journal_mode = WAL`. That PRAGMA is a
|
|
292
|
+
*query*: the returned mode is the ground truth, and SQLite is entitled to
|
|
293
|
+
answer with the mode it settled on rather than to fail — which is how a
|
|
294
|
+
connection ends up on a rollback journal while every reader of the code
|
|
295
|
+
believes WAL is live. The ledger's durability reasoning (single-writer
|
|
296
|
+
serialisation, readers that never block the writer, the shutdown checkpoint)
|
|
297
|
+
rests entirely on WAL, so the observed mode is now captured and verified: a
|
|
298
|
+
file database that does not report `wal` throws at startup, with the observed
|
|
299
|
+
mode in the message, instead of surfacing later as data loss. `:memory:` is
|
|
300
|
+
exempt deliberately, keyed on `PRAGMA database_list` reporting an empty
|
|
301
|
+
`file` — not on a database name, and not by accident.
|
|
302
|
+
- The M2-(g) concurrency acceptance now runs as `npm run test:wal-stress`:
|
|
303
|
+
60 supervisor processes against one ledger, 9004 attempts, 9004 commits, 0
|
|
304
|
+
busy, p50 1.03 ms / p95 1.99 ms / p99 4.92 ms.
|
|
305
|
+
|
|
306
|
+
- **The worker role's turn budget is 45 minutes, not 30.** The worker does the
|
|
307
|
+
heaviest per-dispatch work — several files, its own verification run, and a
|
|
308
|
+
report — yet it shared centurion's budget while scout gets 10 minutes and
|
|
309
|
+
reviewer 15. Two dispatches in this repository were cut off mid-work, one
|
|
310
|
+
with nothing on disk and one that ran out while verifying. 45 rather than 60
|
|
311
|
+
keeps a genuinely stuck child from holding a concurrency slot for an hour.
|
|
312
|
+
Only `timeoutMs` changed.
|
|
313
|
+
|
|
314
|
+
### Fixed
|
|
315
|
+
|
|
316
|
+
- **A poison event row no longer freezes the cross-process event poller
|
|
317
|
+
(#233).** `node_abandoned` was missing from the event-type closed set, so a
|
|
318
|
+
snapshot row of that type could not be decoded; the 250 ms poller then threw
|
|
319
|
+
on the same row forever, its cursor never advanced, and no later event was
|
|
320
|
+
ever delivered. The closed set now contains `node_abandoned` (15 types), and
|
|
321
|
+
the poller advances past a single poison row instead of freezing: the row is
|
|
322
|
+
reported once through `onError`, the cursor steps over it, and the remaining
|
|
323
|
+
events in that batch are still delivered.
|
|
324
|
+
|
|
325
|
+
- **A finished supervisor's pid is no longer read as its successor's (#250).**
|
|
326
|
+
`rss` probes re-read `/proc/<pid>` after the child had exited, and Linux
|
|
327
|
+
reuses pids; the panel could attribute a stranger's memory to a retired node.
|
|
328
|
+
The probe now closes the race it was silently losing.
|
|
329
|
+
|
|
330
|
+
- **The log view stays inside its rect on narrow panes (#251).** `drawLog` wrote
|
|
331
|
+
past its own `maxX` when the pane was squeezed, which corrupted the frame
|
|
332
|
+
beside it.
|
|
333
|
+
|
|
334
|
+
- **The live overlay no longer scans the whole tree per token (#261).** A
|
|
335
|
+
per-token `getTreeNodes()` call made a long child turn quadratic in the number
|
|
336
|
+
of nodes; the scan is gone and re-opening the node that is already open is
|
|
337
|
+
guarded.
|
|
338
|
+
|
|
339
|
+
- **Repository subdirectories are no longer mistaken for child worktrees
|
|
340
|
+
(#237 P10).** The probe treated any git-visible path below the repo root as an
|
|
341
|
+
existing worktree, so a dispatch from `src/` could reuse the wrong directory.
|
|
342
|
+
All git probes are now bounded: `GIT_PROBE_TIMEOUT_MS = 5_000` and
|
|
343
|
+
`GIT_WORKTREE_TIMEOUT_MS = 120_000`, so a hung `git` can no longer wedge a
|
|
344
|
+
launch forever.
|
|
345
|
+
|
|
346
|
+
- **`SubagentError` is exported explicitly again.** It had reached
|
|
347
|
+
`pi-legion/api` only as a side effect of the legacy client re-exporting it, so
|
|
348
|
+
removing the Herdr backend silently dropped it from the public surface. It is
|
|
349
|
+
a deliberate export now, pinned by a test.
|
|
350
|
+
|
|
351
|
+
### Performance
|
|
352
|
+
|
|
353
|
+
Ten fixes measured against the baseline in `docs/performance-2026-10-19.md`,
|
|
354
|
+
plus the harness that measured them (#239). Numbers below are the before/after
|
|
355
|
+
from each fix's own commit.
|
|
356
|
+
|
|
357
|
+
- **`collect()` no longer waits out a fixed settle-quiet window (#232).** An
|
|
358
|
+
already-settled turn cost a flat ~2.5 s of dead time; that wait is dropped when
|
|
359
|
+
the turn is settled (2509 ms → 503 ms).
|
|
360
|
+
- **The settle polls back off instead of re-parsing every 500 ms (#232).** The
|
|
361
|
+
transient-connection wait re-parsed the whole session file on every tick. It
|
|
362
|
+
now keeps the last parse, re-parses only when the artifact size moved, and
|
|
363
|
+
backs off 500 ms → 4 s. On an unchanged file in a 900 s window: 1800
|
|
364
|
+
iterations / 1804 parses → 228 iterations / 5 parses. The blip tolerance is
|
|
365
|
+
unchanged — sleeps are clamped to the remaining budget and the loop still ends
|
|
366
|
+
exactly on the deadline.
|
|
367
|
+
- **The `/legion` log view reads the session incrementally (#234).** It seeks
|
|
368
|
+
past the cached byte offset and projects only the new JSONL lines, instead of
|
|
369
|
+
re-parsing from the top on every refresh.
|
|
370
|
+
- **`attachUsageTotals` is one batched query (#235/#248).** It accounted for
|
|
371
|
+
~72% of the cost of a UI snapshot; it is now a chunked `IN` query instead of a
|
|
372
|
+
per-node round trip.
|
|
373
|
+
- **Heartbeats are one transaction (#236 P1).** N children meant N commits;
|
|
374
|
+
eight children now cost one (3.83 ms → 0.99 ms, 3.9×).
|
|
375
|
+
- **Prepared statements are cached per connection (#236 P2).** `getNode` ×400
|
|
376
|
+
drops 15.7 ms → 4.8 ms.
|
|
377
|
+
- **Session parsing projects its result instead of `structuredClone` (#237 P7).**
|
|
378
|
+
- **The DB event source seeds its cursor at attach instead of replaying the log
|
|
379
|
+
(#238).** Attaching used to block ~1027 ms on replay.
|
|
380
|
+
- **Widget events are coalesced into one snapshot and one paint per tick
|
|
381
|
+
(#238).** `legion-tools` emits N `mail_sent` events in one synchronous loop, so
|
|
382
|
+
every burst cost 2N scans; a burst now costs a constant 2 snapshots — 2575 ms
|
|
383
|
+
→ 4.4 ms for 1000 events over a 120-node ledger. `applyTreeEvent` itself still
|
|
384
|
+
runs synchronously, per event, in arrival order: it is an incremental state
|
|
385
|
+
machine, not a render, so deferring or reordering it would change the state it
|
|
386
|
+
computes.
|
|
387
|
+
- **The messages rate-limit `COUNT` is covered by an index (#237 P9).** 50k rows
|
|
388
|
+
go from a `SCAN` to ~6.6 µs.
|
|
389
|
+
|
|
14
390
|
## [3.1.0] — 2026-10-19
|
|
15
391
|
|
|
16
392
|
### Added
|
|
@@ -436,12 +812,16 @@ First **Pi Legion** line on headless Pi RPC. Relative to the final Herdr-era
|
|
|
436
812
|
|
|
437
813
|
Last line that runs children in Herdr panes. Not developed further on this
|
|
438
814
|
branch — pin `pi-legion@0.16.x` if you still need that model. See
|
|
439
|
-
[Herdr transition](README.md#
|
|
815
|
+
[Herdr transition](README.md#supported-kinds) in the README.
|
|
440
816
|
|
|
441
817
|
Earlier 0.11–0.15 history (including the `pi-herdr-swarm` /
|
|
442
818
|
`@zzjcool/pi-herdr-subagents` renames) lives in git tags and older Releases.
|
|
443
819
|
|
|
444
|
-
[Unreleased]: https://github.com/zzjcool/pi-legion/compare/
|
|
820
|
+
[Unreleased]: https://github.com/zzjcool/pi-legion/compare/v5.0.0...HEAD
|
|
821
|
+
[5.0.0]: https://github.com/zzjcool/pi-legion/compare/v4.0.0...v5.0.0
|
|
822
|
+
[4.0.0]: https://github.com/zzjcool/pi-legion/compare/v3.1.0...v4.0.0
|
|
823
|
+
[3.1.0]: https://github.com/zzjcool/pi-legion/compare/v3.0.3...v3.1.0
|
|
824
|
+
[3.0.3]: https://github.com/zzjcool/pi-legion/compare/v3.0.2...v3.0.3
|
|
445
825
|
[3.0.2]: https://github.com/zzjcool/pi-legion/compare/v3.0.1...v3.0.2
|
|
446
826
|
[3.0.1]: https://github.com/zzjcool/pi-legion/compare/v3.0.0...v3.0.1
|
|
447
827
|
[3.0.0]: https://github.com/zzjcool/pi-legion/compare/v2.0.4...v3.0.0
|
package/CONTRIBUTING.md
CHANGED
|
@@ -107,10 +107,10 @@ the top of that file). Do not fail CI on it.
|
|
|
107
107
|
Prefer **TDD** for behaviour changes: a failing test in `test/unit` or
|
|
108
108
|
`test/integration` first, then the production code. Integration tests should
|
|
109
109
|
drive the orchestrator through `FakeSupervisor` (`test/helpers/fake-supervisor.ts`)
|
|
110
|
-
so nesting / launch / collect stay covered without a live `pi`. The
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
so nesting / launch / collect stay covered without a live `pi`. The old
|
|
111
|
+
herdr-era `createHerdrClient` / `createFakeRunner` helpers are **gone** as of
|
|
112
|
+
v4.0.0, together with `src/backends/legacy/` and `test/legacy/`; everything
|
|
113
|
+
runs as `kind: pi`, in-process or over `pi --mode rpc`.
|
|
114
114
|
|
|
115
115
|
## Mock LLM
|
|
116
116
|
|
|
@@ -161,19 +161,6 @@ Or all at once (RPC mainline **1–11** only):
|
|
|
161
161
|
npm run test:docker
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
-
**Legacy Herdr / Space / kind checks** live under `test/legacy/docker/` (need
|
|
165
|
-
pi-legion **0.16.x** Herdr-era setup). They are **not** part of
|
|
166
|
-
`npm run test:docker`:
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
bash test/legacy/docker/verify-12-workspace.sh # Herdr Space pin (legacy)
|
|
170
|
-
bash test/legacy/docker/verify-13-kinds.sh # kind/model matrix; live CodeBuddy when mounted
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
`verify-13-kinds.sh` also bind-mounts host `codebuddy` / `cursor-agent` and
|
|
174
|
-
(unless `LIVE_KIND_PROBES=0`) calls live CodeBuddy. The numbered 1–11 scripts
|
|
175
|
-
under `test/docker/` stay offline/mock.
|
|
176
|
-
|
|
177
164
|
Override paths with `HOST_PI_AGENT`, `DOCKER_PI_HOME`, `PI_DOCKER_IMAGE`.
|
|
178
165
|
|
|
179
166
|
Each `inside-N-*.sh` is what actually runs **in** the container. Keep those
|