@gotgenes/pi-subagents 5.1.0 → 5.2.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.
@@ -1,32 +1,24 @@
1
1
  # Architecture
2
2
 
3
- This document describes the planned decomposition of the pi-subagents fork
4
- into a focused, composable core with a stable API boundary that other
5
- extensions can build on.
3
+ This document describes the planned decomposition of the pi-subagents fork into a focused, composable core with a stable API boundary that other extensions can build on.
6
4
 
7
5
  ## Design principles
8
6
 
9
- 1. **Narrow core** — the extension owns agent spawning, execution, and result
10
- retrieval. Everything else is a consumer.
11
- 2. **Composable by default** — other extensions can spawn agents, observe
12
- their lifecycle, and display their state without importing this package
13
- directly.
14
- 3. **Typed API boundary** this package exports a `SubagentsAPI` interface
15
- and `Symbol.for()` accessors (`publishSubagentsAPI` /
16
- `getSubagentsAPI`). Consumers declare this package as an optional peer
17
- dependency and use dynamic import for compile-time types. The runtime
18
- bridge is `Symbol.for()` on `globalThis` — no separate API package.
7
+ 1. **Narrow core** — the extension owns agent spawning, execution, and result retrieval.
8
+ Everything else is a consumer.
9
+ 2. **Composable by default** — other extensions can spawn agents, observe their lifecycle, and display their state without importing this package directly.
10
+ 3. **Typed API boundary** this package exports a `SubagentsAPI` interface and `Symbol.for()` accessors (`publishSubagentsAPI` / `getSubagentsAPI`).
11
+ Consumers declare this package as an optional peer dependency and use dynamic import for compile-time types.
12
+ The runtime bridge is `Symbol.for()` on `globalThis` no separate API package.
19
13
  4. **No scheduling** — in-process scheduling is removed from the core.
20
- Scheduling is a separate concern that any extension can implement by
21
- calling `spawn()` on the published API.
22
- 5. **UI extraction is deferred** the widget, conversation viewer, and
23
- `/agents` command menu stay in the core for now. They are the first
24
- candidate for extraction once the API boundary is proven stable.
14
+ Scheduling is a separate concern that any extension can implement by calling `spawn()` on the published API.
15
+ 5. **UI extraction is deferred** — the widget, conversation viewer, and `/agents` command menu stay in the core for now.
16
+ They are the first candidate for extraction once the API boundary is proven stable.
25
17
 
26
18
  ## Current state
27
19
 
28
- The extension is a 6,300 LOC monolith organized into well-factored internal
29
- modules but with no public API contract. The subsystems are:
20
+ The extension is a 6,300 LOC monolith organized into well-factored internal modules but with no public API contract.
21
+ The subsystems are:
30
22
 
