@gotgenes/pi-subagents 5.0.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.
- package/CHANGELOG.md +41 -0
- package/README.md +176 -133
- package/docs/architecture/architecture.md +141 -92
- package/docs/decisions/0001-deferred-patches.md +11 -5
- package/docs/plans/0048-implement-subagents-api.md +2 -1
- package/docs/plans/0049-remove-group-join-output-file-rpc.md +22 -5
- package/docs/plans/0051-update-adr-0001-hard-fork.md +2 -1
- package/docs/plans/0052-remove-scheduled-subagents.md +4 -2
- package/docs/plans/0057-structured-debug-logging.md +154 -0
- package/docs/plans/0069-create-subagent-runtime.md +345 -0
- package/docs/retro/0049-remove-group-join-output-file-rpc.md +15 -4
- package/docs/retro/0051-update-adr-0001-hard-fork.md +7 -3
- package/docs/retro/0053-extract-model-resolution-from-execute.md +14 -4
- package/docs/retro/0054-decompose-index-into-modules.md +20 -5
- package/docs/retro/0057-structured-debug-logging.md +77 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +13 -5
- package/src/agent-runner.ts +13 -26
- package/src/custom-agents.ts +5 -2
- package/src/debug.ts +14 -0
- package/src/env.ts +5 -3
- package/src/index.ts +37 -28
- package/src/memory.ts +5 -2
- package/src/notification.ts +3 -2
- package/src/output-file.ts +4 -1
- package/src/runtime.ts +62 -0
- package/src/skill-loader.ts +3 -1
- package/src/tools/agent-tool.ts +4 -2
- package/src/ui/agent-menu.ts +16 -13
- package/src/worktree.ts +14 -12
|
@@ -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
|
-
|
|
11
|
-
2. **Composable by default** — other extensions can spawn agents, observe
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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.
|
|
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
|
-
`
|
|
185
|
-
|
|
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.
|
|
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
|
-
|
|
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`.
|
|
344
|
-
the `schedule` parameter from the `Agent` tool schema.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
362
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
|
374
|
-
|
|
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.
|
|
378
|
-
gains the peer-dep fix and the two RepOne patches.
|
|
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
|
-
|
|
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`.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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**.
|
|
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.
|
|
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).
|
|
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`.
|
|
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`.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.**
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 57
|
|
3
|
+
issue_title: "feat: structured debug logging for silenced catch blocks"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Structured debug logging for silenced catch blocks
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
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.
|
|
12
|
+
|
|
13
|
+
## Goals
|
|
14
|
+
|
|
15
|
+
- Add a `debugLog(context, err)` utility in `src/debug.ts` gated on
|
|
16
|
+
`PI_SUBAGENTS_DEBUG=1`.
|
|
17
|
+
- Thread `debugLog` into every silenced `catch` block listed in the issue scope.
|
|
18
|
+
- Leave production behavior **completely unchanged** — no new log output unless
|
|
19
|
+
the env var is explicitly set.
|
|
20
|
+
- Provide a `debug.test.ts` unit test covering the on/off branching.
|
|
21
|
+
|
|
22
|
+
## Non-Goals
|
|
23
|
+
|
|
24
|
+
- `usage.ts` catch blocks — they return `0`/`null` on failure and are already
|
|
25
|
+
recoverable, not truly "silent drops"; they are out of scope.
|
|
26
|
+
- `settings.ts` catch block — it returns `false` on failure, already surfaced
|
|
27
|
+
to the caller; out of scope.
|
|
28
|
+
- Log rotation, structured JSON output, or stderr vs. stdout routing.
|
|
29
|
+
- Wiring debug output to the Pi UI; `console.warn` to stderr is sufficient.
|
|
30
|
+
|
|
31
|
+
## Background
|
|
32
|
+
|
|
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`.
|
|
35
|
+
|
|
36
|
+
The `DEBUG` constant is evaluated once at module import time (top-level `const`).
|
|
37
|
+
That matches how similar opt-in env vars work in Node.js tooling and keeps the hot path to a single boolean branch.
|
|
38
|
+
|
|
39
|
+
### Catch blocks in scope
|
|
40
|
+
|
|
41
|
+
| File | Line(s) | Context label(s) |
|
|
42
|
+
| ------------------ | ------------------------------------ | ------------------------------------------------------- |
|
|
43
|
+
| `env.ts` | 15, 23 | `"git rev-parse"`, `"git branch"` |
|
|
44
|
+
| `skill-loader.ts` | 74 | `"readdirSync skill root"` |
|
|
45
|
+
| `custom-agents.ts` | 37, 47 | `"readdirSync agents dir"`, `"readFileSync agent file"` |
|
|
46
|
+
| `memory.ts` | 33, 47 | `"lstatSync"`, `"readFileSync"` |
|
|
47
|
+
| `output-file.ts` | 83 | `"write JSONL chunk"` |
|
|
48
|
+
| `worktree.ts` | 40, 56, 110, 130, 132, 147, 151, 161 | `"git rev-parse"`, `"git worktree add"`, etc. |
|
|
49
|
+
| `agent-manager.ts` | 233, 249, 266, 275, 480 | `"outputCleanup"`, `"onComplete callback"`, etc. |
|
|
50
|
+
| `notification.ts` | 145 | `"notification render"` |
|
|
51
|
+
|
|
52
|
+
## Design Overview
|
|
53
|
+
|
|
54
|
+
### `src/debug.ts`
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
export const DEBUG = process.env.PI_SUBAGENTS_DEBUG === "1";
|
|
58
|
+
|
|
59
|
+
export function debugLog(context: string, err: unknown): void {
|
|
60
|
+
if (DEBUG) console.warn(`[pi-subagents:debug] ${context}:`, err);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The module-level `DEBUG` constant is the canonical source.
|
|
65
|
+
`debugLog` is a pure side-effect function: no return value, no SDK dependency, no coupling to Pi types.
|
|
66
|
+
|
|
67
|
+
### Threading pattern
|
|
68
|
+
|
|
69
|
+
Every silenced `catch` block is updated from:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
} catch { /* ignore */ }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
to:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
} catch (err) { debugLog("<context label>", err); }
|
|
79
|
+
```
|
|
80
|
+
|
|
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.
|
|
82
|
+
|
|
83
|
+
### Context label convention
|
|
84
|
+
|
|
85
|
+
Labels read as `"<verb> <noun>"`, lower-case, mirroring the surrounding function name when unambiguous (e.g., `"removeWorktree"`, `"outputCleanup"`).
|
|
86
|
+
|
|
87
|
+
## Module-Level Changes
|
|
88
|
+
|
|
89
|
+
| File | Change |
|
|
90
|
+
| ---------------------- | ---------------------------------------------------------------------------- |
|
|
91
|
+
| `src/debug.ts` | **New.** Exports `DEBUG` constant and `debugLog` function. |
|
|
92
|
+
| `test/debug.test.ts` | **New.** Unit tests for `debugLog` on/off behaviour. |
|
|
93
|
+
| `src/env.ts` | Add `debugLog` import; name the two bare `catch` errors; call `debugLog`. |
|
|
94
|
+
| `src/skill-loader.ts` | Add `debugLog` import; name the bare `catch` error; call `debugLog`. |
|
|
95
|
+
| `src/custom-agents.ts` | Add `debugLog` import; name the two bare `catch` errors; call `debugLog`. |
|
|
96
|
+
| `src/memory.ts` | Add `debugLog` import; name the two bare `catch` errors; call `debugLog`. |
|
|
97
|
+
| `src/output-file.ts` | Add `debugLog` import; name the bare `catch` error; call `debugLog`. |
|
|
98
|
+
| `src/worktree.ts` | Add `debugLog` import; name the eight bare `catch` errors; call `debugLog`. |
|
|
99
|
+
| `src/agent-manager.ts` | Add `debugLog` import; name/update the five `catch` blocks; call `debugLog`. |
|
|
100
|
+
| `src/notification.ts` | Add `debugLog` import; name the bare `catch` error; call `debugLog`. |
|
|
101
|
+
|
|
102
|
+
## Test Impact Analysis
|
|
103
|
+
|
|
104
|
+
1. **New tests enabled** — `debug.test.ts` can directly unit-test `debugLog` in isolation.
|
|
105
|
+
Asserting `console.warn` is/isn't called based on the env var is now trivial.
|
|
106
|
+
Previously this path was entirely untested.
|
|
107
|
+
|
|
108
|
+
2. **Existing tests become redundant** — None.
|
|
109
|
+
The catch blocks were never exercised by existing tests (they were silent drops), so no existing test coverage needs to be removed.
|
|
110
|
+
|
|
111
|
+
3. **Tests that must stay as-is** — All existing tests remain valid.
|
|
112
|
+
The threading adds a call inside each catch but does not change control flow, return values, or thrown exceptions; no test expectations change.
|
|
113
|
+
|
|
114
|
+
## TDD Order
|
|
115
|
+
|
|
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.
|
|
118
|
+
Suggested commit: `feat: add debugLog utility gated on PI_SUBAGENTS_DEBUG (#57)`
|
|
119
|
+
|
|
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).
|
|
121
|
+
Suggested commit: `feat: thread debugLog into env catch blocks (#57)`
|
|
122
|
+
|
|
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).
|
|
125
|
+
Suggested commit: `feat: thread debugLog into skill-loader catch block (#57)`
|
|
126
|
+
|
|
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.
|
|
129
|
+
Suggested commit: `feat: thread debugLog into custom-agents and memory catch blocks (#57)`
|
|
130
|
+
|
|
131
|
+
5. **Thread into `output-file.ts`** Test surface: `output-file.test.ts` — existing tests still pass.
|
|
132
|
+
Suggested commit: `feat: thread debugLog into output-file catch block (#57)`
|
|
133
|
+
|
|
134
|
+
6. **Thread into `worktree.ts`** Test surface: `worktree.test.ts` — existing tests still pass.
|
|
135
|
+
Eight catch sites; name each with its enclosing function for label clarity.
|
|
136
|
+
Suggested commit: `feat: thread debugLog into worktree catch blocks (#57)`
|
|
137
|
+
|
|
138
|
+
7. **Thread into `agent-manager.ts` and `notification.ts`** Test surface: `agent-manager.test.ts`, `notification.test.ts` — existing tests still pass.
|
|
139
|
+
Suggested commit: `feat: thread debugLog into agent-manager and notification catch blocks (#57)`
|
|
140
|
+
|
|
141
|
+
## Risks and Mitigations
|
|
142
|
+
|
|
143
|
+
| Risk | Mitigation |
|
|
144
|
+
| ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
145
|
+
| `DEBUG` is a module-level constant — `vi.stubEnv` alone won't flip it mid-test | Use `vi.resetModules()` + dynamic `import()` inside a test that sets the env var before re-importing; the test skill documents this pattern |
|
|
146
|
+
| Adding `(err)` to a bare `catch` in TypeScript is type-safe (`unknown`) but a `noImplicitAny`-adjacent change | `tsconfig` already uses `"useUnknownInCatchVariables": true` (default in strict mode); no cast needed |
|
|
147
|
+
| Verbose worktree.ts threading (8 sites) could miss one | The TDD step runs `pnpm vitest run worktree` and `pnpm run check` before committing |
|
|
148
|
+
|
|
149
|
+
## Open Questions
|
|
150
|
+
|
|
151
|
+
- Should a future issue expose `debugLog` as part of the public `service.ts` API so consumer extensions can share the same debug flag?
|
|
152
|
+
Deferred — out of scope for this change; no consumer currently needs it.
|
|
153
|
+
- Should `PI_SUBAGENTS_DEBUG` be documented in the package `README.md`?
|
|
154
|
+
Likely yes, but deferred to a follow-up doc PR.
|