@gotgenes/pi-subagents 10.2.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/docs/architecture/architecture.md +114 -59
- package/docs/plans/0229-agent-born-complete.md +564 -0
- package/docs/plans/0231-push-exec-registry-to-runner.md +245 -0
- package/docs/retro/0228-async-start-agent-dissolve-run-handle.md +38 -0
- package/docs/retro/0229-agent-born-complete.md +47 -0
- package/docs/retro/0231-push-exec-registry-to-runner.md +71 -0
- package/package.json +1 -1
- package/src/index.ts +17 -15
- package/src/lifecycle/agent-manager.ts +49 -112
- package/src/lifecycle/agent-runner.ts +31 -23
- package/src/lifecycle/agent.ts +166 -39
- package/src/observation/record-observer.ts +1 -2
- package/src/tools/agent-tool.ts +2 -2
- package/src/tools/background-spawner.ts +7 -5
- package/src/tools/foreground-runner.ts +11 -9
- package/src/types.ts +13 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 231
|
|
3
|
+
issue_title: "Push exec/registry relay deps to runner construction (Phase 15, Step 3)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Push exec/registry relay deps to runner construction
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`AgentManager` receives `exec` and `registry` in its constructor but never uses them directly.
|
|
11
|
+
They are stored as fields solely to relay them into `runner.run()` via the `RunContext` parameter.
|
|
12
|
+
This makes `AgentManager` wider than necessary and prevents the runner from being self-contained — a prerequisite for #229 (Agent.run() absorbs startAgent).
|
|
13
|
+
|
|
14
|
+
## Goals
|
|
15
|
+
|
|
16
|
+
- Move `exec` and `registry` from `AgentManager` construction to `ConcreteAgentRunner` construction.
|
|
17
|
+
- Remove `exec` and `registry` from `AgentManagerOptions` (7 → 5 fields).
|
|
18
|
+
- Remove `exec` and `registry` from `RunContext` (4 → 2 fields).
|
|
19
|
+
- Group runner-owned dependencies in a `RunnerDeps` interface: `{ io, exec, registry }`.
|
|
20
|
+
- Replace `runAgent()`'s `io: RunnerIO` parameter with `deps: RunnerDeps`.
|
|
21
|
+
|
|
22
|
+
## Non-Goals
|
|
23
|
+
|
|
24
|
+
- Dissolving `RunContext` entirely — it shrinks to `{ cwd?, parentSession? }`, which is still a coherent per-call grouping.
|
|
25
|
+
Issue #229 will likely dissolve it when `Agent.run()` calls the runner directly.
|
|
26
|
+
- Changing the `AgentRunner` interface's `run()` signature — callers continue to pass `RunOptions` with `context: RunContext`.
|
|
27
|
+
`ConcreteAgentRunner` merges its stored deps before calling `runAgent()`.
|
|
28
|
+
- Touching `resume()` or `resumeAgent()` — they don't use `exec` or `registry`.
|
|
29
|
+
|
|
30
|
+
## Background
|
|
31
|
+
|
|
32
|
+
Issue #169 extracted `RunContext` from `RunOptions` to group the 4 parent-context fields: `exec`, `registry`, `cwd`, `parentSession`.
|
|
33
|
+
The doc comment describes them as "parent environment and identity" fields.
|
|
34
|
+
However, 2 of the 4 fields (`exec`, `registry`) are static — identical across every `run()` call — while the other 2 (`cwd`, `parentSession`) vary per spawn.
|
|
35
|
+
The static pair are relay-only dependencies on `AgentManager`: stored at construction, never read, only forwarded.
|
|
36
|
+
|
|
37
|
+
From the code-design skill, this is a **parameter relay** smell: intermediaries (`AgentManager`) carry fields they never use, only to thread them to the endpoint (`runAgent`).
|
|
38
|
+
The fix: put them on the object the endpoint owns — the runner.
|
|
39
|
+
|
|
40
|
+
### Key references
|
|
41
|
+
|
|
42
|
+
- `src/lifecycle/agent-manager.ts` — stores `exec` and `registry`, relays them at lines 193–194.
|
|
43
|
+
- `src/lifecycle/agent-runner.ts` — `RunContext` interface (line 125), `ConcreteAgentRunner` class (line 189), `runAgent()` free function (line 236).
|
|
44
|
+
- `src/index.ts` — constructs both `ConcreteAgentRunner` and `AgentManager` (lines 148–157).
|
|
45
|
+
- Phase 15 roadmap in `docs/architecture/architecture.md` § Step 3.
|
|
46
|
+
|
|
47
|
+
## Design Overview
|
|
48
|
+
|
|
49
|
+
### RunnerDeps — grouping runner-owned dependencies
|
|
50
|
+
|
|
51
|
+
A new `RunnerDeps` interface groups the three dependencies that the runner owns:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
export interface RunnerDeps {
|
|
55
|
+
io: RunnerIO;
|
|
56
|
+
exec: ShellExec;
|
|
57
|
+
registry: AgentConfigLookup;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`ConcreteAgentRunner` takes `RunnerDeps` at construction:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
export class ConcreteAgentRunner implements AgentRunner {
|
|
65
|
+
constructor(private readonly deps: RunnerDeps) {}
|
|
66
|
+
|
|
67
|
+
run(snapshot, type, prompt, options) {
|
|
68
|
+
return runAgent(snapshot, type, prompt, options, this.deps);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`runAgent()` changes its last parameter from `io: RunnerIO` to `deps: RunnerDeps`:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
export async function runAgent(
|
|
77
|
+
snapshot: ParentSnapshot,
|
|
78
|
+
type: SubagentType,
|
|
79
|
+
prompt: string,
|
|
80
|
+
options: RunOptions,
|
|
81
|
+
deps: RunnerDeps,
|
|
82
|
+
): Promise<RunResult> {
|
|
83
|
+
const effectiveCwd = options.context?.cwd ?? snapshot.cwd;
|
|
84
|
+
const env = await deps.io.detectEnv(deps.exec, effectiveCwd);
|
|
85
|
+
// ...
|
|
86
|
+
const cfg = assembleSessionConfig(type, ..., deps.registry, deps.io.assemblerIO);
|
|
87
|
+
// ...
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### RunContext shrinks
|
|
92
|
+
|
|
93
|
+
`RunContext` loses `exec` and `registry`:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
export interface RunContext {
|
|
97
|
+
/** Override working directory (e.g. for worktree isolation). */
|
|
98
|
+
cwd?: string;
|
|
99
|
+
/** Parent session identity (file path + session ID). */
|
|
100
|
+
parentSession?: ParentSessionInfo;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The `AgentRunner.run()` interface is unchanged — callers still pass `RunOptions` with `context: RunContext`.
|
|
105
|
+
`ConcreteAgentRunner.run()` reads `exec` and `registry` from its own `deps` instead of from `options.context`.
|
|
106
|
+
|
|
107
|
+
### AgentManager loses 2 fields
|
|
108
|
+
|
|
109
|
+
`AgentManagerOptions` removes `exec` and `registry`.
|
|
110
|
+
`AgentManager` removes the corresponding private fields and the `this.exec` / `this.registry` relay in `startAgent()`.
|
|
111
|
+
The `context` object constructed in `startAgent()` shrinks from 4 fields to 2:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
context: {
|
|
115
|
+
cwd: record.worktreeState?.path,
|
|
116
|
+
parentSession: options.parentSession,
|
|
117
|
+
},
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Wiring in index.ts
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const runner = new ConcreteAgentRunner({
|
|
124
|
+
io: runnerIO,
|
|
125
|
+
exec: (cmd, args, opts) => pi.exec(cmd, args, opts),
|
|
126
|
+
registry,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const manager = new AgentManager({
|
|
130
|
+
runner,
|
|
131
|
+
worktrees: new GitWorktreeManager(process.cwd()),
|
|
132
|
+
observer,
|
|
133
|
+
getMaxConcurrent: () => settings.maxConcurrent,
|
|
134
|
+
getRunConfig: () => settings,
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Module-Level Changes
|
|
139
|
+
|
|
140
|
+
### `src/lifecycle/agent-runner.ts`
|
|
141
|
+
|
|
142
|
+
1. Add `RunnerDeps` interface (exported): `{ io: RunnerIO; exec: ShellExec; registry: AgentConfigLookup }`.
|
|
143
|
+
2. Remove `exec` and `registry` from `RunContext`.
|
|
144
|
+
Update doc comment to reflect the 2 remaining per-call fields.
|
|
145
|
+
3. Update `ConcreteAgentRunner` constructor: accept `RunnerDeps` instead of `RunnerIO`.
|
|
146
|
+
4. Update `ConcreteAgentRunner.run()`: pass `this.deps` to `runAgent()`.
|
|
147
|
+
5. Update `runAgent()`: change last parameter from `io: RunnerIO` to `deps: RunnerDeps`.
|
|
148
|
+
Replace `io.` references with `deps.io.`, `options.context.exec` with `deps.exec`, `options.context.registry` with `deps.registry`.
|
|
149
|
+
|
|
150
|
+
### `src/lifecycle/agent-manager.ts`
|
|
151
|
+
|
|
152
|
+
1. Remove `exec: ShellExec` and `registry: AgentTypeRegistry` from `AgentManagerOptions`.
|
|
153
|
+
2. Remove `private readonly exec` and `private readonly registry` fields from `AgentManager`.
|
|
154
|
+
3. Remove assignment of `this.exec` and `this.registry` in the constructor.
|
|
155
|
+
4. Remove `exec: this.exec` and `registry: this.registry` from the `context` object in `startAgent()`.
|
|
156
|
+
5. Remove `ShellExec` and `AgentTypeRegistry` imports (verify no other references first).
|
|
157
|
+
|
|
158
|
+
### `src/index.ts`
|
|
159
|
+
|
|
160
|
+
1. Move `exec` and `registry` from the `AgentManager` constructor call to `ConcreteAgentRunner`:
|
|
161
|
+
`new ConcreteAgentRunner({ io: runnerIO, exec: ..., registry })`.
|
|
162
|
+
2. Remove `exec` and `registry` from the `AgentManager({...})` constructor argument.
|
|
163
|
+
|
|
164
|
+
### `test/lifecycle/agent-runner.test.ts`
|
|
165
|
+
|
|
166
|
+
1. Update all `runAgent(..., io)` calls to `runAgent(..., { io, exec, registry: mockAgentLookup })`.
|
|
167
|
+
2. Remove `exec` and `registry` from `context:` objects in `RunOptions`.
|
|
168
|
+
`context: { exec, registry: mockAgentLookup }` → `context: {}` or `{}`.
|
|
169
|
+
|
|
170
|
+
### `test/lifecycle/agent-runner-extension-tools.test.ts`
|
|
171
|
+
|
|
172
|
+
1. Same pattern as `agent-runner.test.ts`: update `runAgent(..., io)` last param and strip `exec`/`registry` from `context:`.
|
|
173
|
+
|
|
174
|
+
### `test/lifecycle/concrete-agent-runner.test.ts`
|
|
175
|
+
|
|
176
|
+
1. Update `new ConcreteAgentRunner(io)` → `new ConcreteAgentRunner({ io, exec: vi.fn(), registry })`.
|
|
177
|
+
2. Remove `exec` and `registry` from the `context:` in `runner.run()` call options.
|
|
178
|
+
|
|
179
|
+
### `test/lifecycle/agent-manager.test.ts`
|
|
180
|
+
|
|
181
|
+
1. Remove `exec: vi.fn()` and `registry: testRegistry` from `createManager()` factory.
|
|
182
|
+
2. Remove the `testRegistry` construction and `AgentTypeRegistry` import if no other references exist.
|
|
183
|
+
|
|
184
|
+
### `test/helpers/runner-io.ts`
|
|
185
|
+
|
|
186
|
+
1. No structural changes needed — `createRunnerIO()` returns the `RunnerIO` shape, which is unchanged.
|
|
187
|
+
However, add a `createRunnerDeps()` convenience factory that bundles `{ io: createRunnerIO(), exec: vi.fn(), registry: createAgentLookup() }` for runner test files.
|
|
188
|
+
|
|
189
|
+
### `docs/architecture/architecture.md`
|
|
190
|
+
|
|
191
|
+
1. Update the `RunContext` code block in § "RunOptions (12 fields → extract RunContext)" to show only `cwd` and `parentSession`.
|
|
192
|
+
2. Update the field-count description (4 → 2 per-call fields).
|
|
193
|
+
3. Mark Step 3 as complete in the Phase 15 roadmap.
|
|
194
|
+
|
|
195
|
+
## Test Impact Analysis
|
|
196
|
+
|
|
197
|
+
1. No new test surfaces are needed — this is a pure mechanical refactoring (moving constructor parameters).
|
|
198
|
+
The existing runner and manager test suites fully cover the behavior.
|
|
199
|
+
2. No existing tests become redundant — all tests exercise the same interactions, just with deps flowing through a different path.
|
|
200
|
+
3. Existing `agent-manager.test.ts` tests remain as-is in coverage scope.
|
|
201
|
+
They verify `AgentManager` behavior (spawning, queueing, abort, etc.) independent of runner deps.
|
|
202
|
+
4. Existing `agent-runner.test.ts` and `concrete-agent-runner.test.ts` tests remain.
|
|
203
|
+
They verify `runAgent()` and `ConcreteAgentRunner` behavior.
|
|
204
|
+
Call-site patterns change but assertions stay the same.
|
|
205
|
+
|
|
206
|
+
## TDD Order
|
|
207
|
+
|
|
208
|
+
1. **Add `RunnerDeps` interface and update `runAgent()` parameter** — define `RunnerDeps`, change `runAgent()`'s last param from `io` to `deps`, update internal references.
|
|
209
|
+
Update `agent-runner.test.ts` and `agent-runner-extension-tools.test.ts` call sites.
|
|
210
|
+
Commit: `refactor: add RunnerDeps and update runAgent parameter (#231)`
|
|
211
|
+
|
|
212
|
+
2. **Update `ConcreteAgentRunner` to accept `RunnerDeps`** — change constructor from `RunnerIO` to `RunnerDeps`, update `.run()` to pass `this.deps`.
|
|
213
|
+
Update `concrete-agent-runner.test.ts`.
|
|
214
|
+
Add `createRunnerDeps()` helper to `test/helpers/runner-io.ts`.
|
|
215
|
+
Commit: `refactor: ConcreteAgentRunner accepts RunnerDeps (#231)`
|
|
216
|
+
|
|
217
|
+
3. **Remove `exec` and `registry` from `RunContext`** — shrink the interface to 2 fields, update doc comment.
|
|
218
|
+
Strip `exec`/`registry` from `context:` in all runner test call sites.
|
|
219
|
+
Run `pnpm run check` to verify no stale references.
|
|
220
|
+
Commit: `refactor: remove exec and registry from RunContext (#231)`
|
|
221
|
+
|
|
222
|
+
4. **Remove `exec` and `registry` from `AgentManager`** — remove from `AgentManagerOptions`, remove class fields, remove relay in `startAgent()`, clean up imports.
|
|
223
|
+
Update `agent-manager.test.ts` factory.
|
|
224
|
+
Commit: `refactor: remove relay deps from AgentManager (#231)`
|
|
225
|
+
|
|
226
|
+
5. **Update wiring in `index.ts`** — move `exec` and `registry` from `AgentManager` construction to `ConcreteAgentRunner` construction.
|
|
227
|
+
Commit: `refactor: wire exec and registry to ConcreteAgentRunner (#231)`
|
|
228
|
+
|
|
229
|
+
6. **Update architecture docs** — update `RunContext` description and field counts, mark Step 3 complete.
|
|
230
|
+
Commit: `docs: update architecture for runner self-contained (#231)`
|
|
231
|
+
|
|
232
|
+
## Risks and Mitigations
|
|
233
|
+
|
|
234
|
+
1. **Test churn** — ~20 `runAgent()` call sites change their last parameter pattern.
|
|
235
|
+
Mitigation: mechanical find-and-replace; assertions stay identical.
|
|
236
|
+
2. **Step ordering** — Steps 3 and 4 both remove `exec`/`registry` from different types.
|
|
237
|
+
If done in the wrong order, intermediate commits may not type-check.
|
|
238
|
+
Mitigation: Step 1–2 add the new path (`deps`), Step 3 removes from `RunContext` (runner side), Step 4 removes from `AgentManager` (manager side), Step 5 wires them together.
|
|
239
|
+
Each commit is independently valid.
|
|
240
|
+
3. **Import cleanup** — removing `exec`/`registry` from `AgentManager` may leave unused imports (`ShellExec`, `AgentTypeRegistry`).
|
|
241
|
+
Mitigation: grep for other usages before removing; `pnpm run check` catches unused imports.
|
|
242
|
+
|
|
243
|
+
## Open Questions
|
|
244
|
+
|
|
245
|
+
- None — the issue scope is narrow and the design is straightforward.
|
|
@@ -40,3 +40,41 @@ Test count: 986 → 1005.
|
|
|
40
40
|
- Pre-completion reviewer returned WARN for stale `AgentRecord` and `run-handle.ts` references in `architecture.md` class diagram and layout listing.
|
|
41
41
|
These were pre-existing staleness from #227's rename that wasn't fully propagated to Mermaid diagrams.
|
|
42
42
|
Fixed by amending the docs commit.
|
|
43
|
+
|
|
44
|
+
## Stage: Final Retrospective (2026-05-27T21:46:00Z)
|
|
45
|
+
|
|
46
|
+
### Session summary
|
|
47
|
+
|
|
48
|
+
Completed all stages (plan, TDD, ship, retro) in a single session.
|
|
49
|
+
Dissolved `RunHandle` into 6 Agent methods, converted `startAgent` to async/await, released as `pi-subagents-v10.2.0`.
|
|
50
|
+
Test delta: 986 → 1005 (+19).
|
|
51
|
+
|
|
52
|
+
### Observations
|
|
53
|
+
|
|
54
|
+
#### What went well
|
|
55
|
+
|
|
56
|
+
- The user's two redirecting questions during planning ("What's the change that makes this easier?"
|
|
57
|
+
and "Tell me more about RunHandle — is there something that should replace it?") transformed a mechanical "move the class" plan into a "dissolve the abstraction" plan.
|
|
58
|
+
The dissolve approach is architecturally superior and sets up #232 (resume unification) for free.
|
|
59
|
+
- The lift-and-shift decomposition (introduce Agent methods alongside `RunHandle`, then swap and delete) produced 5 independently-green commits.
|
|
60
|
+
The riskiest commit (step 3: delete `RunHandle`, -96/+6 lines) was trivially safe because step 2 had already proven the replacement methods.
|
|
61
|
+
|
|
62
|
+
#### What caused friction (agent side)
|
|
63
|
+
|
|
64
|
+
- `premature-convergence` — The agent planned around the issue's proposed `Agent.createRunHandle()` factory without questioning whether `RunHandle` should exist as a separate class.
|
|
65
|
+
The user had to ask two redirecting questions to push the analysis deeper.
|
|
66
|
+
Impact: plan was rewritten before commit (no wasted implementation), but the user spent two turns guiding analysis the agent should have done proactively.
|
|
67
|
+
- `missing-context` — Plan step 1 (narrow `Promise<string>` to `Promise<void>`) listed only `agent-manager.test.ts` for updates but missed 3 additional test files (`make-agent.test.ts`, `service-adapter.test.ts`, `get-result-tool.test.ts`) that construct `Promise<string>` values.
|
|
68
|
+
The testing skill says "grep for all test files" for type changes — this was not applied during planning.
|
|
69
|
+
Impact: caught by `pnpm run check` in the same step, no rework.
|
|
70
|
+
|
|
71
|
+
#### What caused friction (user side)
|
|
72
|
+
|
|
73
|
+
- No friction observed.
|
|
74
|
+
The user's questioning style (asking "what does it do?
|
|
75
|
+
who needs it?"
|
|
76
|
+
rather than prescribing the solution) was collaborative and effective.
|
|
77
|
+
|
|
78
|
+
### Changes made
|
|
79
|
+
|
|
80
|
+
1. `.pi/prompts/plan-issue.md` — added relocation-dissolution heuristic: when an issue proposes moving a class, list callers and fields touched to check if it should be dissolved into the owner instead.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 229
|
|
3
|
+
issue_title: "Agent born complete: Agent.run() absorbs startAgent (Phase 15, Step 4)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #229 — Agent born complete: Agent.run() absorbs startAgent
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-05-27T18:00:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Produced a 9-step TDD plan for absorbing `AgentManager.startAgent()` into `Agent.run()`.
|
|
13
|
+
Key design decisions: per-agent `AgentLifecycleObserver` interface passed at construction (chosen over callback fields and EventEmitter), and fully async worktree error surface (chosen over split sync/async).
|
|
14
|
+
|
|
15
|
+
### Observations
|
|
16
|
+
|
|
17
|
+
- **Observer pattern chosen over callbacks:** The per-agent `AgentLifecycleObserver` interface replaces three separate mechanisms (`onSessionCreated` callback, `setOnRunFinished`, `onCompact` callback).
|
|
18
|
+
All methods are optional, composed by `AgentManager.buildObserver()` per spawn.
|
|
19
|
+
- **`ParentSessionInfo`/`CompactionInfo` relocation needed:** `agent.ts` importing from `agent-manager.ts` would create a circular type import (agent-manager already imports `Agent`).
|
|
20
|
+
Moving both types to `types.ts` in step 1 avoids the cycle.
|
|
21
|
+
- **`AgentInit` grows wide (15+ optional fields):** Making run-config fields optional preserves backward compat for the 55+ `new Agent()` calls in tests.
|
|
22
|
+
Noted as a known smell — follow-up issues (#230 ConcurrencyQueue, potential `AgentInit` restructuring) may address this.
|
|
23
|
+
- **Async error surface changes tool behavior:** `background-spawner.ts`'s try/catch around `manager.spawn()` becomes unreachable for worktree errors.
|
|
24
|
+
Keeping it for robustness; the error surfaces on `record.error` instead.
|
|
25
|
+
- **Lift-and-shift TDD order:** Steps 3–5 incrementally change `AgentInit`, `setupWorktree`, and `completeRun`/`failRun` before step 6 adds `Agent.run()`.
|
|
26
|
+
This avoids a single massive step that rewrites everything at once.
|
|
27
|
+
|
|
28
|
+
## Stage: Implementation — TDD (2026-05-28T01:00:00Z)
|
|
29
|
+
|
|
30
|
+
### Session summary
|
|
31
|
+
|
|
32
|
+
Completed all 9 TDD steps in 9 commits (plus 2 planning/retro docs commits).
|
|
33
|
+
Test count went from 1005 to 1020 (+15 tests).
|
|
34
|
+
`AgentManager.startAgent()`, `SpawnArgs`, and `onSessionCreated` callback are deleted.
|
|
35
|
+
`Agent.run()` now owns the full execution lifecycle.
|
|
36
|
+
|
|
37
|
+
### Observations
|
|
38
|
+
|
|
39
|
+
- **Steps 7–8 merged in practice:** The tool-layer `onSessionCreated` → `observer` migration (step 8) had to be done alongside the `AgentSpawnConfig` change (step 7) because removing the `onSessionCreated` field broke compilation of `background-spawner.ts` and `foreground-runner.ts`.
|
|
40
|
+
This was expected — they share the same type.
|
|
41
|
+
- **`setupWorktree` kept public:** The plan called for making it private in step 4, but it was kept public through step 6 since the manager still called it.
|
|
42
|
+
After step 7 (Agent.run() absorbs the call), it could be made private; left as a minor follow-up (reviewer flagged as WARN).
|
|
43
|
+
- **`isBackground` removed from Agent storage:** The field was declared on `AgentInit` but Agent never reads it — the manager resolves `isBackground` before construction (setting initial status and composing the observer).
|
|
44
|
+
Biome flagged it as unused; removed from stored fields, kept on `AgentInit` for the manager's use.
|
|
45
|
+
- **Worktree error surface confirmed async:** The `agent-manager.test.ts` test for synchronous worktree throw was rewritten to verify the error surfaces on `record.error` after awaiting the promise.
|
|
46
|
+
`background-spawner.ts` try/catch around `spawn()` retained for robustness.
|
|
47
|
+
- **Pre-completion reviewer:** WARN — 3 non-blocking findings: `setupWorktree` not marked private, `isBackground` dead field on `AgentInit`, and `package-pi-subagents` SKILL.md Phase 15 description referencing deleted `startAgent`.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 231
|
|
3
|
+
issue_title: "Push exec/registry relay deps to runner construction (Phase 15, Step 3)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #231 — Push exec/registry relay deps to runner construction
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-05-27T21:53:10Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Produced a 6-step TDD plan to move `exec` and `registry` from `AgentManager` to `ConcreteAgentRunner` via a new `RunnerDeps` interface.
|
|
13
|
+
The plan keeps `RunContext` (shrunk to 2 per-call fields) rather than dissolving it — #229 will likely dissolve it when `Agent.run()` calls the runner directly.
|
|
14
|
+
|
|
15
|
+
### Observations
|
|
16
|
+
|
|
17
|
+
- Confirmed `exec` and `registry` are pure relay deps on `AgentManager` — stored at construction, used only at lines 193–194 to forward into `runner.run()`.
|
|
18
|
+
- Chose `RunnerDeps` bag over separate positional params on `ConcreteAgentRunner` and `runAgent()` — groups all three runner-owned deps (`io`, `exec`, `registry`) in one interface, and `runAgent()` stays at 5 parameters.
|
|
19
|
+
- `AgentManagerOptions.registry` uses the concrete `AgentTypeRegistry` class; `RunContext.registry` uses the narrow `AgentConfigLookup` interface.
|
|
20
|
+
The new `RunnerDeps.registry` uses `AgentConfigLookup` (ISP).
|
|
21
|
+
- Test churn is moderate (~20 `runAgent()` call sites change last param pattern) but mechanical — assertions stay identical.
|
|
22
|
+
- Added a `createRunnerDeps()` test helper to `runner-io.ts` to reduce per-file boilerplate in runner tests.
|
|
23
|
+
|
|
24
|
+
## Stage: Implementation — TDD (2026-05-27T22:05:32Z)
|
|
25
|
+
|
|
26
|
+
### Session summary
|
|
27
|
+
|
|
28
|
+
Implemented the 6-step plan in 4 commits (steps 3–5 merged).
|
|
29
|
+
All 1005 tests pass; no test count change.
|
|
30
|
+
Pre-completion reviewer returned PASS.
|
|
31
|
+
|
|
32
|
+
### Observations
|
|
33
|
+
|
|
34
|
+
- Plan steps 3, 4, and 5 could not be separate commits: removing `exec`/`registry` from `RunContext` (step 3) immediately caused TypeScript excess-property errors in `AgentManager` (step 4) and `index.ts` (step 5).
|
|
35
|
+
Merged all three into one commit.
|
|
36
|
+
The testing skill’s rule “when a TDD step changes an interface that has a single call site, the step must include updating that call site” applies.
|
|
37
|
+
- Shrinking `RunContext` to all-optional fields made pre-existing `as never` casts in `test/helpers/manager-stubs.test.ts` unnecessary (eslint `no-unnecessary-type-assertion`).
|
|
38
|
+
Fixed as a lint cleanup in the doc commit.
|
|
39
|
+
- The `sed`-based bulk replacement for `runAgent(..., io)` → `runAgent(..., { io, exec, registry: mockAgentLookup })` missed one multi-line call site (the `rejects.toThrow` test wrapping the call in `expect()`).
|
|
40
|
+
Caught immediately by the test run.
|
|
41
|
+
|
|
42
|
+
## Stage: Final Retrospective (2026-05-27T22:43:52Z)
|
|
43
|
+
|
|
44
|
+
### Session summary
|
|
45
|
+
|
|
46
|
+
Shipped #231 cleanly: CI passed on first push, issue closed, release `pi-subagents-v10.2.1` published.
|
|
47
|
+
The entire issue (plan → TDD → ship) completed in one sitting with no user intervention needed.
|
|
48
|
+
|
|
49
|
+
### Observations
|
|
50
|
+
|
|
51
|
+
#### What went well
|
|
52
|
+
|
|
53
|
+
- The `RunnerDeps` design was unambiguous — the `ask_user` gate in planning correctly identified the one genuine design choice (`RunContext` fate) and got user input before proceeding.
|
|
54
|
+
- Pre-completion reviewer returned PASS with zero findings, confirming the mechanical refactoring was clean.
|
|
55
|
+
- Merging plan steps 3–5 during TDD was the right call; the testing skill rule about single-call-site interfaces caught the plan's error before any broken commit landed.
|
|
56
|
+
|
|
57
|
+
#### What caused friction (agent side)
|
|
58
|
+
|
|
59
|
+
- `wrong-abstraction` — The plan listed steps 3, 4, and 5 as separate commits and claimed "each commit is independently valid," but removing fields from `RunContext` (step 3) immediately caused TypeScript excess-property errors in `AgentManager` (step 4) and `index.ts` (step 5).
|
|
60
|
+
The existing `/plan-issue` rule (line 109) covers removing exports with single call sites, but did not trigger recognition because this was *shrinking* an interface, not removing one.
|
|
61
|
+
Impact: the TDD agent had to merge three steps on the fly — no rework, but the plan was misleading.
|
|
62
|
+
- `missing-context` — The `sed`-based bulk replacement for `runAgent(..., io)` missed one multi-line call site where `}, io)` appeared on a different line than the opening `runAgent(`.
|
|
63
|
+
Impact: one extra manual edit; caught immediately by the test run.
|
|
64
|
+
|
|
65
|
+
#### What caused friction (user side)
|
|
66
|
+
|
|
67
|
+
- No friction observed — the user's involvement was limited to confirming the `RunContext` design choice during planning.
|
|
68
|
+
|
|
69
|
+
### Changes made
|
|
70
|
+
|
|
71
|
+
1. `.pi/prompts/plan-issue.md` — added a rule under TDD Order: when a step removes fields from an interface, include downstream object-literal call-site updates in the same step (TypeScript excess property checking).
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { AgentTypeRegistry } from "#src/config/agent-types";
|
|
|
24
24
|
import { loadCustomAgents } from "#src/config/custom-agents";
|
|
25
25
|
import { SessionLifecycleHandler, ToolStartHandler } from "#src/handlers/index";
|
|
26
26
|
import { AgentManager, type AgentManagerObserver } from "#src/lifecycle/agent-manager";
|
|
27
|
-
import { ConcreteAgentRunner, type
|
|
27
|
+
import { ConcreteAgentRunner, type RunnerDeps } from "#src/lifecycle/agent-runner";
|
|
28
28
|
import { buildParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
29
29
|
import { GitWorktreeManager } from "#src/lifecycle/worktree";
|
|
30
30
|
import { buildEventData, type NotificationDetails, NotificationManager } from "#src/observation/notification";
|
|
@@ -132,25 +132,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
132
132
|
},
|
|
133
133
|
};
|
|
134
134
|
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
135
|
+
const runnerDeps: RunnerDeps = {
|
|
136
|
+
io: {
|
|
137
|
+
detectEnv,
|
|
138
|
+
getAgentDir,
|
|
139
|
+
createResourceLoader: (opts) => new DefaultResourceLoader(opts),
|
|
140
|
+
deriveSessionDir: deriveSubagentSessionDir,
|
|
141
|
+
createSessionManager: (cwd, dir) => SessionManager.create(cwd, dir),
|
|
142
|
+
createSettingsManager: (cwd, dir) => SdkSettingsManager.create(cwd, dir),
|
|
143
|
+
createSession: (opts) => createAgentSession(opts as any),
|
|
144
|
+
assemblerIO: {
|
|
145
|
+
preloadSkills,
|
|
146
|
+
buildAgentPrompt,
|
|
147
|
+
},
|
|
146
148
|
},
|
|
149
|
+
exec: (cmd, args, opts) => pi.exec(cmd, args, opts),
|
|
150
|
+
registry,
|
|
147
151
|
};
|
|
148
152
|
|
|
149
153
|
const manager = new AgentManager({
|
|
150
|
-
runner: new ConcreteAgentRunner(
|
|
154
|
+
runner: new ConcreteAgentRunner(runnerDeps),
|
|
151
155
|
worktrees: new GitWorktreeManager(process.cwd()),
|
|
152
|
-
exec: (cmd, args, opts) => pi.exec(cmd, args, opts),
|
|
153
|
-
registry,
|
|
154
156
|
observer,
|
|
155
157
|
getMaxConcurrent: () => settings.maxConcurrent,
|
|
156
158
|
getRunConfig: () => settings,
|