31
23
  ```text
32
24
  index.ts (1,894 LOC) — entry point, tool registration, event wiring
@@ -57,18 +49,11 @@ ui/conversation-viewer.ts — scrollable session overlay
57
49
 
58
50
  ### Coupling today
59
51
 
60
- The widget reads agent state by holding a direct reference to
61
- `AgentManager` and polling a shared mutable `Map<string, AgentActivity>`
62
- every 80 ms. The conversation viewer subscribes directly to `AgentSession`
63
- objects.
52
+ The widget reads agent state by holding a direct reference to `AgentManager` and polling a shared mutable `Map<string, AgentActivity>` every 80 ms. The conversation viewer subscribes directly to `AgentSession` objects.
64
53
 
65
- Cross-extension consumers use an ad-hoc RPC layer over `pi.events`
66
- (`subagents:rpc:spawn`, `subagents:rpc:stop`, `subagents:rpc:ping`) with
67
- per-request reply channels and untyped envelopes.
54
+ Cross-extension consumers use an ad-hoc RPC layer over `pi.events` (`subagents:rpc:spawn`, `subagents:rpc:stop`, `subagents:rpc:ping`) with per-request reply channels and untyped envelopes.
68
55
 
69
- There is also a `Symbol.for("pi-subagents:manager")` export on
70
- `globalThis` that exposes `{ waitForAll, hasRunning, spawn, getRecord }`,
71
- but it is undocumented and untyped.
56
+ There is also a `Symbol.for("pi-subagents:manager")` export on `globalThis` that exposes `{ waitForAll, hasRunning, spawn, getRecord }`, but it is undocumented and untyped.
72
57
 
73
58
  ## Target state
74
59
 
@@ -111,31 +96,26 @@ but it is undocumented and untyped.
111
96
 
112
97
  - The three tools: `Agent`, `get_subagent_result`, `steer_subagent`.
113
98
  - `AgentManager` — spawn, queue, abort, resume, concurrency control.
114
- - `agent-runner` — session creation, turn loop, tool filtering, extension
115
- binding (Patches 2 and 3).
99
+ - `agent-runner` — session creation, turn loop, tool filtering, extension binding (Patches 2 and 3).
116
100
  - Agent type registry — default agents, custom `.md` file loading.
117
101
  - Prompt assembly, context extraction, memory, skills, environment.
118
102
  - Worktree isolation.
119
103
  - Token usage tracking.
120
104
  - Settings persistence.
121
- - Internal UI (widget, conversation viewer, `/agents` menu) — these stay
122
- until the API boundary is proven, then move to a separate extension.
105
+ - Internal UI (widget, conversation viewer, `/agents` menu) — these stay until the API boundary is proven, then move to a separate extension.
123
106
 
124
107
  ### What the core drops
125
108
 
126
- - **Scheduling** (`schedule.ts`, `schedule-store.ts`,
127
- `ui/schedule-menu.ts`) 612 LOC removed. The `schedule` parameter is
128
- removed from the `Agent` tool schema. Any extension that wants scheduling
129
- can implement it by calling `getSubagentsAPI()?.spawn(...)` on a timer.
130
- - **Ad-hoc RPC** (`cross-extension-rpc.ts`) replaced by the typed
131
- `SubagentsAPI` published via `Symbol.for()`. The untyped event-bus RPC
132
- channels are removed.
133
- - **Group join** (`group-join.ts`) — 141 LOC removed. The grouped
134
- notification batching adds complexity for a marginal UX improvement.
109
+ - **Scheduling** (`schedule.ts`, `schedule-store.ts`, `ui/schedule-menu.ts`) — 612 LOC removed.
110
+ The `schedule` parameter is removed from the `Agent` tool schema.
111
+ Any extension that wants scheduling can implement it by calling `getSubagentsAPI()?.spawn(...)` on a timer.
112
+ - **Ad-hoc RPC** (`cross-extension-rpc.ts`) — replaced by the typed `SubagentsAPI` published via `Symbol.for()`.
113
+ The untyped event-bus RPC channels are removed.
114
+ - **Group join** (`group-join.ts`) 141 LOC removed.
115
+ The grouped notification batching adds complexity for a marginal UX improvement.
135
116
  Individual completion notifications are sufficient.
136
- - **Output file** (`output-file.ts`) — 96 LOC removed. JSONL transcript
137
- streaming is a consumer concern; a separate extension can subscribe to
138
- lifecycle events and write transcripts.
117
+ - **Output file** (`output-file.ts`) — 96 LOC removed.
118
+ JSONL transcript streaming is a consumer concern; a separate extension can subscribe to lifecycle events and write transcripts.
139
119
 
140
120
  ### Estimated impact
141
121
 
@@ -147,13 +127,11 @@ but it is undocumented and untyped.
147
127
  | Output file | 83 | ~50 |
148
128
  | **Total** | **~916** | **~400** |
149
129
 
150
- After removal and `index.ts` decomposition, the core shrinks from ~6,300
151
- to ~5,400 LOC, and `index.ts` shrinks from ~1,894 to ~1,300 LOC.
130
+ After removal and `index.ts` decomposition, the core shrinks from ~6,300 to ~5,400 LOC, and `index.ts` shrinks from ~1,894 to ~1,300 LOC.
152
131
 
153
132
  ## SubagentsAPI
154
133
 
155
- The `SubagentsAPI` interface, accessor functions, and serializable types
156
- are exported directly from this package (`@earendil-works/pi-subagents`).
134
+ The `SubagentsAPI` interface, accessor functions, and serializable types are exported directly from this package (`@earendil-works/pi-subagents`).
157
135
  No separate API package is needed.
158
136
 
159
137
  Consumers declare this package as an optional peer dependency:
@@ -169,8 +147,7 @@ Consumers declare this package as an optional peer dependency:
169
147
  }
170
148
  ```
171
149
 
172
- At runtime, consumers use dynamic import for type-safe access to the
173
- accessor functions:
150
+ At runtime, consumers use dynamic import for type-safe access to the accessor functions:
174
151
 
175
152
  ```typescript
176
153
  const { getSubagentsAPI } = await import("@earendil-works/pi-subagents");
@@ -180,12 +157,9 @@ if (api) {
180
157
  }
181
158
  ```
182
159
 
183
- Pi's extension loader creates a fresh `jiti` instance per extension with
184
- `moduleCache: false`, so module-scoped singletons don't survive across
185
- extensions. The accessor functions use `Symbol.for()` on `globalThis`,
186
- which is process-global by spec, to bridge this gap. The dynamic import
187
- provides compile-time types; the `Symbol.for()` key is the actual
188
- runtime channel.
160
+ Pi's extension loader creates a fresh `jiti` instance per extension with `moduleCache: false`, so module-scoped singletons don't survive across extensions.
161
+ The accessor functions use `Symbol.for()` on `globalThis`, which is process-global by spec, to bridge this gap.
162
+ The dynamic import provides compile-time types; the `Symbol.for()` key is the actual runtime channel.
189
163
 
190
164
  ### Interface
191
165
 
@@ -245,9 +219,7 @@ export function getSubagentsAPI(): SubagentsAPI | undefined {
245
219
  }
