@ferris1225/pi-subagents 4.3.4 → 4.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +103 -83
  3. package/agents/artisan.md +0 -1
  4. package/agents/steward.md +1 -2
  5. package/{src/index.ts → index.ts} +19 -19
  6. package/package.json +4 -3
  7. package/src/{config.ts → configuration/config.ts} +19 -24
  8. package/src/configuration/setup.ts +375 -0
  9. package/src/configuration/ui.ts +245 -0
  10. package/src/{agents.ts → delegation/agents.ts} +3 -3
  11. package/src/{dispatch.ts → delegation/dispatch.ts} +12 -18
  12. package/src/{prompt.ts → delegation/prompt.ts} +5 -9
  13. package/src/{background.ts → execution/background.ts} +3 -6
  14. package/src/execution/rpc-control.ts +235 -0
  15. package/src/{rpc-run.ts → execution/rpc-run.ts} +35 -225
  16. package/src/{session-fork.ts → execution/session-fork.ts} +1 -1
  17. package/src/{spawn.ts → execution/spawn.ts} +10 -8
  18. package/src/isolation/git-command.ts +147 -0
  19. package/src/isolation/managed-paths.ts +145 -0
  20. package/src/{recovery.ts → isolation/recovery.ts} +42 -13
  21. package/src/{temp-hygiene.ts → isolation/temp-hygiene.ts} +7 -7
  22. package/src/{worktree.ts → isolation/worktree.ts} +11 -158
  23. package/src/{completion.ts → lifecycle/completion.ts} +2 -2
  24. package/src/{durable.ts → lifecycle/durable.ts} +101 -27
  25. package/src/{runtime.ts → lifecycle/runtime.ts} +12 -12
  26. package/src/{thread-lifecycle.ts → lifecycle/thread-lifecycle.ts} +25 -519
  27. package/src/lifecycle/thread-restore.ts +253 -0
  28. package/src/lifecycle/thread-shared.ts +269 -0
  29. package/src/{tools.ts → lifecycle/tools.ts} +51 -13
  30. package/src/{announcements.ts → presentation/announcements.ts} +4 -4
  31. package/src/{format.ts → presentation/format.ts} +3 -3
  32. package/src/{monitor.ts → presentation/monitor.ts} +2 -2
  33. package/src/{widget.ts → presentation/widget.ts} +1 -1
  34. package/agents/sentinel.md +0 -16
  35. package/src/setup.ts +0 -344
  36. package/src/ui.ts +0 -160
  37. /package/src/{models.ts → configuration/models.ts} +0 -0
  38. /package/src/{status.ts → presentation/status.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,42 @@ Published versions of `@ferris1225/pi-subagents`. Unpublished numbers
4
4
  (`4.2.3`, `4.2.6`, `4.2.9`–`4.2.11`) never shipped on npm; their changes
5
5
  landed in the next published release.
6
6
 
7
+ ## 4.3.6
8
+
9
+ - Add `subagent_control steer` for nonblank, parent-mediated guidance to the current active
10
+ RPC attempt without replacing its logical objective. Stable control serialization orders
11
+ steering against stop and AbortSignal shutdown; bounded ACKs keep stop responsive.
12
+ - Refine the lean delegation directive: main owns architecture; for one high-stakes
13
+ uncertainty at most two read-only scouts may examine distinct hypotheses, without
14
+ capping unrelated disjoint scout scopes; main reconciles cited evidence, writers
15
+ and briefs never overlap, and new evidence steers the existing phase instead of
16
+ duplicating or restarting it.
17
+ - Steer background completions and stop results into the next parent model boundary
18
+ instead of queuing them until the whole parent run settles, preventing stale notifications
19
+ from arriving after the main agent has already finished.
20
+ - Validate durable session, worktree, repository, and recovery paths against their canonical
21
+ project-scoped layout before restore or cleanup. Forged and junction-escaping records are
22
+ dropped without deleting external targets.
23
+ - Keep worktree and patch artifacts referenced by recovery records through durable sweeps and
24
+ stale project-root retention until their recovery notice can be delivered.
25
+ - Clarify that worktree isolation protects Git changes rather than sandboxing child privileges
26
+ or third-party Pi packages.
27
+
28
+ ## 4.3.5
29
+
30
+ - Restore `/subagents-setup`'s nested menu flow, fuzzy model picker, and disabled
31
+ custom-role discovery.
32
+ - Retire the built-in `sentinel` role and mandatory pre-commit review workflow.
33
+ Config normalization, loading, and saving now remove its enabled/known entries
34
+ and model/thinking overrides while preserving every other custom role.
35
+ - Keep role prompts self-contained: direct diagnosis, test, and cleanup rules remain,
36
+ while external loading directives are removed.
37
+ - Delegate substantial independent phases more proactively while capping the child-process
38
+ pool at six and retaining phase leases, duplicate-dispatch rejection, and single-route delivery.
39
+ - Move the extension assembly to package-root `index.ts`, group `src/` by responsibility,
40
+ and split thread restoration, shared lifecycle coordination, RPC control, and Git command
41
+ execution out of the largest modules. Pi now shows the package name without a `:src` suffix.
42
+
7
43
  ## 4.3.4
8
44
 
9
45
  - Keep artisan, steward, and sentinel fully usable when Ferris skills are absent.
package/README.md CHANGED
@@ -6,15 +6,16 @@
6
6
  ![platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey)
7
7
  ![pi](https://img.shields.io/badge/pi-extension-orange)
8
8
 
9
- A managed engineering team for [pi](https://github.com/earendil-works/pi): four
9
+ A managed engineering team for [pi](https://github.com/earendil-works/pi): three
10
10
  focused sub-agents, durable threads, and Git worktree isolation. You install it
11
11
  once and your main agent delegates on its own.
12
12
 
13
13
  ## What's new
14
14
 
15
- **4.3.4** — makes Ferris skills optional enhancements instead of runtime
16
- requirements. Artisan, steward, and sentinel remain fully operational from their
17
- standalone role prompts when users have no Ferris skills installed.
15
+ **4.3.6** — adds live parent-mediated steering for running children, delivers background
16
+ completions and stop results at the next safe parent boundary, and hardens durable
17
+ recovery paths without treating Git worktrees as a security sandbox.
18
+
18
19
  See [CHANGELOG.md](./CHANGELOG.md).
19
20
 
20
21
  ## Contents
@@ -24,7 +25,7 @@ See [CHANGELOG.md](./CHANGELOG.md).
24
25
  - [The team](#the-team)
25
26
  - [Dispatching work](#dispatching-work)
26
27
  - [Parallel edits](#parallel-edits)
27
- - [Threads: resume, stop](#threads-resume-stop)
28
+ - [Threads: steer, resume, stop](#threads-steer-resume-stop)
28
29
  - [Live status and results](#live-status-and-results)
29
30
  - [Models, thinking, and tools](#models-thinking-and-tools)
30
31
  - [Configuration](#configuration)
@@ -41,12 +42,13 @@ at "spawn a child with a prompt" and leave the hard parts — when to delegate,
41
42
  wide to fan out, what happens when a model dies, how results come
42
43
  back — with you. This extension owns them:
43
44
 
44
- - The main model gets a cost-aware routing contract and delegates only when a leaf
45
- context saves more work than its handoff costs.
45
+ - The main model gets a cost-aware routing contract and proactively delegates
46
+ substantial self-contained phases when a fresh context saves more work than its
47
+ handoff costs.
46
48
  - One active normalized task and working directory owns its phase, so an exact
47
49
  duplicate dispatch is rejected instead of paying twice.
48
- - Background completions wake the main model; `wait: true` returns the same result
49
- in-turn instead. A run uses exactly one route.
50
+ - Background completions and stop results arrive at the next parent model boundary;
51
+ `wait: true` returns the same result in-turn instead. A run uses exactly one route.
50
52
  - Parallel writers use detached Git worktrees without touching your index.
51
53
  Worktree setup obeys the bounded queue; final integration releases its process
52
54
  slot.
@@ -63,11 +65,14 @@ Requires **pi >= 0.84.4** and **Node.js >= 22.19.0**.
63
65
  pi install npm:@ferris1225/pi-subagents
64
66
  ```
65
67
 
66
- Open pi and run `/subagents-setup`. One overlay shows every role's enabled state,
67
- model, and effective thinking level. Move through the grid, search models in place,
68
- then choose **Save & Exit** to write everything once; **Cancel** or `Esc` discards
69
- the draft. Fresh installs select all four, and an upgrade surfaces a new built-in
70
- once without re-enabling it after you deliberately turn it off. Then ask for work:
68
+ Pi's extension list shows `@ferris1225/pi-subagents` without an internal source-path suffix.
69
+
70
+ Open pi and run `/subagents-setup`. The original menu flow lets you enable or
71
+ disable roles, configure one role's model and thinking level, or run the full setup
72
+ again. Each screen uses the usual arrow-key/Enter/Esc navigation, and model lists
73
+ remain searchable. Fresh installs select all three. A newly shipped built-in is
74
+ surfaced once without being re-enabled after you deliberately turn it off. Then ask
75
+ for work:
71
76
 
72
77
  ```text
73
78
  Map how authentication works, fix the refresh race, run the tests, and review the diff.
@@ -78,18 +83,14 @@ directly when you want exact control.
78
83
 
79
84
  ## The team
80
85
 
81
- | Agent | Access | Owns |
82
- | ---------- | ----------- | ---- |
83
- | `scout` | Read-only | Broad or unfamiliar code reconnaissance and external research. Returns compact file citations or source URLs as leads, not proof. |
84
- | `artisan` | Full | One substantial primary change—implementation, fix, refactor, test, or docs—through root cause, affected verification, and local hygiene. |
85
- | `steward` | Full | One final cleanup and cross-cutting docs/comment sync pass after a broad or multi-writer change. |
86
- | `sentinel` | Review-only | A post-cleanup adversarial review using standalone evidence gates plus any matching Ferris skills; reports only evidence-backed defects and concrete test gaps. |
86
+ | Agent | Access | Owns |
87
+ | --------- | --------- | ---- |
88
+ | `scout` | Read-only | Broad or unfamiliar code reconnaissance and external research. Returns compact file citations or source URLs as leads, not proof. |
89
+ | `artisan` | Full | One substantial primary change—implementation, fix, refactor, test, or docs—through root cause, affected verification, and local hygiene. |
90
+ | `steward` | Full | One final cleanup and cross-cutting docs/comment sync pass after a broad or multi-writer change. |
87
91
 
88
- Role prompts are standalone: they embed root-cause-first diagnosis, meaningful test
89
- evidence, bounded cleanup, and evidence-only review. Optional Ferris skills remain
90
- the canonical source of deeper language, platform, debugging, testing, and audit
91
- guidance. Artisan and sentinel load matching skills when available; steward uses
92
- `ferris-audit`; a missing skill never blocks a role or changes its ownership contract.
92
+ Role prompts are self-contained and directly embed root-cause-first diagnosis,
93
+ meaningful test evidence, and bounded cleanup.
93
94
 
94
95
  Custom roles join them with a Markdown file (see [Custom agents](#custom-agents)).
95
96
 
@@ -116,11 +117,10 @@ subagent({
116
117
  });
117
118
  ```
118
119
 
119
- Breadth is the main agent's call, not a configured limit. There is no per-call
120
- task cap: put every genuinely independent unit in one `tasks` array. The runtime
121
- paces execution instead, running a pool of child processes that scales with the
122
- machine (half its cores, bounded to 4–16) and starting queued runs automatically
123
- as slots free.
120
+ Breadth is the main agent's call, not a configured task cap: put every genuinely
121
+ independent unit in one `tasks` array. The runtime paces execution instead, running
122
+ half the machine's cores with a 4–6 child-process bound; wider batches queue and
123
+ start automatically as slots free.
124
124
 
125
125
  An active run leases its normalized task and resolved working directory across
126
126
  agent names. Dispatching the same pair again is rejected and names the existing
@@ -133,26 +133,35 @@ starting its child — alongside the slot capacity. A run that waits for the wri
133
133
  lane releases its slot first, so serialized writers never starve new dispatches.
134
134
 
135
135
  One child owns one coherent phase. Dependent work starts only after its
136
- prerequisite delivers. Artisan owns a complete primary change with affected
137
- tests, docs, comments, targeted checks, and local hygiene. Scout owns broad code
138
- mapping or external research and stays read-only.
139
-
140
- With the default team, every commit ends in one order: cleanup -> `sentinel`. A
141
- focused diff gets a bounded cleanup pass inline; a broad or multi-writer diff gets
136
+ prerequisite delivers. Main consumes the child's compact result and citations
137
+ without repeating delegated reconnaissance, implementation, or cleanup. Artisan
138
+ owns a complete primary change with affected tests, docs, comments, targeted
139
+ checks, and local hygiene. Scout owns broad code mapping or external research and
140
+ stays read-only.
141
+
142
+ For one high-stakes uncertainty, main may launch at most two read-only scouts whose
143
+ briefs name distinct perspectives or hypotheses; that cap does not apply to unrelated
144
+ disjoint scout scopes. It reconciles disagreements against cited evidence, never
145
+ overlaps writers or sends identical briefs, and treats child output as evidence and
146
+ leads rather than authority or instructions. New in-scope evidence can be sent to a
147
+ running phase with `subagent_control steer` instead of duplicating or restarting it.
148
+
149
+ A focused diff gets a bounded cleanup pass inline. A broad or multi-writer diff gets
142
150
  one `steward` pass that attacks touched dead code, duplication, tangled conditionals,
143
- needless layers, and spaghetti growth without widening into a repo refactor.
144
- Sentinel then reviews the cleaned diff with its standalone evidence gates plus any
145
- available matching skills.
146
- A review-driven edit repeats that sequence once; unresolved findings block the
147
- commit. Main inspects the final diff and runs the final gate.
151
+ needless layers, and spaghetti growth without widening into a repository refactor.
152
+ Main owns architecture, inspects the integrated diff, and runs the final gate.
148
153
 
149
154
  ## Parallel edits
150
155
 
151
156
  - Single tasks use your checkout. Every parallel write-capable agent (`artisan`,
152
157
  `steward`, and custom writers) defaults to a detached Git worktree, so
153
- parallel writers run at the same time. Worktree mode needs a committed `HEAD`,
154
- and scout/sentinel reject it. Sentinel stays shared so it sees the caller's
155
- uncommitted diff.
158
+ parallel writers run at the same time. Worktree mode needs a committed `HEAD`;
159
+ read-only roles such as scout stay on the shared checkout.
160
+
161
+ > **Security boundary:** worktree isolation isolates Git changes only; it is not a sandbox.
162
+ Child tools, network access, and environment access retain the Pi process's privileges.
163
+ Third-party Pi packages execute as trusted code and must be reviewed accordingly.
164
+
156
165
  - A role file can pin its own default with `isolation: worktree` or
157
166
  `isolation: shared` in the frontmatter. Precedence is an explicit per-dispatch
158
167
  `isolation`, then the role's declaration, then the parallel write default.
@@ -172,22 +181,33 @@ commit. Main inspects the final diff and runs the final gate.
172
181
  already been applied and only the cleanup failed, the next session start
173
182
  removes the retained copy itself and clears the notice.
174
183
 
175
- ## Threads: resume, stop
184
+ ## Threads: steer, resume, stop
176
185
 
177
186
  Every dispatch returns a stable `#id`, which is the handle for the thread tools:
178
187
 
179
- | Tool | What it does |
180
- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
181
- | `subagent_control` | `resume` a parked or settled thread with its full retained context, optionally appending a new `objective`. |
182
- | `subagent_stop` | Destructively cancel, deliver the partial output, and retire the thread. Steering and follow-up messages still queued in the child are dropped so nothing can revive it later. |
188
+ | Tool | What it does |
189
+ | ------------------ | ------------ |
190
+ | `subagent_control` | `steer` a currently running RPC attempt with additional in-scope evidence/guidance, or `resume` a parked/settled thread with retained context and an optional appended `objective`. |
191
+ | `subagent_stop` | Destructively cancel, deliver partial output, and retire the thread. Steering and follow-up messages still queued in the child are dropped so nothing can revive it later. |
183
192
 
184
193
  ```ts
194
+ subagent_control({ action: "steer", id: 7, objective: "The failing request used an expired token; account for that evidence." });
185
195
  subagent_control({ action: "resume", id: 7, objective: "Finish the tests." });
186
196
  ```
187
197
 
198
+ `steer` requires a nonblank `objective` and accepts only the active running RPC
199
+ attempt for that stable id. It adds guidance to the current phase; it does not replace
200
+ the thread's original task. Parked, settled, queued, starting, retrying, interrupting,
201
+ stopped, retired, and missing threads are rejected without changing them. Steering ACKs
202
+ are bounded, and steering/stop are serialized so stop can clear queued child messages
203
+ and abort without a stale steer landing afterward.
204
+
188
205
  There is no status, polling, or separate wait tool. A background dispatch returns
189
- a launch receipt, then its completion is delivered as a follow-up that wakes the
190
- main model. `wait: true` instead holds that tool call until its new runs settle,
206
+ a launch receipt, then its completion is steered at the next safe parent boundary—after
207
+ the current tool calls and before the next model call. This wakes the main model without
208
+ waiting for its whole run to end.
209
+
210
+ `wait: true` instead holds that tool call until its new runs settle,
191
211
  which is useful for one-shot `pi -p` sessions. It claims the delivery route before
192
212
  launch, so the same result cannot also arrive as a background completion; if the
193
213
  parent turn is aborted, delivery falls back to the completion path.
@@ -210,6 +230,11 @@ missing or reused. If a recorded worktree is gone, the run is surfaced as failed
210
230
  and non-resumable while its retained session and recovery record remain available
211
231
  for inspection or destructive stop.
212
232
 
233
+ Persisted sessions and worktrees are resumed or removed only when their canonical paths
234
+ match the current project's managed storage layout and repository. Invalid records are
235
+ dropped without following or deleting their targets. Recovery-owned worktrees and patches
236
+ remain protected from startup sweeps and project-root retention until recovery is announced.
237
+
213
238
  Only interrupted work needs a record, so a thread that completes or fails cleanly
214
239
  drops its own. That also means a reload keeps interrupted threads resumable, while
215
240
  threads that had already finished keep only their delivered result.
@@ -279,17 +304,15 @@ rather than a fabricated number.
279
304
  ## Models, thinking, and tools
280
305
 
281
306
  Each agent runs on the current main model or one picked in `/subagents-setup`,
282
- which labels vision and text-only models. Without its own override, `sentinel`
283
- uses `artisan`'s configured model; if artisan also follows main, sentinel does
284
- too. If a selected model is missing, rate-limited, or fails at the provider level,
285
- the **same retained
307
+ which labels vision and text-only models. If a selected model is missing,
308
+ rate-limited, or fails at the provider level, the **same retained
286
309
  session** continues on the main model, so finished searches, reads, and edits
287
310
  survive. Ordinary task failures do not trigger a handoff.
288
311
 
289
- Thinking is a **role default** — scout `low`, artisan `high`, steward `medium`,
290
- sentinel `max` — clamped to what the effective model supports. The unified setup
291
- grid shows the effective level; changing the thinking cell cycles only supported
292
- levels, and returning to the role default clears the stored override. There is no
312
+ Thinking is a **role default** — scout `low`, artisan `high`, steward `medium` —
313
+ clamped to what the effective model supports. `/subagents-setup`
314
+ _Configure an agent_ lists only the levels that model supports and marks the role
315
+ default; selecting it clears the stored override. There is no
293
316
  Auto choice, no per-dispatch `thinking` flag, and
294
317
  no `thinking` field in agent Markdown. Precedence: your setup override > the
295
318
  role default, then the model clamp. There is no separate vision mode — assign
@@ -314,25 +337,19 @@ For external research scout prefers official documentation, specifications, rele
314
337
  notes, and first-party repositories; it fetches decisive pages rather than citing
315
338
  search snippets, records material dates/versions, and marks uncertainty.
316
339
 
317
- `sentinel` has an explicit retrieval/documentation list plus a portable shell
318
- slot for Git inspection and the smallest proving check. It is pinned to `shared`
319
- so it sees the current uncommitted diff. Its concise prompt uses matching available
320
- Ferris skills, preserves their owners, and forbids mutation; missing skills do not
321
- block review. This is a review contract,
322
- not a hard shell sandbox.
323
-
324
340
  ## Configuration
325
341
 
326
- `/subagents-setup` opens one transactional overlay for every built-in and already
327
- configured custom role. Its grid edits enabled state, model, and thinking before
328
- **Save & Exit** persists the complete draft; **Cancel**/`Esc` writes nothing. Model
329
- selection remains inside the overlay and supports fuzzy search. Other settings live in
342
+ `/subagents-setup` opens the original settings menu: enable or disable roles,
343
+ configure one enabled role's model and thinking level, or walk through a full
344
+ re-setup. `Esc` moves back through the menu stack, and model lists support fuzzy
345
+ search. Built-in and previously configured custom roles remain available in the
346
+ enable menu. Other settings live in
330
347
  `~/.pi/agent/pi-subagents.json` (following `PI_CODING_AGENT_DIR`):
331
348
 
332
349
  ```json
333
350
  {
334
- "enabledAgents": ["scout", "artisan", "steward", "sentinel"],
335
- "knownAgents": ["scout", "artisan", "steward", "sentinel"],
351
+ "enabledAgents": ["scout", "artisan", "steward"],
352
+ "knownAgents": ["scout", "artisan", "steward"],
336
353
  "agentModels": { "scout": "anthropic/claude-haiku-4-5" },
337
354
  "agentThinkingLevels": { "artisan": "high" },
338
355
  "maxResultLines": 40,
@@ -345,7 +362,7 @@ selection remains inside the overlay and supports fuzzy search. Other settings l
345
362
  | --------------------- | ------- |
346
363
  | `enabledAgents` | Agents available for discovery and delegation. `[]` disables all. |
347
364
  | `knownAgents` | Roles already surfaced by setup; retains disabled custom roles and tracks built-in adoption. |
348
- | `agentModels` | Optional model per agent; missing means main, except sentinel inherits artisan's override. |
365
+ | `agentModels` | Optional model per agent; missing means the current main model. |
349
366
  | `agentThinkingLevels` | Optional setup override per agent; missing means the role default. |
350
367
  | `maxResultLines` | Lines kept in a completion message before the artifact takes over. Default `40`. |
351
368
  | `agentScope` | Discover `user`, `project`, or `both` agent directories. Default `user`. |
@@ -354,8 +371,10 @@ selection remains inside the overlay and supports fuzzy search. Other settings l
354
371
  When at least one role is enabled, the cost-aware delegation directive is injected
355
372
  automatically. `enabledAgents` is authoritative after catalog adoption: a newly
356
373
  shipped built-in is appended once, then `knownAgents` records that it was surfaced
357
- so a deliberate later disable remains disabled. Invalid known fields fall back
358
- safely, and unknown fields are dropped when canonical config is persisted.
374
+ so a deliberate later disable remains disabled. On upgrade, the retired `sentinel`
375
+ entry is removed from enabled and known agents plus model and thinking overrides;
376
+ all other custom roles and known-agent entries remain intact. Invalid known fields
377
+ fall back safely, and unknown fields are dropped when canonical config is persisted.
359
378
 
360
379
  At session start, model overrides that pi no longer reports are removed with a
361
380
  one-time notice. If pi's own session compaction fails mid-thread, a notice surfaces
@@ -398,16 +417,16 @@ that removes it, so this directory does not grow without bound:
398
417
  | `ferris-pi-subagents/pi-subagents-recovery.json` | Worktree integration and cleanup failures | When the retained patch or worktree it points at is gone |
399
418
  | `ferris-pi-subagents/<project>/pi-subagents-threads.json` | One record per interrupted thread | When the thread settles, or after 30 days |
400
419
  | `ferris-pi-subagents/<project>/sessions/` | Retained child sessions that a resume continues from | When the thread settles or its retained record is removed |
401
- | `ferris-pi-subagents/<project>/worktrees/` | Isolated checkouts for parallel writers | On integration, or when no retained record claims them |
420
+ | `ferris-pi-subagents/<project>/worktrees/` | Isolated checkouts for parallel writers | On integration, or when no thread/recovery record claims them |
402
421
  | `ferris-pi-subagents/<project>/results/` | Full text of truncated results | After 7 days, or beyond 50 per project |
403
422
  | `ferris-pi-subagents/<project>/tmp/` | Child prompt copies and the no-retry policy shim | When its owning process exits |
404
- | `ferris-pi-subagents/<project>/` | All of the above for one checkout | When the whole directory has been idle for 3 days |
423
+ | `ferris-pi-subagents/<project>/` | All of the above for one checkout | After 3 idle days unless a thread/recovery record claims it |
405
424
 
406
425
  Cleanup runs at session start and is deliberately conservative. A directory goes
407
- away only when the process that created it is gone and no manifest record still
408
- claims it, so a live sibling pi instance never loses state and parked work
409
- outlives its own process by design a reference from the threads manifest always
410
- beats an age rule.
426
+ away only when the process that created it is gone and no valid manifest record still
427
+ claims it, so a live sibling pi instance never loses state and parked or recovery-owned
428
+ work outlives its own process by design. Thread and recovery references always beat an
429
+ age rule.
411
430
 
412
431
  ## Development
413
432
 
@@ -418,8 +437,9 @@ npm run check
418
437
 
419
438
  `npm run check` is `tsc --noEmit` plus the unit tests (`npm test`). There are
420
439
  no bundled runtime dependencies; pi and TypeBox are peers. The source is
421
- split by responsibility: dispatch policy, thread lifecycle, RPC
422
- transport, worktree integration, completion delivery, tools, and TUI status.
440
+ grouped by responsibility under `src/`: configuration, delegation, execution, isolation,
441
+ lifecycle, and presentation. Thread restoration, shared lifecycle coordination, RPC control,
442
+ and Git command execution live in focused modules rather than oversized catch-all files.
423
443
 
424
444
  ## Changelog
425
445
 
package/agents/artisan.md CHANGED
@@ -7,7 +7,6 @@ You own one primary change phase: implementation, fix, refactor, test, or substa
7
7
 
8
8
  ## Rules
9
9
 
10
- - Use matching ferris skills when available: `ferris-debug` for unexplained failures, `ferris-tests` for test changes, and the relevant language or platform skill. Missing skills are not a blocker; the rules below are the fallback contract.
11
10
  - For a reported defect or failure, inspect current behavior, confirm the defect before editing, and establish its root cause; a disproved issue means zero edits.
12
11
  - Make the smallest coherent root-cause change. Preserve unrelated work and existing conventions; avoid speculative abstractions and unrelated cleanup.
13
12
  - Own the complete primary change plus directly affected tests, README/docs, comments, and local diff hygiene. Remove debug output, dead code, stale comments, and other debris introduced in your scope.
package/agents/steward.md CHANGED
@@ -3,11 +3,10 @@ name: steward
3
3
  description: Pre-commit cleanup and cross-cutting docs/comment sync for a completed broad or multi-writer change.
4
4
  ---
5
5
 
6
- You own one final hygiene phase after primary writing has finished and before sentinel review. The task brief is your only context.
6
+ You own one final hygiene phase after primary writing has finished. The task brief is your only context.
7
7
 
8
8
  ## Rules
9
9
 
10
- - Use `ferris-audit` when available and apply its automatic pre-commit tier. Missing skills are not a blocker; the rules below are the fallback contract and must not widen into repo-wide cleanup.
11
10
  - Require a named completed scope such as an uncommitted diff or Git range. Start there; never repeat implementation or reconnaissance, and stop if primary writing is still active.
12
11
  - Hunt hard inside the touched scope: dead or unreachable code, unused imports/exports, duplicated facts or branches, debug residue, stale comments, one-off flags, tangled conditionals, pass-through wrappers, cast/optional fallback sprawl, feature logic in shared paths, and growth toward 1000-line files.
13
12
  - Prefer deleting branches, state, and layers; otherwise reuse the canonical helper. Never merely move spaghetti. Prove every cut has no live consumer, and keep uncertain dynamic behavior, public APIs, persisted formats, and compatibility.
@@ -3,12 +3,12 @@
3
3
  *
4
4
  * Assembly point: builds the shared runtime and registers everything.
5
5
  * The heavy lifting lives in focused modules:
6
- * - dispatch.ts tool contract, managed role policy, internal steps
7
- * - thread-lifecycle.tsstable generations, controls, final integration/delivery
8
- * - tools.ts subagent_control / subagent_stop
9
- * - announcements.ts session-start recovery, notices, and widget install
10
- * - widget.ts active-only TUI run status
11
- * - runtime.ts shared per-session state
6
+ * - delegation/ role discovery, routing prompts, and tool contract
7
+ * - configuration/persisted settings, model routes, and setup UI
8
+ * - execution/ process queue, RPC transport/control, and model handoff
9
+ * - lifecycle/ durable threads, restoration, controls, and delivery
10
+ * - isolation/ Git worktrees, recovery, and temporary-state hygiene
11
+ * - presentation/ announcements, formatting, monitor, status, and widget
12
12
  *
13
13
  * Also registers the `/subagents-setup` command and a `before_agent_start` hook
14
14
  * that injects a delegation directive into the parent system prompt so the main
@@ -20,19 +20,19 @@
20
20
 
21
21
  import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
22
22
  import { Text } from "@earendil-works/pi-tui";
23
- import { discoverAgents } from "./agents.ts";
24
- import { registerAnnouncements } from "./announcements.ts";
25
- import { getConfigPath, loadConfig } from "./config.ts";
26
- import { registerSubagentTool } from "./dispatch.ts";
27
- import { matchRunIds } from "./format.ts";
28
- import { buildDelegationDirective } from "./prompt.ts";
29
- import { createRuntime } from "./runtime.ts";
30
- import { runSetup } from "./setup.ts";
31
- import { currentSubagentDepth } from "./spawn.ts";
32
- import { clearActiveRunsStatus } from "./status.ts";
33
- import { bootstrapDurableState } from "./thread-lifecycle.ts";
34
- import { registerLookupTools } from "./tools.ts";
35
- import { clearActiveRunsWidget } from "./widget.ts";
23
+ import { getConfigPath, loadConfig } from "./src/configuration/config.ts";
24
+ import { runSetup } from "./src/configuration/setup.ts";
25
+ import { discoverAgents } from "./src/delegation/agents.ts";
26
+ import { registerSubagentTool } from "./src/delegation/dispatch.ts";
27
+ import { buildDelegationDirective } from "./src/delegation/prompt.ts";
28
+ import { currentSubagentDepth } from "./src/execution/spawn.ts";
29
+ import { createRuntime } from "./src/lifecycle/runtime.ts";
30
+ import { bootstrapDurableState } from "./src/lifecycle/thread-restore.ts";
31
+ import { registerLookupTools } from "./src/lifecycle/tools.ts";
32
+ import { registerAnnouncements } from "./src/presentation/announcements.ts";
33
+ import { matchRunIds } from "./src/presentation/format.ts";
34
+ import { clearActiveRunsStatus } from "./src/presentation/status.ts";
35
+ import { clearActiveRunsWidget } from "./src/presentation/widget.ts";
36
36
 
37
37
  export { matchRunIds };
38
38
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ferris1225/pi-subagents",
3
- "version": "4.3.4",
4
- "description": "A managed sub-agent team for pi: scout, artisan, steward, and sentinel roles, durable threads, model fallback, and Git worktree isolation.",
3
+ "version": "4.3.6",
4
+ "description": "A managed sub-agent team for pi: scout, artisan, and steward roles, durable threads, model fallback, and Git worktree isolation.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -25,6 +25,7 @@
25
25
  "dead-code"
26
26
  ],
27
27
  "files": [
28
+ "index.ts",
28
29
  "src",
29
30
  "agents",
30
31
  "README.md",
@@ -33,7 +34,7 @@
33
34
  ],
34
35
  "pi": {
35
36
  "extensions": [
36
- "./src/index.ts"
37
+ "./index.ts"
37
38
  ]
38
39
  },
39
40
  "scripts": {
@@ -13,11 +13,14 @@ import { dirname, join } from "node:path";
13
13
  import { getAgentDir, withFileMutationQueue } from "@earendil-works/pi-coding-agent";
14
14
 
15
15
  /** Full catalog of agents shipped with the package (selectable in /subagents-setup). */
16
- export const BUILTIN_AGENT_NAMES = ["scout", "artisan", "steward", "sentinel"] as const;
16
+ export const BUILTIN_AGENT_NAMES = ["scout", "artisan", "steward"] as const;
17
17
 
18
- // Historical catalog for configs written before the marker existed. Keep this
19
- // frozen so future built-ins are still recognized as new.
20
- const PRE_SENTINEL_AGENT_NAMES = ["scout", "artisan", "steward"] as const;
18
+ // Historical catalog for configs written before built-in adoption tracking.
19
+ // Keep this frozen so future built-ins are still recognized as new.
20
+ const INITIAL_AGENT_NAMES = ["scout", "artisan", "steward"] as const;
21
+
22
+ /** Names that used to be built in and must not survive as custom roles. */
23
+ const RETIRED_AGENT_NAMES = new Set(["sentinel"]);
21
24
 
22
25
  /** Agents enabled out of the box on a fresh install. */
23
26
  export const DEFAULT_ENABLED_AGENTS: readonly string[] = [...BUILTIN_AGENT_NAMES];
@@ -38,8 +41,6 @@ export function roleThinkingLevel(agentName: string): ThinkingLevel {
38
41
  return "low";
39
42
  case "artisan":
40
43
  return "high";
41
- case "sentinel":
42
- return "max";
43
44
  case "steward":
44
45
  return "medium";
45
46
  default:
@@ -47,14 +48,6 @@ export function roleThinkingLevel(agentName: string): ThinkingLevel {
47
48
  }
48
49
  }
49
50
 
50
- /** Sentinel shares artisan's configured model unless it has its own override. */
51
- export function configuredModelForAgent(
52
- agentModels: Readonly<Record<string, string>>,
53
- agentName: string,
54
- ): string | undefined {
55
- return agentModels[agentName] ?? (agentName === "sentinel" ? agentModels.artisan : undefined);
56
- }
57
-
58
51
  /** Short responsibility line shown next to each built-in in setup lists. */
59
52
  export interface AgentProfile {
60
53
  /** A few words for picker rows. */
@@ -76,10 +69,6 @@ export const AGENT_PROFILES: Record<(typeof BUILTIN_AGENT_NAMES)[number], AgentP
76
69
  summary: "pre-commit finish",
77
70
  remark: "Cleans a completed broad or multi-writer diff and synchronizes cross-cutting docs/comments without changing behavior.",
78
71
  },
79
- sentinel: {
80
- summary: "adversarial review",
81
- remark: "Challenges every cleaned pre-commit diff with standalone evidence gates plus available matching skills; follows artisan's model with max thinking by default.",
82
- },
83
72
  };
84
73
 
85
74
  export function agentProfile(name: string): AgentProfile | undefined {
@@ -143,8 +132,7 @@ export const DEFAULT_CONFIG: SubagentsConfig = {
143
132
 
144
133
  export const FIRST_RUN_SETUP_HINT =
145
134
  "Run /subagents-setup to choose enabled roles, models, and thinking levels. " +
146
- "Sentinel follows artisan's model by default and requests max thinking; scout and steward can use efficient models. " +
147
- "Scout maps code or researches external sources, artisan owns the primary change, steward cleans broad final diffs, and sentinel reviews after cleanup before every commit.";
135
+ "Scout maps code or researches external sources, artisan owns the primary change, and steward cleans broad final diffs.";
148
136
 
149
137
  export function getConfigPath(agentDir: string = getAgentDir()): string {
150
138
  return join(agentDir, CONFIG_FILE_NAME);
@@ -181,15 +169,21 @@ export function normalizeConfig(raw: unknown): SubagentsConfig {
181
169
 
182
170
  if (Array.isArray(raw.enabledAgents)) {
183
171
  const names = raw.enabledAgents.filter(
184
- (name): name is string => typeof name === "string" && name.trim().length > 0,
172
+ (name): name is string =>
173
+ typeof name === "string" &&
174
+ name.trim().length > 0 &&
175
+ !RETIRED_AGENT_NAMES.has(name.trim()),
185
176
  );
186
177
  // An explicitly empty array is honored; duplicates collapse.
187
178
  config.enabledAgents = [...new Set(names.map((name) => name.trim()))];
188
179
  }
189
180
 
190
- const rawKnownAgents = Array.isArray(raw.knownAgents) ? raw.knownAgents : PRE_SENTINEL_AGENT_NAMES;
181
+ const rawKnownAgents = Array.isArray(raw.knownAgents) ? raw.knownAgents : INITIAL_AGENT_NAMES;
191
182
  config.knownAgents = [...new Set(rawKnownAgents.filter(
192
- (name): name is string => typeof name === "string" && name.trim().length > 0,
183
+ (name): name is string =>
184
+ typeof name === "string" &&
185
+ name.trim().length > 0 &&
186
+ !RETIRED_AGENT_NAMES.has(name.trim()),
193
187
  ).map((name) => name.trim()))];
194
188
  for (const name of config.enabledAgents) {
195
189
  if (!config.knownAgents.includes(name)) config.knownAgents.push(name);
@@ -198,7 +192,7 @@ export function normalizeConfig(raw: unknown): SubagentsConfig {
198
192
  if (isRecord(raw.agentModels)) {
199
193
  for (const [rawKey, value] of Object.entries(raw.agentModels)) {
200
194
  const key = rawKey.trim();
201
- if (key !== "" && isModelReference(value)) {
195
+ if (key !== "" && !RETIRED_AGENT_NAMES.has(key) && isModelReference(value)) {
202
196
  config.agentModels[key] = value.trim();
203
197
  }
204
198
  }
@@ -209,6 +203,7 @@ export function normalizeConfig(raw: unknown): SubagentsConfig {
209
203
  const key = rawKey.trim();
210
204
  if (
211
205
  key !== "" &&
206
+ !RETIRED_AGENT_NAMES.has(key) &&
212
207
  typeof value === "string" &&
213
208
  (THINKING_LEVEL_VALUES as readonly string[]).includes(value)
214
209
  ) {