246
220
  ```
247
221
 
248
- If Pi gains a native service registry ([earendil-works/pi#4207]), these
249
- accessors can be updated to delegate to `pi.registerService()` /
250
- `pi.getService()` internally while keeping the same consumer API.
222
+ If Pi gains a native service registry ([earendil-works/pi#4207]), these accessors can be updated to delegate to `pi.registerService()` / `pi.getService()` internally while keeping the same consumer API.
251
223
 
252
224
  ### Lifecycle events
253
225
 
@@ -259,8 +231,8 @@ The core emits events on `pi.events` that any extension can observe:
259
231
  | `subagents:completed` | `{ id, type, status, result?, error? }` | Agent finishes |
260
232
  | `subagents:activity` | `{ id, toolName?, textDelta?, turnCount? }` | Streaming progress |
261
233
 
262
- These replace the ad-hoc RPC channels. They are fire-and-forget broadcast
263
- events — no request IDs, no reply channels.
234
+ These replace the ad-hoc RPC channels.
235
+ They are fire-and-forget broadcast events — no request IDs, no reply channels.
264
236
 
265
237
  ### Consumer example: scheduling extension
266
238
 
@@ -327,60 +299,137 @@ src/
327
299
  └── (existing modules unchanged)
328
300
  ```
329
301
 
330
- Each extracted module receives narrow constructor-injected dependencies
331
- rather than closing over module-level state.
302
+ Each extracted module receives narrow constructor-injected dependencies rather than closing over module-level state.
332
303
 
333
304
  ## Phase plan
334
305
 
335
306
  ### Phase 1: Export `SubagentsAPI` from this package
336
307
 
337
- Add the `SubagentsAPI` interface, serializable types, and `Symbol.for()`
338
- accessor functions as public exports of this package. No behavioral
339
- changes to the core yet.
308
+ Add the `SubagentsAPI` interface, serializable types, and `Symbol.for()` accessor functions as public exports of this package.
309
+ No behavioral changes to the core yet.
340
310
 
341
311
  ### Phase 2: Remove scheduling ✓ (done — issue #52)
342
312
 
343
- Deleted `schedule.ts`, `schedule-store.ts`, `ui/schedule-menu.ts`. Removed
344
- the `schedule` parameter from the `Agent` tool schema. Removed scheduler
345
- setup and lifecycle hooks from `index.ts`.
313
+ Deleted `schedule.ts`, `schedule-store.ts`, `ui/schedule-menu.ts`.
314
+ Removed the `schedule` parameter from the `Agent` tool schema.
315
+ Removed scheduler setup and lifecycle hooks from `index.ts`.
346
316
 
347
317
  ### Phase 3: Remove group-join, output-file, ad-hoc RPC
348
318
 
349
319
  Delete `group-join.ts`, `output-file.ts`, `cross-extension-rpc.ts`.
350
- Simplify `index.ts` to use direct individual notifications. Emit
351
- lifecycle events on `pi.events` for external consumers.
320
+ Simplify `index.ts` to use direct individual notifications.
321
+ Emit lifecycle events on `pi.events` for external consumers.
352
322
 
353
323
  ### Phase 4: Implement and publish `SubagentsAPI`
354
324
 
355
- Wire `api-adapter.ts` to wrap `AgentManager` and call
356
- `publishSubagentsAPI()` at extension init. Resolve model strings inside
357
- the adapter (fixing upstream [tintinweb/pi-subagents#60]).
325
+ Wire `api-adapter.ts` to wrap `AgentManager` and call `publishSubagentsAPI()` at extension init.
326
+ Resolve model strings inside the adapter (fixing upstream [tintinweb/pi-subagents#60]).
358
327
 
359
- ### Phase 5: Decompose `index.ts`
328
+ ### Phase 5: Decompose `index.ts` ✓ (done — issue #54)
360
329
 
361
- Extract tools, notifications, activity tracking, and the `/agents` command
362
- into separate modules per the decomposition above.
330
+ Extracted tools, notifications, activity tracking, and the `/agents` command into separate modules.
331
+ `src/index.ts` shrank from ~1,619 lines to ~265 lines.
363
332
 
364
333
  ### Phase 6 (future): Extract UI to `@earendil-works/pi-subagents-ui`
365
334
 
366
- Move `ui/agent-widget.ts`, `ui/conversation-viewer.ts`, the `/agents`
367
- command, notifications, and activity tracking to a separate extension that
368
- consumes `SubagentsAPI` + lifecycle events. This phase is deferred until
369
- the API boundary is proven stable in production.
335
+ Move `ui/agent-widget.ts`, `ui/conversation-viewer.ts`, the `/agents` command, notifications, and activity tracking to a separate extension that consumes `SubagentsAPI` + lifecycle events.
336
+ This phase is deferred until the API boundary is proven stable in production.
337
+
338
+ ## Structural refactoring roadmap (post-#54)
339
+
340
+ The Issue #54 decomposition created focused modules but left several structural cleanup opportunities on the table.
341
+ The following issues track the work needed to bring `pi-subagents` to the same level of testability and composability as `pi-permission-system`.
342
+
343
+ ### Phase 1: Foundation
344
+
345
+ These three issues are independent of each other and can land in any order.
346
+ Together they eliminate module-scope mutable state and create a testable functional core.
347
+
348
+ 1. **gotgenes/pi-packages#69** — Create `SubagentRuntime`
349
+ - Move `defaultMaxTurns`, `graceTurns`, `agentActivity`, `currentCtx`, and widget references out of closure/module scope into a single factory-constructed object.
350
+ - This unblocks handler extraction (Issue #70) by giving handlers a concrete deps bag instead of closure variables.
351
+
352
+ 2. **gotgenes/pi-packages#71** — Extract pure agent-session assembler from `agent-runner.ts`
353
+ - Split `runAgent()` into a pure configuration assembler (~200 lines) and an IO shell (~200 lines).
354
+ - The assembler becomes independently testable without mocking the Pi SDK.
355
+
356
+ 3. **gotgenes/pi-packages#76** — Inject `cwd` into `AgentManager`
357
+ - Replace the `process.cwd()` call in `dispose()` with a constructor parameter.
358
+ - A small, mechanical prerequisite for Issue #72.
359
+
360
+ ### Phase 2: Core decomposition
361
+
362
+ These build on Phase 1 and should land after it.
363
+
364
+ 4. **gotgenes/pi-packages#72** — Dependency-inject `AgentManager`'s collaborators
365
+ - Introduce `AgentRunner` and `WorktreeManager` interfaces and inject them into `AgentManager`.
366
+ - Removes direct imports of `agent-runner.ts` and `worktree.ts` from `agent-manager.ts`.
367
+
368
+ 5. **gotgenes/pi-packages#70** — Extract event handlers into `src/handlers/`
369
+ - Move the four inline lambdas (`session_start`, `session_before_switch`, `session_shutdown`, `tool_execution_start`) into named handler modules.
370
+ - Requires Issue #69 because handlers need the `SubagentRuntime` as their deps bag.
371
+ - Target: `src/index.ts` ≤150 lines.
372
+
373
+ ### Phase 3: Interface polish
374
+
375
+ Small cleanups that are safest after the structural changes settle.
376
+
377
+ 6. **gotgenes/pi-packages#66** — Replace `as any` casts with proper SDK types
378
+ - Type-only change in the tool/menu factory dep interfaces.
379
+ - Best done after Issues #69 and #70 when the interfaces are stable.
380
+
381
+ 7. **gotgenes/pi-packages#77** — Add `projectAgentsDir` to `AgentMenuDeps`
382
+ - Remove the inline `process.cwd()` lambda from the menu handler.
383
+
384
+ ### Phase 4: Features and cross-cutting concerns
385
+
386
+ 8. **gotgenes/pi-packages#61** — Port transcript logging to Pi's official JSONL session format
387
+ - Feature work that should happen after structural refactoring is complete so the output-file subsystem has a stable home.
388
+
389
+ 9. **gotgenes/pi-packages#22** — Parent-session resolution for `nicobailon/pi-subagents` children
390
+ - Cross-extension issue that spans `pi-permission-system` and `pi-subagents`.
391
+ - Requires coordination on env-var conventions.
392
+ - Not blocked by the structural refactor but logically separate from it.
393
+
394
+ ### Dependency graph
395
+
396
+ ```text
397
+ #69 (SubagentRuntime) ──┬──► #70 (handler extraction)
398
+
399
+ └──► #72 (AgentManager DI) ──(optional)──► #70
400
+
401
+ #71 (pure assembler) ─────(independent)────► (can land any time)
402
+
403
+ #76 (cwd injection) ──────► #72
404
+
405
+ #66 (type casts) ◄────────(after structural changes settle)
406
+ #77 (projectAgentsDir) ◄──(after #66 or parallel)
407
+
408
+ #61 (transcript format) ◄─(after structural refactor)
409
+ #22 (parent session) ◄────(cross-extension, independent)
410
+ ```
411
+
412
+ ### Recommended order
413
+
414
+ The recommended sequence is:
415
+
416
+ ```text
417
+ #69 → #71 → #76 → #72 → #70 → #66 → #77 → #61
418
+ ```
419
+
420
+ Issue #22 is a parallel cross-extension track and does not gate the structural work.
370
421
 
371
422
  ## Relationship with upstream
372
423
 
373
- This fork ([earendil-works/pi-subagents]) is now a **hard fork** of
374
- [tintinweb/pi-subagents]. The decomposition diverges materially from
375
- upstream's direction.
424
+ This fork ([earendil-works/pi-subagents]) is now a hard fork of [tintinweb/pi-subagents].
425
+ The decomposition diverges materially from upstream's direction.
376
426
 
377
- The three upstream PRs (#71, #72, #73) remain open. If they land, upstream
378
- gains the peer-dep fix and the two RepOne patches. This fork continues
379
- independently regardless.
427
+ The three upstream PRs (#71, #72, #73) remain open.
428
+ If they land, upstream gains the peer-dep fix and the two RepOne patches.
429
+ This fork continues independently regardless.
380
430
 
381
- Upstream fixes and ideas are cherry-picked when they align with this
382
- fork's scope. The upstream test suite is run periodically as a regression
383
- canary for the agent-runner core.
431
+ Upstream fixes and ideas are cherry-picked when they align with this fork's scope.
432
+ The upstream test suite is run periodically as a regression canary for the agent-runner core.
384
433
 
385
434
  [earendil-works/pi#4207]: https://github.com/earendil-works/pi/issues/4207
386
435
  [earendil-works/pi-subagents]: https://github.com/earendil-works/pi-subagents
@@ -28,7 +28,8 @@ This ADR records why Patch 1 was deferred and the strategy for upstream PRs back
28
28
 
29
29
  ### Patch 1 is deferred
30
30
 
31
- The original Spike 2 finding was that the parent's `additionalExtensionPaths` does not propagate to the child's `DefaultResourceLoader`. The fix was sketched as "plumb parent's `additionalExtensionPaths` (and siblings) into the child."
31
+ The original Spike 2 finding was that the parent's `additionalExtensionPaths` does not propagate to the child's `DefaultResourceLoader`.
32
+ The fix was sketched as "plumb parent's `additionalExtensionPaths` (and siblings) into the child."
32
33
 
33
34
  During planning for this fork, two implementation constraints surfaced:
34
35
 
@@ -40,9 +41,12 @@ A working patch would have to either:
40
41
  - Accept new fields in `RunOptions` so callers supply the paths explicitly, **or**
41
42
  - Reach into `process.argv` to re-resolve `-e`/`--extensions` flags from the child's perspective.
42
43
 
43
- Neither matches the production need. For RepOne (and any consumer that installs extensions via `pi install`), extensions are settings-discoverable: children inherit them independently of the parent's `DefaultResourceLoader` configuration. The `pi -e <path>` ephemeral-extension case is the only beneficiary of Patch 1, and it does not appear in our workflow.
44
+ Neither matches the production need.
45
+ For RepOne (and any consumer that installs extensions via `pi install`), extensions are settings-discoverable: children inherit them independently of the parent's `DefaultResourceLoader` configuration.
46
+ The `pi -e <path>` ephemeral-extension case is the only beneficiary of Patch 1, and it does not appear in our workflow.
44
47
 
45
- We therefore defer Patch 1 rather than carry a speculative patch in the fork's diff against upstream. A follow-up issue on the RepOne board (linked from #443) captures the criterion for revisiting: **a workflow that needs `pi -e <path>` ephemeral extensions to reach children**.
48
+ We therefore defer Patch 1 rather than carry a speculative patch in the fork's diff against upstream.
49
+ A follow-up issue on the RepOne board (linked from #443) captures the criterion for revisiting: **a workflow that needs `pi -e <path>` ephemeral extensions to reach children**.
46
50
 
47
51
  ### Upstream PRs are open
48
52
 
@@ -65,10 +69,12 @@ However, the fork now diverges intentionally beyond those patches — see [`docs
65
69
 
66
70
  ### Negative
67
71
 
68
- - The `pi -e <path>` ephemeral-extension case in subagents will not work until Patch 1 lands. We accept this because no consumer in scope uses that pattern.
72
+ - The `pi -e <path>` ephemeral-extension case in subagents will not work until Patch 1 lands.
73
+ We accept this because no consumer in scope uses that pattern.
69
74
 
70
75
  ### Operational
71
76
 
72
- - Upstream PRs are open and linked above. If merged, upstream gains the three patches, but the fork continues independently with broader architectural changes per [`docs/architecture/architecture.md`](../architecture/architecture.md).
77
+ - Upstream PRs are open and linked above.
78
+ If merged, upstream gains the three patches, but the fork continues independently with broader architectural changes per [`docs/architecture/architecture.md`](../architecture/architecture.md).
73
79
  - The architecture document governs the fork's direction going forward; this ADR's original "thin-patch" framing no longer describes the fork's trajectory.
74
80
  - When Patch 1 is eventually added, it should be a separate ADR in `docs/decisions/` with its own follow-up.
@@ -33,7 +33,8 @@ This issue implements that boundary, following the naming and structural convent
33
33
 
34
34
  ### Prerequisite issues
35
35
 
36
- - #49 (remove group-join and RPC) — **closed/merged**. The untyped RPC channels are already gone.
36
+ - #49 (remove group-join and RPC) — **closed/merged**.
37
+ The untyped RPC channels are already gone.
37
38
  - #52 (remove scheduled subagents) — **closed/merged**.
38
39
  - #51 (update ADR for hard fork) — **closed/merged**.
39
40
 
@@ -128,23 +128,40 @@ Since this is a removal (not a feature), the order is deletion-first with valida
128
128
  Commit: `feat!: remove group-join and cross-extension-rpc source`
129
129
 
130
130
  2. **Remove RPC wiring from `index.ts`.**
131
- Remove `registerRpcHandlers` import and call. Remove `currentCtx` state and RPC-related `session_start`/`session_shutdown` logic (keep `manager.clearCompleted()` call). Remove `unsubPing/Spawn/Stop` teardown. Remove `subagents:ready` emit.
131
+ Remove `registerRpcHandlers` import and call.
132
+ Remove `currentCtx` state and RPC-related `session_start`/`session_shutdown` logic (keep `manager.clearCompleted()` call).
133
+ Remove `unsubPing/Spawn/Stop` teardown.
134
+ Remove `subagents:ready` emit.
132
135
  Commit: `feat!: remove RPC wiring from index.ts`
133
136
 
134
137
  3. **Remove group-join wiring from `index.ts`.**
135
- Remove `GroupJoinManager` import and instantiation (including the grouped-delivery callback). Remove batch tracking (`currentBatchAgents`, `batchFinalizeTimer`, `batchCounter`, `finalizeBatch`). Remove `defaultJoinMode` state, `getDefaultJoinMode`, `setDefaultJoinMode`. Remove join-mode resolution in background spawn path. Remove "Join mode" settings menu entry. Remove `defaultJoinMode` from `snapshotSettings()`. Simplify the `onComplete` callback: remove `currentBatchAgents` check and `groupJoin.onAgentComplete()` routing — always call `sendIndividualNudge(record)`. Remove `setDefaultJoinMode` from `applyAndEmitLoaded` appliers.
138
+ Remove `GroupJoinManager` import and instantiation (including the grouped-delivery callback).
139
+ Remove batch tracking (`currentBatchAgents`, `batchFinalizeTimer`, `batchCounter`, `finalizeBatch`).
140
+ Remove `defaultJoinMode` state, `getDefaultJoinMode`, `setDefaultJoinMode`.
141
+ Remove join-mode resolution in background spawn path.
142
+ Remove "Join mode" settings menu entry.
143
+ Remove `defaultJoinMode` from `snapshotSettings()`.
144
+ Simplify the `onComplete` callback: remove `currentBatchAgents` check and `groupJoin.onAgentComplete()` routing — always call `sendIndividualNudge(record)`.
145
+ Remove `setDefaultJoinMode` from `applyAndEmitLoaded` appliers.
136
146
  Commit: `feat!: remove group-join wiring from index.ts`
137
147
 
138
148
  4. **Clean up types, settings, and invocation-config.**
139
- Remove `JoinMode` type from `types.ts`. Remove `groupId` and `joinMode` from `AgentRecord`. Remove `others` from `NotificationDetails`. Remove `defaultJoinMode` from `SubagentsSettings` and `SettingsAppliers` in `settings.ts`. Remove `VALID_JOIN_MODES` and sanitize/apply clauses. Remove `resolveJoinMode` and `JoinMode` import from `invocation-config.ts`.
149
+ Remove `JoinMode` type from `types.ts`.
150
+ Remove `groupId` and `joinMode` from `AgentRecord`.
151
+ Remove `others` from `NotificationDetails`.
152
+ Remove `defaultJoinMode` from `SubagentsSettings` and `SettingsAppliers` in `settings.ts`.
153
+ Remove `VALID_JOIN_MODES` and sanitize/apply clauses.
154
+ Remove `resolveJoinMode` and `JoinMode` import from `invocation-config.ts`.
140
155
  Commit: `feat!: remove join-mode types and settings`
141
156
 
142
157
  5. **Verify all tests pass and fix straggling references.**
143
- Run `pnpm vitest run` and `pnpm run check`. Fix any test fixtures or assertions that reference removed fields (`joinMode`, `groupId`, `defaultJoinMode`, `resolveJoinMode`).
158
+ Run `pnpm vitest run` and `pnpm run check`.
159
+ Fix any test fixtures or assertions that reference removed fields (`joinMode`, `groupId`, `defaultJoinMode`, `resolveJoinMode`).
144
160
  Commit (if fixes needed): `test: remove references to deleted subsystems from test fixtures`
145
161
 
146
162
  6. **Update documentation.**
147
- Update `README.md`: remove "Cross-extension RPC" section, join-mode documentation, `subagents:ready` event row. Update settings persistence paragraph.
163
+ Update `README.md`: remove "Cross-extension RPC" section, join-mode documentation, `subagents:ready` event row.
164
+ Update settings persistence paragraph.
148
165
  Update `.pi/skills/package-pi-subagents/SKILL.md`: remove `cross-extension-rpc.ts` and `group-join.ts` from architecture diagram and module tables, update `index.ts` description.
149
166
  Commit: `docs: remove group-join and RPC from README and AGENTS`
150
167
 
@@ -43,7 +43,8 @@ The update touches three areas of the ADR:
43
43
 
44
44
  1. **Frontmatter + Status section** — change `status: accepted` to `status: superseded` in frontmatter, and update the Status section body to read "Superseded" with a pointer to `docs/architecture/architecture.md`.
45
45
  2. **"Upstream PRs are open" subsection** — keep the PR list and factual statements intact; revise the final sentence ("Once these land upstream, the fork's divergence reduces to package naming and tooling.") to note that the fork now diverges intentionally beyond those patches, per the architecture document.
46
- 3. **Consequences → Operational** — add a sentence noting that the fork diverges intentionally beyond patches, and that the architecture document governs the fork's direction going forward. Keep the existing bullet about upstream PRs.
46
+ 3. **Consequences → Operational** — add a sentence noting that the fork diverges intentionally beyond patches, and that the architecture document governs the fork's direction going forward.
47
+ Keep the existing bullet about upstream PRs.
47
48
 
48
49
  No structural changes (new sections, removed sections, reordered content).
49
50
 
@@ -83,7 +83,8 @@ The `bypassQueue` option on `SpawnOptions` stays — its JSDoc comment mentionin
83
83
 
84
84
  1. No new unit tests are needed — this is pure deletion.
85
85
  2. All three scheduling test files (`schedule.test.ts`, `schedule-store.test.ts`, `schedule-e2e.test.ts`) become entirely redundant and are deleted.
86
- 3. Existing tests for `agent-manager`, `agent-runner`, `settings`, and other modules stay as-is. The `settings.test.ts` file (if it exists) may need minor updates to remove `schedulingEnabled` from fixture data.
86
+ 3. Existing tests for `agent-manager`, `agent-runner`, `settings`, and other modules stay as-is.
87
+ The `settings.test.ts` file (if it exists) may need minor updates to remove `schedulingEnabled` from fixture data.
87
88
 
88
89
  ## TDD Order
89
90
 
@@ -109,7 +110,8 @@ Since this is a removal (not a feature), the order is deletion-first with a sing
109
110
  Commit: `build: remove croner dependency`
110
111
 
111
112
  5. **Verify all tests pass.**
112
- Run `pnpm vitest run` in the package. Fix any test fixtures that reference `schedulingEnabled` or scheduling types.
113
+ Run `pnpm vitest run` in the package.
114
+ Fix any test fixtures that reference `schedulingEnabled` or scheduling types.
113
115
  Commit (if fixes needed): `test: remove scheduling references from test fixtures`
114
116
 
115
117
  6. **Update documentation.**
@@ -7,12 +7,8 @@ issue_title: "feat: structured debug logging for silenced catch blocks"
7
7
 
8
8
  ## Problem Statement
9
9
 
10
- The codebase contains ~20 `catch { /* ignore */ }` blocks spread across
11
- `agent-manager.ts`, `worktree.ts`, `output-file.ts`, `skill-loader.ts`,
12
- `custom-agents.ts`, `memory.ts`, `env.ts`, and `notification.ts`.
13
- Each block correctly suppresses a non-essential side-effect failure, but
14
- in development the silence makes it impossible to diagnose what is actually
15
- failing inside those paths.
10
+ The codebase contains ~20 `catch { /* ignore */ }` blocks spread across `agent-manager.ts`, `worktree.ts`, `output-file.ts`, `skill-loader.ts`, `custom-agents.ts`, `memory.ts`, `env.ts`, and `notification.ts`.
11
+ Each block correctly suppresses a non-essential side-effect failure, but in development the silence makes it impossible to diagnose what is actually failing inside those paths.
16
12
 
17
13
  ## Goals
18
14
 
@@ -34,14 +30,11 @@ failing inside those paths.
34
30
 
35
31
  ## Background
36
32
 
37
- The `AGENTS.md` rule "prefer explicit configuration over hidden behavior" aligns
38
- with gating noise behind an opt-in env var rather than always-on logging.
39
- The code-style skill notes that business logic should remain pure — `debug.ts`
40
- must not import the Pi SDK; it only reads `process.env`.
33
+ The `AGENTS.md` rule "prefer explicit configuration over hidden behavior" aligns with gating noise behind an opt-in env var rather than always-on logging.
34
+ The code-style skill notes that business logic should remain pure `debug.ts` must not import the Pi SDK; it only reads `process.env`.
41
35
 
42
36
  The `DEBUG` constant is evaluated once at module import time (top-level `const`).
43
- That matches how similar opt-in env vars work in Node.js tooling and keeps the
44
- hot path to a single boolean branch.
37
+ That matches how similar opt-in env vars work in Node.js tooling and keeps the hot path to a single boolean branch.
45
38
 
46
39
  ### Catch blocks in scope
47
40
 
@@ -69,8 +62,7 @@ export function debugLog(context: string, err: unknown): void {
69
62
  ```
70
63
 
71
64
  The module-level `DEBUG` constant is the canonical source.
72
- `debugLog` is a pure side-effect function: no return value, no SDK dependency,
73
- no coupling to Pi types.
65
+ `debugLog` is a pure side-effect function: no return value, no SDK dependency, no coupling to Pi types.
74
66
 
75
67
  ### Threading pattern
76
68
 
@@ -86,14 +78,11 @@ to:
86
78
  } catch (err) { debugLog("<context label>", err); }
87
79
  ```
88
80
 
89
- When the block already had a named error (`catch (err)`), only the `debugLog`
90
- call is added; the existing comment, if descriptive, is removed in favour of
91
- the context string.
81
+ When the block already had a named error (`catch (err)`), only the `debugLog` call is added; the existing comment, if descriptive, is removed in favour of the context string.
92
82
 
93
83
  ### Context label convention
94
84
 
95
- Labels read as `"<verb> <noun>"`, lower-case, mirroring the surrounding
96
- function name when unambiguous (e.g., `"removeWorktree"`, `"outputCleanup"`).
85
+ Labels read as `"<verb> <noun>"`, lower-case, mirroring the surrounding function name when unambiguous (e.g., `"removeWorktree"`, `"outputCleanup"`).
97
86
 
98
87
  ## Module-Level Changes
99
88
 
@@ -112,59 +101,41 @@ function name when unambiguous (e.g., `"removeWorktree"`, `"outputCleanup"`).
112
101
 
113
102
  ## Test Impact Analysis
114
103
 
115
- 1. **New tests enabled** — `debug.test.ts` can directly unit-test `debugLog` in
116
- isolation.
104
+ 1. **New tests enabled** — `debug.test.ts` can directly unit-test `debugLog` in isolation.
117
105
  Asserting `console.warn` is/isn't called based on the env var is now trivial.
118
106
  Previously this path was entirely untested.
119
107
 
120
108
  2. **Existing tests become redundant** — None.
121
- The catch blocks were never exercised by existing tests (they were silent
122
- drops), so no existing test coverage needs to be removed.
109
+ The catch blocks were never exercised by existing tests (they were silent drops), so no existing test coverage needs to be removed.
123
110
 
124
111
  3. **Tests that must stay as-is** — All existing tests remain valid.
125
- The threading adds a call inside each catch but does not change control flow,
126
- return values, or thrown exceptions; no test expectations change.
112
+ The threading adds a call inside each catch but does not change control flow, return values, or thrown exceptions; no test expectations change.
127
113
 
128
114
  ## TDD Order
129
115
 
130
- 1. **`src/debug.ts` + `test/debug.test.ts`**
131
- Test surface: `debugLog` calls `console.warn` when `PI_SUBAGENTS_DEBUG=1`;
132
- does nothing when unset or `"0"`.
133
- Use `vi.spyOn(console, 'warn')` + `vi.stubEnv('PI_SUBAGENTS_DEBUG', '1')` +
134
- `vi.resetModules()` + dynamic import to exercise both branches.
116
+ 1. **`src/debug.ts` + `test/debug.test.ts`** Test surface: `debugLog` calls `console.warn` when `PI_SUBAGENTS_DEBUG=1`; does nothing when unset or `"0"`.
117
+ Use `vi.spyOn(console, 'warn')` + `vi.stubEnv('PI_SUBAGENTS_DEBUG', '1')` + `vi.resetModules()` + dynamic import to exercise both branches.
135
118
  Suggested commit: `feat: add debugLog utility gated on PI_SUBAGENTS_DEBUG (#57)`
136
119
 
137
- 2. **Thread into `env.ts`**
138
- Test surface: `env.test.ts` — existing tests still pass (no new assertions
139
- required because the catch paths are tested implicitly by the non-git-dir
140
- test already covering the swallowed failures).
120
+ 2. **Thread into `env.ts`** Test surface: `env.test.ts` — existing tests still pass (no new assertions required because the catch paths are tested implicitly by the non-git-dir test already covering the swallowed failures).
141
121
  Suggested commit: `feat: thread debugLog into env catch blocks (#57)`
142
122
 
143
- 3. **Thread into `skill-loader.ts`**
144
- Test surface: `skill-loader.test.ts` existing tests still pass.
145
- The readdirSync-error catch is exercised when the skill root does not exist
146
- (covered by existing "not found" test paths).
123
+ 3. **Thread into `skill-loader.ts`** Test surface: `skill-loader.test.ts` — existing tests still pass.
124
+ The readdirSync-error catch is exercised when the skill root does not exist (covered by existing "not found" test paths).
147
125
  Suggested commit: `feat: thread debugLog into skill-loader catch block (#57)`
148
126
 
149
- 4. **Thread into `custom-agents.ts` and `memory.ts`**
150
- These two files have structurally identical filesystem-error catch blocks and
151
- can land in a single step.
152
- Test surface: `custom-agents.test.ts`, `memory.test.ts` — existing tests
153
- still pass.
127
+ 4. **Thread into `custom-agents.ts` and `memory.ts`** These two files have structurally identical filesystem-error catch blocks and can land in a single step.
128
+ Test surface: `custom-agents.test.ts`, `memory.test.ts` existing tests still pass.
154
129
  Suggested commit: `feat: thread debugLog into custom-agents and memory catch blocks (#57)`
155
130
 
156
- 5. **Thread into `output-file.ts`**
157
- Test surface: `output-file.test.ts` — existing tests still pass.
131
+ 5. **Thread into `output-file.ts`** Test surface: `output-file.test.ts` — existing tests still pass.
158
132
  Suggested commit: `feat: thread debugLog into output-file catch block (#57)`
159
133
 
160
- 6. **Thread into `worktree.ts`**
161
- Test surface: `worktree.test.ts` — existing tests still pass.
134
+ 6. **Thread into `worktree.ts`** Test surface: `worktree.test.ts` — existing tests still pass.
162
135
  Eight catch sites; name each with its enclosing function for label clarity.
163
136
  Suggested commit: `feat: thread debugLog into worktree catch blocks (#57)`
164
137
 
165
- 7. **Thread into `agent-manager.ts` and `notification.ts`**
166
- Test surface: `agent-manager.test.ts`, `notification.test.ts` — existing
167
- tests still pass.
138
+ 7. **Thread into `agent-manager.ts` and `notification.ts`** Test surface: `agent-manager.test.ts`, `notification.test.ts` — existing tests still pass.
168
139
  Suggested commit: `feat: thread debugLog into agent-manager and notification catch blocks (#57)`
169
140
 
170
141
  ## Risks and Mitigations
@@ -177,8 +148,7 @@ function name when unambiguous (e.g., `"removeWorktree"`, `"outputCleanup"`).
177
148
 
178
149
  ## Open Questions
179
150
 
180
- - Should a future issue expose `debugLog` as part of the public `service.ts` API
181
- so consumer extensions can share the same debug flag?
151
+ - Should a future issue expose `debugLog` as part of the public `service.ts` API so consumer extensions can share the same debug flag?
182
152
  Deferred — out of scope for this change; no consumer currently needs it.
183
153
  - Should `PI_SUBAGENTS_DEBUG` be documented in the package `README.md`?
184
154
  Likely yes, but deferred to a follow-up doc PR.