@gotgenes/pi-subagents 16.6.0 → 17.0.1
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 +19 -0
- package/dist/public.d.ts +4 -1
- package/docs/architecture/architecture.md +238 -169
- package/docs/architecture/history/phase-18-reconsider-ui.md +166 -0
- package/docs/decisions/0004-reconsider-ui-direction.md +220 -0
- package/docs/plans/0422-delete-activity-tracker-ui-observer.md +204 -0
- package/docs/plans/0423-widget-self-drive-from-lifecycle.md +264 -0
- package/docs/plans/0424-drop-widget-dep-from-subagent-tool.md +166 -0
- package/docs/plans/0425-reconcile-subagent-events-contract.md +144 -0
- package/docs/plans/0426-consolidate-residual-test-clone-families.md +235 -0
- package/docs/plans/0427-reconsider-ui-direction.md +212 -0
- package/docs/plans/0446-spike-session-navigation-entry-criteria.md +171 -0
- package/docs/plans/0448-block-disabled-agent-spawn.md +207 -0
- package/docs/retro/0421-migrate-activity-readers-to-record-getters.md +49 -0
- package/docs/retro/0422-delete-activity-tracker-ui-observer.md +97 -0
- package/docs/retro/0423-widget-self-drive-from-lifecycle.md +112 -0
- package/docs/retro/0424-drop-widget-dep-from-subagent-tool.md +89 -0
- package/docs/retro/0425-reconcile-subagent-events-contract.md +101 -0
- package/docs/retro/0426-consolidate-residual-test-clone-families.md +100 -0
- package/docs/retro/0427-reconsider-ui-direction.md +102 -0
- package/docs/retro/0446-spike-session-navigation-entry-criteria.md +99 -0
- package/docs/retro/0448-block-disabled-agent-spawn.md +42 -0
- package/package.json +1 -1
- package/src/index.ts +11 -3
- package/src/observation/composite-subagent-observer.ts +49 -0
- package/src/runtime.ts +0 -6
- package/src/service/service.ts +4 -1
- package/src/tools/agent-tool.ts +1 -36
- package/src/tools/background-spawner.ts +1 -25
- package/src/tools/foreground-runner.ts +1 -30
- package/src/tools/helpers.ts +3 -2
- package/src/tools/spawn-config.ts +6 -0
- package/src/types.ts +1 -1
- package/src/ui/agent-widget.ts +34 -14
- package/src/ui/agent-activity-tracker.ts +0 -84
- package/src/ui/ui-observer.ts +0 -61
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 448
|
|
3
|
+
issue_title: "`enabled: false` does not prevent explicitly spawning disabled agents"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Honor `enabled: false` in the spawn path and tool-description list
|
|
7
|
+
|
|
8
|
+
## Release Recommendation
|
|
9
|
+
|
|
10
|
+
**Release:** ship independently
|
|
11
|
+
|
|
12
|
+
This is a standalone bug fix.
|
|
13
|
+
It is not referenced by any step in `packages/pi-subagents/docs/architecture/architecture.md`, so it carries no `Release:` batch tag and should ship on its own.
|
|
14
|
+
|
|
15
|
+
## Problem Statement
|
|
16
|
+
|
|
17
|
+
In `@gotgenes/pi-subagents`, setting `enabled: false` on a custom agent override (e.g. `.pi/agents/Plan.md`) hides the agent from `getAvailableTypes()` — so it disappears from the `subagent_type` available-types list — but does **not** prevent it from being spawned when named explicitly via `subagent_type: "Plan"`.
|
|
18
|
+
|
|
19
|
+
This contradicts both the README ("disabled per-project with `enabled: false`") and the inline registry comment in `agent-types.ts` ("Disabled agents are kept but excluded from spawning").
|
|
20
|
+
|
|
21
|
+
The root cause is that the spawn resolution path (`resolveSpawnConfig`) calls `registry.resolveType()`, which delegates to `resolveKey()` and only checks key existence, never `enabled`.
|
|
22
|
+
The registry already has an `isValidType()` method that checks `enabled`, but it is not used in the spawn path.
|
|
23
|
+
|
|
24
|
+
A secondary inconsistency: `buildTypeListText()` (the subagent tool description) lists agents via `getDefaultAgentNames()` / `getUserAgentNames()`, which filter by `isDefault` only, not `enabled`.
|
|
25
|
+
So a disabled agent still appears in the "Default agents" / "Custom agents" sections of the tool description, even though it is correctly excluded from the `subagent_type` parameter's available-types line.
|
|
26
|
+
|
|
27
|
+
## Goals
|
|
28
|
+
|
|
29
|
+
- A disabled agent named explicitly via `subagent_type` is **not** spawned.
|
|
30
|
+
Instead, the spawn returns an explicit error: `Agent type "<Name>" is disabled` (operator-confirmed behavior over the fallback alternative).
|
|
31
|
+
- The error uses the registry's canonical key for the name, so case-insensitive input (`plan` → `Plan`) reports the canonical casing.
|
|
32
|
+
- Disabled agents no longer appear in the "Default agents" / "Custom agents" sections of the subagent tool description, making the tool description consistent with the `subagent_type` available-types line.
|
|
33
|
+
- Genuinely unknown types continue to fall back to `general-purpose` (unchanged).
|
|
34
|
+
- Enabled types continue to resolve and spawn exactly as before (unchanged).
|
|
35
|
+
|
|
36
|
+
This is a **behavior change** but **not a breaking change**: explicit spawning of a disabled agent was buggy, undocumented behavior that contradicted the documented contract.
|
|
37
|
+
The fix makes the code match what the README and registry comment already promise.
|
|
38
|
+
Commit messages use `fix:`.
|
|
39
|
+
|
|
40
|
+
## Non-Goals
|
|
41
|
+
|
|
42
|
+
- No change to the lenient unknown-type fallback (`fellBack` → `general-purpose`).
|
|
43
|
+
Disabled is distinct from unknown: a disabled type is a known agent that the operator deliberately turned off, so an explicit error is more informative than a silent fallback.
|
|
44
|
+
- No change to `resolveAgentConfig()`'s handling of disabled configs.
|
|
45
|
+
The UI (`display.ts`, `agent-menu.ts`, `agent-config-editor.ts`) deliberately resolves disabled configs to display, edit, and re-enable them; that behavior must be preserved.
|
|
46
|
+
- No change to the `/agents` menu, which lists disabled agents (marked `✕`) via `getAllTypes()` — intentional and unaffected.
|
|
47
|
+
- No README rewrite: the README already states that `enabled: false` disables an agent for spawning, so it is now accurate.
|
|
48
|
+
(An optional one-line note documenting the error message is in Open Questions.)
|
|
49
|
+
|
|
50
|
+
## Background
|
|
51
|
+
|
|
52
|
+
Relevant modules:
|
|
53
|
+
|
|
54
|
+
- `src/config/agent-types.ts` — `AgentTypeRegistry`.
|
|
55
|
+
- `resolveType(name)` → `resolveKey(name)`: returns the canonical key for any existing agent, **ignoring `enabled`**.
|
|
56
|
+
- `isValidType(type)`: resolves the key and returns `false` when the config has `enabled === false` (or the key is unknown).
|
|
57
|
+
Already exists, already tested, **not used in the spawn path**.
|
|
58
|
+
- `resolveAgentConfig(type)`: returns the config for an existing key (including disabled) or falls back to `general-purpose`.
|
|
59
|
+
Used by both the spawn path and the UI.
|
|
60
|
+
- `getDefaultAgentNames()` / `getUserAgentNames()`: filter by `isDefault`, not `enabled`.
|
|
61
|
+
Sole consumer is `buildTypeListText` (verified by grep across `src/` and `test/`).
|
|
62
|
+
- `src/tools/spawn-config.ts` — `resolveSpawnConfig(params, registry, modelInfo, settings)`.
|
|
63
|
+
Pure function returning `ResolvedSpawnConfig | SpawnConfigError`.
|
|
64
|
+
Lines 80-83 resolve the type:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const rawType = params.subagent_type as SubagentType;
|
|
68
|
+
const resolved = registry.resolveType(rawType);
|
|
69
|
+
const subagentType = resolved ?? "general-purpose";
|
|
70
|
+
const fellBack = resolved === undefined;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- `src/tools/helpers.ts` — `buildTypeListText(registry, agentDir)` and the `TypeListRegistry` interface (extends `AgentConfigLookup`, so `resolveAgentConfig` is already available on it).
|
|
74
|
+
- `src/tools/agent-tool.ts` — `AgentTool.execute` already short-circuits on a config error: `if ("error" in config) return textResult(config.error);`.
|
|
75
|
+
So returning `SpawnConfigError` from `resolveSpawnConfig` is sufficient to surface the message to the model with no further wiring.
|
|
76
|
+
|
|
77
|
+
AGENTS.md / package constraints that apply:
|
|
78
|
+
|
|
79
|
+
- Pi-subagents is a minimal core with no policy enforcement.
|
|
80
|
+
This fix is **not** policy — it enforces the agent's own `enabled` flag, which is part of the registry's documented contract, not a cross-cutting tool/skill restriction (those live in `@gotgenes/pi-permission-system`).
|
|
81
|
+
- Biome handles formatting; no Prettier.
|
|
82
|
+
|
|
83
|
+
## Design Overview
|
|
84
|
+
|
|
85
|
+
Two localized, independent changes.
|
|
86
|
+
Neither introduces a new collaborator, widens a dependency bag, or changes layer wiring, so the design-review structural checklist finds nothing to flag.
|
|
87
|
+
|
|
88
|
+
### 1. Spawn-path gate (explicit error for disabled types)
|
|
89
|
+
|
|
90
|
+
In `resolveSpawnConfig`, after resolving the canonical key, gate on `enabled` before falling back.
|
|
91
|
+
The check reuses the registry's existing `isValidType`, which already encodes "exists and enabled":
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const rawType = params.subagent_type as SubagentType;
|
|
95
|
+
const resolved = registry.resolveType(rawType);
|
|
96
|
+
|
|
97
|
+
// A known-but-disabled type is an explicit error, not a silent fallback.
|
|
98
|
+
if (resolved !== undefined && !registry.isValidType(resolved)) {
|
|
99
|
+
return { error: `Agent type "${resolved}" is disabled` };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const subagentType = resolved ?? "general-purpose";
|
|
103
|
+
const fellBack = resolved === undefined;
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Decision model:
|
|
107
|
+
|
|
108
|
+
| `subagent_type` input | `resolveType` | `isValidType(resolved)` | Outcome |
|
|
109
|
+
| ------------------------- | ------------- | ----------------------- | -------------------------------------------------------------- |
|
|
110
|
+
| enabled agent (`Explore`) | `"Explore"` | `true` | resolves, spawns (unchanged) |
|
|
111
|
+
| disabled agent (`Plan`) | `"Plan"` | `false` | error: `Agent type "Plan" is disabled` |
|
|
112
|
+
| unknown (`foo`) | `undefined` | not reached | falls back to `general-purpose`, `fellBack = true` (unchanged) |
|
|
113
|
+
|
|
114
|
+
`isValidType(resolved)` re-runs `resolveKey` on an already-canonical key — a negligible, correct lookup.
|
|
115
|
+
Because the gate runs first, `resolveAgentConfig(subagentType)` downstream is only ever reached for enabled or unknown types, so its disabled-config behavior (kept for the UI) is never exercised on the spawn path.
|
|
116
|
+
|
|
117
|
+
This keeps the gate at the spawn boundary and leaves `resolveType` and `resolveAgentConfig` untouched, preserving every UI consumer that relies on resolving disabled agents.
|
|
118
|
+
|
|
119
|
+
### 2. Tool-description list filter
|
|
120
|
+
|
|
121
|
+
In `buildTypeListText`, filter both name lists by `enabled` before rendering.
|
|
122
|
+
`resolveAgentConfig(name)` returns the disabled config (with `enabled === false`) for a disabled name, so the predicate is straightforward:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const isEnabled = (name: string) => registry.resolveAgentConfig(name).enabled !== false;
|
|
126
|
+
const defaultNames = registry.getDefaultAgentNames().filter(isEnabled);
|
|
127
|
+
const userNames = registry.getUserAgentNames().filter(isEnabled);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This localizes the fix to the one consumer that needs it, preserves the `getDefaultAgentNames` / `getUserAgentNames` method semantics (they still answer "which names are default / user"), and makes the tool description consistent with the `subagent_type` available-types line (both now exclude disabled agents).
|
|
131
|
+
|
|
132
|
+
`TypeListRegistry` already extends `AgentConfigLookup`, so `resolveAgentConfig` is in scope; no interface change.
|
|
133
|
+
|
|
134
|
+
## Module-Level Changes
|
|
135
|
+
|
|
136
|
+
- `src/tools/spawn-config.ts` — add the disabled-type gate in `resolveSpawnConfig` (3 lines) before the `subagentType` / `fellBack` assignment.
|
|
137
|
+
No signature or return-type change (`SpawnConfigError` already supported).
|
|
138
|
+
- `src/tools/helpers.ts` — add the `isEnabled` predicate and `.filter(isEnabled)` to the two name lists in `buildTypeListText`.
|
|
139
|
+
No signature or interface change.
|
|
140
|
+
- `src/config/agent-types.ts` — no code change.
|
|
141
|
+
The inline comment "Disabled agents are kept but excluded from spawning" becomes accurate without edit; the `resolveType` JSDoc already says "Returns the canonical key or undefined" (unchanged behavior).
|
|
142
|
+
|
|
143
|
+
No architecture-doc, ADR, or SKILL changes:
|
|
144
|
+
|
|
145
|
+
- `grep -rn "448" docs/` and the architecture roadmap show no reference to this issue → no roadmap/health-table update.
|
|
146
|
+
- No file is added, removed, or moved → no layout-listing update.
|
|
147
|
+
- The `package-pi-subagents` SKILL describes domains and phases, not the `resolveType`/`isValidType` mechanics, so the reworded-prose grep (`enabled`, `resolveType`, `spawning`) surfaces nothing that this fix invalidates.
|
|
148
|
+
- README is already consistent (see Non-Goals); no edit.
|
|
149
|
+
|
|
150
|
+
## Test Impact Analysis
|
|
151
|
+
|
|
152
|
+
This is a small behavior fix, not an extraction, so no existing tests become redundant.
|
|
153
|
+
|
|
154
|
+
1. **New tests enabled** — both target surfaces are pure and already directly tested:
|
|
155
|
+
- `resolveSpawnConfig` disabled-type error path (`test/tools/spawn-config.test.ts`).
|
|
156
|
+
- `buildTypeListText` disabled-exclusion path (`test/tools/helpers.test.ts`).
|
|
157
|
+
2. **Redundant tests** — none.
|
|
158
|
+
The existing `agent-types.test.ts` case "returns config for disabled type (no fallback for existing disabled)" stays as-is: it pins `resolveAgentConfig`'s disabled behavior, which the UI depends on and this fix deliberately does not change.
|
|
159
|
+
3. **Tests that must stay** — `agent-types.test.ts` `isValidType` "returns false for disabled agents" and `getAvailableTypes` "excludes disabled agents" already pin the registry primitives the gate reuses; keep them.
|
|
160
|
+
|
|
161
|
+
## Invariants at risk
|
|
162
|
+
|
|
163
|
+
This change touches the spawn-resolution and tool-description surfaces but does not regress any prior phase's documented outcome:
|
|
164
|
+
|
|
165
|
+
- `resolveAgentConfig` disabled-config behavior is pinned by `test/config/agent-types.test.ts` → "returns config for disabled type (no fallback for existing disabled)".
|
|
166
|
+
The plan leaves `resolveAgentConfig` unchanged; this test must stay green.
|
|
167
|
+
- The unknown-type fallback is pinned by `test/tools/spawn-config.test.ts` → "falls back to general-purpose for unknown agent type".
|
|
168
|
+
The gate runs only for `resolved !== undefined`, so the unknown path is untouched; this test must stay green.
|
|
169
|
+
- The UI's disabled-agent listing (`getAllTypes`) is pinned by `test/config/agent-types.test.ts` → `getAllTypes` "includes disabled agents"; unaffected.
|
|
170
|
+
|
|
171
|
+
## TDD Order
|
|
172
|
+
|
|
173
|
+
1. **Red→Green: spawn-path disabled error.**
|
|
174
|
+
Test surface: `test/tools/spawn-config.test.ts`, `resolveSpawnConfig — type resolution` describe block.
|
|
175
|
+
Add two cases: (a) a registry with a disabled `Plan` override returns `{ error: 'Agent type "Plan" is disabled' }` for `subagent_type: "Plan"`; (b) case-insensitive input `subagent_type: "plan"` reports the canonical `"Plan"` in the message.
|
|
176
|
+
Build the registry with `new AgentTypeRegistry(() => new Map([["Plan", makeAgentConfig({ name: "Plan", enabled: false })]]))` (mirror the `agent-types.test.ts` fixture shape).
|
|
177
|
+
Then add the gate to `resolveSpawnConfig`.
|
|
178
|
+
Verify the existing "falls back to general-purpose for unknown agent type" and "resolves a known agent type" cases stay green (unknown/enabled paths unchanged).
|
|
179
|
+
Commit: `fix(pi-subagents): return an error when spawning a disabled agent type (#448)`.
|
|
180
|
+
|
|
181
|
+
2. **Red→Green: tool-description excludes disabled agents.**
|
|
182
|
+
Test surface: `test/tools/helpers.test.ts`, `buildTypeListText` describe block.
|
|
183
|
+
Add a case: a registry whose `defaults`/`users` include a name resolving to `{ enabled: false }` omits that name from the rendered list (assert `.not.toContain`).
|
|
184
|
+
Extend the `makeRegistry` stub's `resolve` to return `enabled` (the stub currently returns only `description`/`model`; add `enabled` so the predicate can read it).
|
|
185
|
+
Then add the `isEnabled` filter to `buildTypeListText`.
|
|
186
|
+
Verify the existing "lists default agents", "adds Custom agents section", and "omits Custom agents section" cases stay green (their stubs resolve to enabled configs by default).
|
|
187
|
+
Commit: `fix(pi-subagents): exclude disabled agents from the subagent tool description (#448)`.
|
|
188
|
+
|
|
189
|
+
3. **Verify: full check + suite.**
|
|
190
|
+
Run `pnpm --filter @gotgenes/pi-subagents run check`, `pnpm --filter @gotgenes/pi-subagents run lint`, `pnpm --filter @gotgenes/pi-subagents run test`, and `pnpm fallow dead-code`.
|
|
191
|
+
No new commit unless a fix is needed.
|
|
192
|
+
|
|
193
|
+
## Risks and Mitigations
|
|
194
|
+
|
|
195
|
+
- **Risk:** changing `resolveType` directly would break UI consumers (`agent-config-editor.ts`, `agent-menu.ts`) that intentionally resolve disabled agents.
|
|
196
|
+
**Mitigation:** the gate lives in `resolveSpawnConfig`, not in `resolveType`; `resolveType` and `resolveAgentConfig` are unchanged.
|
|
197
|
+
- **Risk:** an operator currently relying on the buggy behavior (explicitly spawning a disabled agent) gets an error after upgrade.
|
|
198
|
+
**Mitigation:** this is the documented, intended contract; the error message is explicit and actionable ("…is disabled"), and the operator can re-enable via the `/agents` menu or by removing `enabled: false`.
|
|
199
|
+
- **Risk:** the `buildTypeListText` filter could accidentally hide enabled agents if `resolveAgentConfig` returned a wrong config.
|
|
200
|
+
**Mitigation:** `resolveAgentConfig(name)` for a name already in the registry returns that exact config; the predicate only excludes `enabled === false`.
|
|
201
|
+
Existing `buildTypeListText` tests (which resolve to enabled configs) guard against over-filtering.
|
|
202
|
+
|
|
203
|
+
## Open Questions
|
|
204
|
+
|
|
205
|
+
- Should the README's `## For Extension Authors` or the agent-config table gain a one-line note that an explicit `subagent_type` for a disabled agent now errors?
|
|
206
|
+
Deferred — the README already states `enabled: false` disables the agent; the error message is self-explanatory.
|
|
207
|
+
Revisit only if a user asks why the spawn errored.
|
|
@@ -54,3 +54,52 @@ Test count went from 1058 to 1066 (+8).
|
|
|
54
54
|
- **Post-commit SKILL.md fix**: the pre-completion reviewer (WARN) flagged that `subagent-events-observer.ts` was missing from the Observation domain table; fixed in a follow-up `docs:` commit.
|
|
55
55
|
- **Pre-completion reviewer**: WARN (non-blocking).
|
|
56
56
|
Reviewer warning: SKILL.md Observation domain table listed 4 modules and omitted `subagent-events-observer.ts`; corrected before writing these notes.
|
|
57
|
+
|
|
58
|
+
## Stage: Final Retrospective (2026-06-17T18:30:00Z)
|
|
59
|
+
|
|
60
|
+
### Session summary
|
|
61
|
+
|
|
62
|
+
Shipped Phase 18 Step 2 across one continuous session (planning, TDD, ship, retro): five activity readers migrated off `AgentActivityTracker` onto `Subagent` record getters, released as `pi-subagents` v16.6.0.
|
|
63
|
+
Execution was clean overall (+8 tests, all deterministic checks green, one-pass CI), but the architecture roadmap's per-step completion marker was missed during TDD and only fixed after the user caught it at ship time.
|
|
64
|
+
|
|
65
|
+
### Observations
|
|
66
|
+
|
|
67
|
+
#### What went well
|
|
68
|
+
|
|
69
|
+
- **`ask_user` design-fork gate during planning paid off downstream.**
|
|
70
|
+
Resolving the two forks (finished-agent turn-count behavior change; `contextPercent` field-vs-method) up front meant TDD had zero design backtracking — every step landed as planned, including the `feat:`-vs-`refactor:` commit-type split the fork decided.
|
|
71
|
+
- **Lift-and-shift sequencing held.**
|
|
72
|
+
The prep step (`createTestSubagent` activity shorthands, commit `bcdb81c9`) made the four reader-migration steps mechanical; each shared-signature change (`renderWidgetLines`, `buildDetails`, `NotificationSystem`) landed with all its call sites in one commit, so `pnpm run check` stayed green at every step boundary.
|
|
73
|
+
|
|
74
|
+
#### What caused friction (agent side)
|
|
75
|
+
|
|
76
|
+
- `instruction-violation` (user-caught) — the architecture roadmap's **per-step** completion marker was not applied during TDD.
|
|
77
|
+
Step 7 of `/tdd-plan` says "mark that step done (`✅`/`Landed:`) and update the phase status row."
|
|
78
|
+
I added the `Landed:` line (commit `999e5ecb`) and confirmed the phase status row correctly stayed `In progress`, but did **not** add the `✅` prefix to the step heading or the Mermaid diagram node.
|
|
79
|
+
The established convention (Step 1 carries `✅` on both its heading and its `S1[...]` diagram node) treats the `✅` markers as the completion signal, with `Landed:` an optional detail line.
|
|
80
|
+
Impact: the user caught it at ship time ("we didn't check off the step in architecture.md in the phase Steps section"); fixed in commit `47644ff1`, after which ship resumed.
|
|
81
|
+
Root cause: the prompt phrasing `(✅/Landed:)` reads as either/or, so satisfying `Landed:` felt sufficient.
|
|
82
|
+
- `missing-context` (reviewer gap) — the pre-completion reviewer's roadmap-status check (added in `b8a938d8` for exactly this class of miss) passed the unchecked step.
|
|
83
|
+
Its report said "Architecture doc has a `Landed:` entry for Step 2; Phase 18 status row correctly remains 'In progress'" — it verified the `Landed:` line and the phase row but never checked the per-step `✅` on the heading and diagram node.
|
|
84
|
+
Impact: the guard built to catch this exact omission did not, leaving the user as the only backstop.
|
|
85
|
+
- `other` (tool friction) — a multi-edit `Edit` batch on `test/ui/agent-widget.test.ts` (Step 2) was rejected for overlapping `oldText` regions; re-issued as a single non-overlapping edit.
|
|
86
|
+
Impact: one retry, no rework.
|
|
87
|
+
|
|
88
|
+
#### What caused friction (user side)
|
|
89
|
+
|
|
90
|
+
- None substantive.
|
|
91
|
+
The single user intervention (the architecture step-checkbox catch) was the correct backstop for a gap the automated reviewer should have caught; surfacing it as a redirect rather than a silent fix kept the convention enforced.
|
|
92
|
+
|
|
93
|
+
### Diagnostic details
|
|
94
|
+
|
|
95
|
+
- **Model-performance correlation** — the ship stage (CI watch, release-PR merge, issue close) ran on `opencode-go/deepseek-v4-flash`; appropriate for mechanical orchestration, and it executed the `UNSTABLE`-merge `GITHUB_TOKEN` branch correctly.
|
|
96
|
+
Planning/TDD ran on `anthropic/claude-opus-4-8` / `claude-sonnet-4-6` (judgment-heavy); retro on `claude-opus-4-8`.
|
|
97
|
+
No quality mismatch — the per-step-checkbox miss occurred during TDD on a high-reasoning model, so it is an instruction-clarity gap, not a model-capability gap.
|
|
98
|
+
- **Feedback-loop gap analysis** — `pnpm run check` ran after each shared-signature step as the plan flagged, and the full suite plus `fallow dead-code` ran before push; verification was incremental, not end-loaded.
|
|
99
|
+
- **Escalation-delay / unused-tool** — no `rabbit-hole` friction; no lens finding.
|
|
100
|
+
|
|
101
|
+
### Changes made
|
|
102
|
+
|
|
103
|
+
1. `.pi/prompts/tdd-plan.md` (step 7) — disambiguated roadmap-step completion: `✅` on both the step heading and its Mermaid diagram node; `Landed:` is not a substitute; phase status row flips only when every step is done.
|
|
104
|
+
2. `.pi/prompts/build-plan.md` (step 4) — same disambiguation as tdd-plan.
|
|
105
|
+
3. `.pi/agents/pre-completion-reviewer.md` (roadmap-status check) — reviewer now verifies `✅` on both the step heading and diagram node (not just a `Landed:` line) and checks the phase row against the actual step count.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 422
|
|
3
|
+
issue_title: "pi-subagents: delete AgentActivityTracker and ui-observer, drop the activity map from the core"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #422 — Delete AgentActivityTracker and ui-observer, drop the activity map from the core
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-06-17T00:00:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Planned Phase 18 Step 3 of the activity-tier disentanglement spine: deleting `AgentActivityTracker` and `ui-observer`, and removing `SubagentRuntime.agentActivity` plus the tracker wiring in the two spawn tools.
|
|
13
|
+
Verified the prerequisites (#420, #421) are both closed and that the trackers/map are now write-only dead state after the reader migration.
|
|
14
|
+
Wrote a four-step plan (two `refactor:` deletion commits, a module-delete commit, a `docs:` sweep) at `packages/pi-subagents/docs/plans/0422-delete-activity-tracker-ui-observer.md`.
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
- The change is **non-breaking** and internal-only: `AgentActivityTracker`, `ui-observer`, and `agentActivity` are absent from the public service surface (`service.ts`) and settings entry, so no `BREAKING CHANGE` footer.
|
|
19
|
+
Issue author is the operator (`gotgenes`) and the proposed change is unambiguous and roadmap-driven, so the `ask-user` gate was skipped.
|
|
20
|
+
- The foreground `observer.onSessionCreated` callback **stays** — it is still the only place `recordRef`/`fgId` bind mid-flight and where `widget.ensureTimer()` fires; only the tracker lines are stripped.
|
|
21
|
+
The background `observer` block, by contrast, did only tracker work and is removed entirely.
|
|
22
|
+
- Commit ordering matters: Step 1 (spawners stop passing `agentActivity`) must precede Step 2 (remove the runtime field), or the build breaks.
|
|
23
|
+
Both the param removal and the field/`AgentActivityAccess` removal cascade to call sites and tests at the type level, so each is folded into a single commit.
|
|
24
|
+
- Re-render cadence: dropping `subscribeUIObserver` removes event-driven foreground re-renders, leaving the existing 80 ms spinner poll.
|
|
25
|
+
Content is identical within ≤80 ms (the poll reads the same record the core observer populates) — pinned by the streaming-`onUpdate` test, noted as a risk not a regression.
|
|
26
|
+
- Found a **pre-existing stale doc** from #421: `architecture.md` still says "the widget reads agent state by polling a shared `Map<string, AgentActivityTracker>`", though #421 already moved the widget onto records.
|
|
27
|
+
Folded that correction into this plan's Step 4 doc sweep alongside the file tree, two Mermaid diagrams, and the SKILL.md domain counts (UI `12 → 10`, header `59 → 57` files).
|
|
28
|
+
- Confirmed no orphaned sibling exports: `SessionLike` (used by `subagent-session.ts`) and `SubscribableSession` (used by `record-observer.ts`, `subagent-session.ts`, `types.ts`) both survive the module deletion; `pnpm fallow dead-code` is the Step 3 backstop.
|
|
29
|
+
|
|
30
|
+
## Stage: Implementation — TDD (2026-06-17T20:40:00Z)
|
|
31
|
+
|
|
32
|
+
### Session summary
|
|
33
|
+
|
|
34
|
+
Executed all four planned steps as a deletion refactor: stripped tracker wiring + the `agentActivity` parameter from the spawners, removed the activity map from `SubagentRuntime`/`AgentToolRuntime`, deleted `agent-activity-tracker.ts` and `ui-observer.ts` (−145 LOC) plus their suites, and swept the architecture doc + SKILL.md.
|
|
35
|
+
Landed in six commits (four planned + one folded test removal + one `style:` lint fixup).
|
|
36
|
+
Test count dropped −34 (1066 → 1032) across 63 files (was 65); `check`, root `lint`, full `test`, and `fallow dead-code` all green.
|
|
37
|
+
|
|
38
|
+
### Observations
|
|
39
|
+
|
|
40
|
+
- **Deviation (test removal moved earlier):** the agent-tool "registers activity in agentActivity map" test was planned for Step 2 but had to be removed in Step 1 — once the spawner stops populating the map, the test fails at runtime in that commit.
|
|
41
|
+
Folded into Step 1 per the testing skill's "account for tests that break" rule.
|
|
42
|
+
- **Deviation (atomic-batch trap):** the Step 2 multi-edit `Edit` on `runtime.ts` was rejected because edit[1] miscounted a decorative `─` rule, which silently dropped edit[0] (the `AgentActivityTracker` import removal).
|
|
43
|
+
`tsc` passed at Step 2 because the leftover was an elided `import type`; it only surfaced as a tsc/fallow error once Step 3 deleted the module.
|
|
44
|
+
Removed it in Step 3 and re-read the region after editing.
|
|
45
|
+
This is exactly the AGENTS.md warning about anchoring on decorative rules.
|
|
46
|
+
- **Lint fixup:** an unused `runtime` destructure remained in one `background-spawner.test.ts` case.
|
|
47
|
+
It belongs to Step 1's file but HEAD was the `docs:` commit (a fixup must not land in a `docs:` commit, and amending a non-HEAD `refactor:` commit needs a rebase), so it landed as a standalone `style:` commit.
|
|
48
|
+
- **No behavior regression:** foreground re-renders now rely solely on the 80 ms spinner poll (the second `subscribeUIObserver` subscription is gone); pinned by the surviving "calls onUpdate with streaming details while running" test.
|
|
49
|
+
- **Doc correction:** fixed the pre-existing stale `architecture.md` prose that still claimed the widget polls a `Map<string, AgentActivityTracker>` (the widget moved onto records in #421); now reads "polls the records exposed via `SubagentManager.listAgents()`".
|
|
50
|
+
- **Pre-completion reviewer: PASS** — all deterministic checks, code-design, test-artifact, Mermaid (`mmdc` parsed all 6 blocks), dead-code, and cross-step-invariant lenses passed; no warnings.
|
|
51
|
+
|
|
52
|
+
## Stage: Final Retrospective (2026-06-18T01:19:58Z)
|
|
53
|
+
|
|
54
|
+
### Session summary
|
|
55
|
+
|
|
56
|
+
Shipped Phase 18 Step 3 across plan → TDD → ship in three sessions: deleted `AgentActivityTracker` and `ui-observer`, removed the `agentActivity` map from `SubagentRuntime` and both spawn tools (−145 LOC, −34 tests), and swept the architecture doc + SKILL.md.
|
|
57
|
+
Six implementation commits, pre-completion reviewer PASS, CI green, issue closed; no release (all `refactor:`/`style:`/`docs:`).
|
|
58
|
+
Clean hands-off execution — the only user input was one `ask_user` answer ("Release now") and no corrections.
|
|
59
|
+
|
|
60
|
+
### Observations
|
|
61
|
+
|
|
62
|
+
#### What went well
|
|
63
|
+
|
|
64
|
+
- Incremental verification cadence was exemplary: `pnpm run check` ran after every shared-type change (after Step 1, Step 2, and twice in Step 3), not just at end-of-cycle — exactly the feedback-loop discipline the TDD prompt asks for.
|
|
65
|
+
This caught the Step 2/3 type fallout immediately rather than as a late surprise.
|
|
66
|
+
- The atomic-batch trap (below) was self-identified and the recovery was clean and well-documented — the Step 3 commit body explains why the `runtime.ts` import removal landed there instead of Step 2.
|
|
67
|
+
|
|
68
|
+
#### What caused friction (agent side)
|
|
69
|
+
|
|
70
|
+
- `instruction-violation` (self-identified) — the Step 2 multi-edit `Edit` on `runtime.ts` anchored `edits[1]` on a decorative `─` rule and miscounted it, rejecting the whole atomic batch and silently dropping `edits[0]` (the `AgentActivityTracker` import removal).
|
|
71
|
+
This is the exact anti-pattern AGENTS.md § "Edit tool batches" warns against ("anchor on adjacent unique code lines rather than the rule itself").
|
|
72
|
+
The follow-up rule ("after a rejection, re-apply every intended edit and run `pnpm run check`") was also only half-followed: the field edit was re-applied but the import edit was not, and the `check` at Step 2 passed anyway because `tsc` elides an unused `import type`.
|
|
73
|
+
Impact: the dropped import surfaced only at Step 3 (once the module was deleted) as a tsc/fallow error, costing one investigation cycle and smearing the import removal into the Step 3 commit instead of Step 2.
|
|
74
|
+
- `other` (self-identified) — dropping the `runtime.agentActivity` argument from the `background-spawner.test.ts` calls left one test still destructuring an unused `runtime`.
|
|
75
|
+
Biome's `noUnusedVariables` is warning-level (exit 0), so it did not fail `lint`; it was caught by comparing the warning count to the green baseline at end-of-cycle.
|
|
76
|
+
Impact: one extra `style:` commit (could not amend — HEAD was a `docs:` commit).
|
|
77
|
+
|
|
78
|
+
#### What caused friction (user side)
|
|
79
|
+
|
|
80
|
+
- None blocking — execution was hands-off.
|
|
81
|
+
Opportunity, not criticism: the ship stage ran on `opencode-go/deepseek-v4-flash`, a weak model, and that stage carries a real judgment call (is a missing release-please PR expected, or a problem?).
|
|
82
|
+
It was answered correctly here, but the release-decision judgment on a weak model is a latent risk worth an operator's awareness.
|
|
83
|
+
|
|
84
|
+
### Diagnostic details
|
|
85
|
+
|
|
86
|
+
- **Model-performance correlation** — Planning and TDD ran on `anthropic/claude-opus-4-8` (appropriate: design + commit-sequencing judgment, deviation recovery).
|
|
87
|
+
The pre-completion reviewer subagent ran to completion (245 s, 26 tool uses) and returned a thorough PASS.
|
|
88
|
+
Ship ran on `opencode-go/deepseek-v4-flash` — fine for the mechanical push/CI/close flow, but it also made the "no release expected" inference; correct here, latent risk in general (see user-side note).
|
|
89
|
+
- **Escalation-delay tracking** — No `rabbit-hole`s.
|
|
90
|
+
Both deviations resolved within 1–2 tool calls; no sequence exceeded 5 calls on one error.
|
|
91
|
+
- **Unused-tool detection** — No `missing-context` gaps. `grep` (not `colgrep`) was used throughout planning, correctly — every search was an exact symbol match (`agentActivity`, `AgentActivityTracker`, `subscribeUIObserver`), which is grep's lane per the colgrep decision table.
|
|
92
|
+
- **Feedback-loop gap analysis** — No gap; verification was incremental, not end-only (see "What went well").
|
|
93
|
+
The one escape (`import type` removal) is a tsc-tolerance gap, not a cadence gap — `check` ran on schedule but cannot flag an unused type import.
|
|
94
|
+
|
|
95
|
+
### Changes made
|
|
96
|
+
|
|
97
|
+
1. `AGENTS.md` § "Edit tool batches" — augmented the post-rejection rule with a caveat: `tsc` passes on a dropped `import type` removal (an unused type import is not an error), so re-read the affected region rather than trusting `pnpm run check` alone.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 423
|
|
3
|
+
issue_title: "pi-subagents: make the agent widget self-drive from lifecycle events"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #423 — Make the agent widget self-drive from lifecycle events
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-06-17T00:00:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Planned Phase 18 Step 4 of the widget/tool decoupling track: making `AgentWidget` a `SubagentManagerObserver` that self-drives its 80 ms timer from lifecycle notifications, wired via a new `CompositeSubagentObserver` fan-out, and removing all inbound widget calls from the spawn tools.
|
|
13
|
+
Wrote a four-step plan (three `refactor:` commits + a `docs:` sweep) at `packages/pi-subagents/docs/plans/0423-widget-self-drive-from-lifecycle.md`.
|
|
14
|
+
|
|
15
|
+
### Observations
|
|
16
|
+
|
|
17
|
+
- **Wiring mechanism was the live design decision.**
|
|
18
|
+
`SubagentManager` has a single `observer` slot.
|
|
19
|
+
Three options surfaced: (A) a `CompositeSubagentObserver` fan-out in `index.ts`, (B) make the manager hold an observer list, (C) subscribe the widget to the public `pi.events` channels.
|
|
20
|
+
The operator initially leaned toward B (matching the issue's literal file list) but was unsure; after reframing around the decouple + overridable-UI north star, they chose **A**.
|
|
21
|
+
Rationale recorded in the plan: A keeps the core closed for modification, B moves fan-out *into* the core (wrong direction), and C front-runs the Step 6 ([#425]) public-event-contract reconciliation.
|
|
22
|
+
Key insight that flattened the decision: all three options keep the widget's `manager.listAgents()` reference, so they only change the *trigger*, not the data source — full broadcast-plus-query decoupling is the Step 8 ([#427]) concern.
|
|
23
|
+
- **`markFinished` is fully redundant** and is deleted, not relocated: `seedFinishedAgents()` (added in [#421] / [#422]) already seeds any agent with `completedAt` on each poll tick.
|
|
24
|
+
This matters because the manager **never fires `onSubagentCompleted` for foreground agents** (`onRunFinished` guards on `isBackground`), so the widget could not learn of foreground completion via the observer anyway — polling covers it.
|
|
25
|
+
- **Construction cycle** (widget needs manager → manager needs observer → observer includes widget) is broken by constructing the composite with `[eventsObserver]`, passing it to the manager, then `observer.add(widget)` after the widget is built; the manager consults the observer only lazily at spawn time.
|
|
26
|
+
- **TDD ordering avoids a behavior gap:** Step 2 wires the widget as an observer *while the spawn tools still drive it* (idempotent double-drive), strictly before Step 3 removes the spawn-tool calls — so no commit leaves the widget without a timer-start signal.
|
|
27
|
+
The new widget observer methods need no `fallow-ignore` because they are invoked polymorphically through `SubagentManagerObserver` (the `SubagentEventsObserver` precedent).
|
|
28
|
+
- This step narrows `AgentToolWidget` to `setUICtx` only but keeps the `AgentTool` widget constructor param; full removal is Step 5 ([#424]).
|
|
29
|
+
- Non-breaking and internal-only (no public service/settings surface touched), so `refactor:`/`docs:` commits, no `BREAKING CHANGE` footer.
|
|
30
|
+
|
|
31
|
+
## Stage: Implementation — TDD (2026-06-17T10:00:00Z)
|
|
32
|
+
|
|
33
|
+
### Session summary
|
|
34
|
+
|
|
35
|
+
Executed the four-step plan exactly as written: added `CompositeSubagentObserver` (Step 1), made `AgentWidget` a `SubagentManagerObserver` and wired the composite in `index.ts` (Step 2), removed all spawn-tool widget wiring and deleted `markFinished` (Step 3), then swept the architecture doc + SKILL.md (Step 4).
|
|
36
|
+
Four commits (three `refactor:`, one `docs:`); test count `1032 → 1039` (+7 composite, +4 widget observer, −4 removed spawner/fixture widget-driving tests).
|
|
37
|
+
`check`, root `lint`, full `test`, and `fallow dead-code` all green; pre-completion reviewer returned PASS.
|
|
38
|
+
|
|
39
|
+
### Observations
|
|
40
|
+
|
|
41
|
+
- **Mid-step correction (not a separate commit):** in Step 2 I initially over-reached by deleting `markFinished` and privatizing `ensureTimer`, which breaks `index.ts` typing because `AgentToolWidget` still required those methods until Step 3.
|
|
42
|
+
Caught it before committing and reverted both to Step 3 per the plan; Step 2 kept them public.
|
|
43
|
+
The plan's ordering (narrow `AgentToolWidget` and delete `markFinished` only in the atomic Step 3 removal) was correct — the lesson is to trust the step boundaries.
|
|
44
|
+
- **Biome `useIterableCallbackReturn` false trigger:** naming the composite's private fan-out helper `forEach` made Biome treat the call as `Array.prototype.forEach` and reject the value-returning arrow.
|
|
45
|
+
Renamed it to `dispatch` — a strictly better name anyway.
|
|
46
|
+
- **`vi.getTimerCount()` cleanly proves the timer started:** the widget observer tests assert `getTimerCount()` goes `0 → 1` on `onSubagentStarted`/`onSubagentCreated`, distinguishing `startLoop` (ensureTimer + render) from a bare `update()` (render only), with the manager-stub creating no other timers.
|
|
47
|
+
- **No dead-code window:** the widget's new observer methods are invoked polymorphically through `SubagentManagerObserver` (the `SubagentEventsObserver` precedent), so `fallow` saw them as used from Step 2 onward; `ensureTimer` became `private` in Step 3, dropping its now-stale `fallow-ignore`.
|
|
48
|
+
- **The Step 2 transient double-drive was harmless** as predicted: both the spawn tools and the composite drove the widget in that commit, and every driven method is idempotent — full suite green at that commit.
|
|
49
|
+
- **Reviewer notes (non-blocking, both PASS):** a pre-existing "six domains" vs "seven domains" inconsistency in `architecture.md` (Phase 17, out of scope, left alone); and `observer.add(widget)` is a justified post-construction write documented as the only construction-cycle break (widget needs manager, manager needs observer).
|
|
50
|
+
- **Pre-completion reviewer: PASS** — all deterministic checks, code-design, test-artifact, Mermaid (`mmdc` parsed all 6 blocks), dead-code, and cross-step-invariant lenses passed.
|
|
51
|
+
|
|
52
|
+
## Stage: Final Retrospective (2026-06-17T18:30:00Z)
|
|
53
|
+
|
|
54
|
+
### Session summary
|
|
55
|
+
|
|
56
|
+
Shipped Phase 18 Step 4 across plan → TDD → ship in a single session: the `AgentWidget` became a `SubagentManagerObserver` that self-drives its 80 ms timer through a new `CompositeSubagentObserver` fan-out, and the spawn tools shed every widget call.
|
|
57
|
+
Four implementation commits (three `refactor:`, one `docs:`), pre-completion reviewer PASS, CI green, issue closed; no release (all commits non-releasing).
|
|
58
|
+
Execution was clean — two self-caught agent-side slips, no rework commits, and the only user input was one productive redirect plus a release-timing answer.
|
|
59
|
+
|
|
60
|
+
### Observations
|
|
61
|
+
|
|
62
|
+
#### What went well
|
|
63
|
+
|
|
64
|
+
- **Two-round `ask_user` on the wiring decision (planning).**
|
|
65
|
+
The first round offered three mechanisms (composite / manager-list / `pi.events`); the operator picked B but flagged uncertainty.
|
|
66
|
+
Reframing around the decouple + overridable-UI north star and re-asking flipped the choice to A (`CompositeSubagentObserver`) with explicit buy-in.
|
|
67
|
+
The reframe surfaced the insight that flattened the decision: all three options keep the widget's `manager.listAgents()` reference, so they change only the *trigger*, not the data source.
|
|
68
|
+
- **`vi.getTimerCount()` as a precise timer-start assertion.**
|
|
69
|
+
The widget observer tests assert `getTimerCount()` goes `0 → 1` on `onSubagentStarted`/`onSubagentCreated`, cleanly distinguishing `startLoop` (timer + render) from a bare `update()` (render only).
|
|
70
|
+
- **"Double-drive" lift-and-shift ordering.**
|
|
71
|
+
Step 2 wired the new observer while the spawn tools still drove the widget (idempotent overlap), and Step 3 removed the old path — no commit left the widget without a timer-start signal, and the full suite was green at every commit.
|
|
72
|
+
- **Incremental verification cadence.**
|
|
73
|
+
`pnpm run check` after each shared-type change, `vitest` per-file in red/green, `mmdc` on all 6 Mermaid blocks, and `fallow`/`lint`/full-suite at the end of Step 3 — no end-only verification.
|
|
74
|
+
|
|
75
|
+
#### What caused friction (agent side)
|
|
76
|
+
|
|
77
|
+
- `scope-drift` (self-identified) — in TDD Step 2 I deleted `markFinished` and privatized `ensureTimer` ahead of schedule; both belonged to the atomic Step 3 removal because `AgentToolWidget` still required them until then (deleting early breaks `index.ts` typing).
|
|
78
|
+
Caught by reasoning before committing and reverted to Step 3.
|
|
79
|
+
Impact: a few corrective edits, no extra commit, no rework.
|
|
80
|
+
Lesson: re-read the step's exact scope before editing, rather than editing from the overall design held in memory.
|
|
81
|
+
- `missing-context` (self-identified) — naming the composite's private fan-out helper `forEach` tripped Biome's `useIterableCallbackReturn` (it treated the call as `Array.prototype.forEach` and rejected the value-returning arrow).
|
|
82
|
+
Renamed to `dispatch`.
|
|
83
|
+
Impact: one rename, caught immediately by the `pi-autoformat` hook; no rework.
|
|
84
|
+
- `other` — the ship stage asked the batch-vs-release question for a multi-issue sequence, but every unreleased commit was `refactor:`/`docs:`, so release-please produced no PR regardless of the answer.
|
|
85
|
+
The operator's "release now" answer was therefore unactionable (the work auto-batches until a `feat`/`fix` lands).
|
|
86
|
+
Impact: one unactionable `ask_user` round; correct outcome reached, but the question implied a choice that did not exist.
|
|
87
|
+
|
|
88
|
+
#### What caused friction (user side)
|
|
89
|
+
|
|
90
|
+
- None blocking — the planning redirect ("I'm not confident in my choice… the goal is to decouple… an event system would make sense but isn't on the roadmap") was the ideal intervention: a redirecting question that handed over the north star instead of a post-hoc correction, and it directly produced the better design.
|
|
91
|
+
|
|
92
|
+
### Diagnostic details
|
|
93
|
+
|
|
94
|
+
- **Model-performance correlation** — Planning ran on `anthropic/claude-opus-4-8` (design + the two-round wiring `ask_user`; appropriate).
|
|
95
|
+
TDD ran on `anthropic/claude-sonnet-4-6` (implementation; handled the Step 2 self-correction and the Biome rename; adequate).
|
|
96
|
+
The `pre-completion-reviewer` subagent ran on `anthropic/claude-sonnet-4-6` (its frontmatter) and returned a thorough PASS.
|
|
97
|
+
Ship ran on `opencode-go/deepseek-v4-flash` — a reasoning-weak model carrying the batch-vs-release judgment and the "no release PR expected" inference.
|
|
98
|
+
It reached the correct outcome, but this is the same latent risk the #422 retro flagged: a weak model on the one ship-stage judgment call.
|
|
99
|
+
Proposal 1 mitigates it by making the release-trigger check deterministic so the stage needs no judgment.
|
|
100
|
+
- **Escalation-delay tracking** — no `rabbit-hole`s; both agent-side slips resolved in one tool call each (one rename, one revert); no sequence exceeded 5 calls on one error.
|
|
101
|
+
- **Unused-tool detection** — no `missing-context` gaps requiring a subagent; `grep` (not `colgrep`) was used in planning, correctly — every search was an exact symbol match (`ensureTimer`, `markFinished`, `observer`).
|
|
102
|
+
- **Feedback-loop gap analysis** — no gap; verification was incremental, not end-only (see "What went well").
|
|
103
|
+
|
|
104
|
+
### Changes made
|
|
105
|
+
|
|
106
|
+
1. `.pi/prompts/ship-issue.md` — step 4b now opens with a release-trigger gate: check `git log --oneline <last-tag>..HEAD`, and when every commit is a non-releasing type (`refactor:`/`docs:`/`style:`/`chore:`/`test:`), state that release-please cuts nothing now and skip the batch-vs-release question.
|
|
107
|
+
|
|
108
|
+
[#421]: https://github.com/gotgenes/pi-packages/issues/421
|
|
109
|
+
[#422]: https://github.com/gotgenes/pi-packages/issues/422
|
|
110
|
+
[#424]: https://github.com/gotgenes/pi-packages/issues/424
|
|
111
|
+
[#425]: https://github.com/gotgenes/pi-packages/issues/425
|
|
112
|
+
[#427]: https://github.com/gotgenes/pi-packages/issues/427
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 424
|
|
3
|
+
issue_title: "pi-subagents: drop the widget and activity-map dependencies from the subagent tool"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #424 — pi-subagents: drop the widget and activity-map dependencies from the subagent tool
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-06-18T15:01:42Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Planned Phase 18 Step 5: dropping the `widget` constructor dependency from `AgentTool` and shedding the widget stub from `createToolDeps`.
|
|
13
|
+
Verified against current `main` that the `agentActivity` / activity-map dependency named in the issue and roadmap was already removed in Phase 18 Step 3 ([#422]) — only `widget` remains to drop, so the plan corrects that stale wording.
|
|
14
|
+
Produced `docs/plans/0424-drop-widget-dep-from-subagent-tool.md` and committed it.
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
- The issue body and the architecture roadmap's Step 5 entry both still say `agentActivity`, but `grep` found no live `agentActivity`/`AgentActivity` references in `src/` — the only hit is a comment in `test/lifecycle/usage.test.ts`.
|
|
19
|
+
Flagged the roadmap Step 5 description for a stale-wording fix during implementation.
|
|
20
|
+
- This is a purely subtractive, non-breaking refactor: `AgentTool` is internal (public exports are only the service and settings entries), and `ToolStartHandler` already captures UICtx on every `tool_execution_start`, which fires before any tool's `execute`.
|
|
21
|
+
So removing the tool's own `setUICtx` call loses no behavior — `test/handlers/tool-start.test.ts` already pins UICtx capture on its true owner.
|
|
22
|
+
- Folded all edits into one refactor commit because removing the constructor parameter breaks the `index.ts:152` call site, the `make-deps.ts` fixture, and `agent-tool.test.ts` at typecheck time — they cannot land separately.
|
|
23
|
+
- Two obsolete tests to remove: `agent-tool.test.ts` → `"sets UI context on runtime at start of execute"` and `make-deps.test.ts` → `describe("widget defaults")`.
|
|
24
|
+
The `UICtx` type itself stays (used by `agent-widget.ts`, `tool-start.ts`, and the widget test) — only the `UICtx` import in `agent-tool.ts` and the `AgentToolWidget` interface go.
|
|
25
|
+
- Skipped the `ask-user` gate: operator-authored issue (`gotgenes`), unambiguous proposed change following an established roadmap, clearly non-breaking.
|
|
26
|
+
|
|
27
|
+
## Stage: Implementation — TDD (2026-06-18T15:10:13Z)
|
|
28
|
+
|
|
29
|
+
### Session summary
|
|
30
|
+
|
|
31
|
+
Executed the two-step plan: one `refactor:` commit dropping the `widget` constructor dependency from `AgentTool` (plus the `UICtx` import, `AgentToolWidget` interface, the redundant `setUICtx` call, the `index.ts` call site, the `createToolDeps` field/stub, and two obsolete tests), and one `docs:` commit marking Phase 18 Step 5 complete in the architecture doc.
|
|
32
|
+
Test count went from 1039 to 1037 (−2 removed obsolete tests); `check`, root `lint`, full `test`, and `fallow dead-code` all green.
|
|
33
|
+
|
|
34
|
+
### Observations
|
|
35
|
+
|
|
36
|
+
- The refactor was purely subtractive and folded all src + test edits into one commit, as the plan predicted — the constructor-signature change breaks `index.ts`, `make-deps.ts`, and `agent-tool.test.ts` at typecheck simultaneously.
|
|
37
|
+
- Renamed the unused `execute` `ctx` parameter to `_ctx` rather than removing it — the inner `defineTool` `execute` closure must keep the 5-arg signature, so the method keeps the positional slot but ignores it.
|
|
38
|
+
- Corrected the architecture doc's Step 5 `Outcome` from "fixture drops 2 fields" to "drops 1 field" and noted the `agentActivity` half was already done in [#422] — the plan flagged this stale wording in advance.
|
|
39
|
+
- Pre-completion reviewer: WARN (1 non-blocking finding).
|
|
40
|
+
The `package-pi-subagents` SKILL test count was stale (994/63 "as of Phase 17 Step 4"); refreshed to 1037/64 "as of Phase 18 Step 5" in a follow-up `docs:` commit.
|
|
41
|
+
All cross-step invariants verified — UICtx capture stays pinned by `handlers/tool-start.test.ts`, and no `setUICtx` calls remain in spawn tools or the tool.
|
|
42
|
+
|
|
43
|
+
## Stage: Final Retrospective (2026-06-18T15:20:14Z)
|
|
44
|
+
|
|
45
|
+
### Session summary
|
|
46
|
+
|
|
47
|
+
Planned, implemented, and shipped Phase 18 Step 5 across three stages: a subtractive refactor dropping the `widget` constructor dependency from `AgentTool`, landed as one `refactor:` commit plus two `docs:` commits.
|
|
48
|
+
CI passed, `#424` closed; all commits since `pi-subagents-v16.6.0` are non-releasing (`refactor:`/`docs:`/`style:`), so release-please batches until a `feat`/`fix` lands.
|
|
49
|
+
Net test count 1039 → 1037 (−2 obsolete tests); the pre-completion reviewer returned WARN on a single stale-doc finding that was fixed inline.
|
|
50
|
+
|
|
51
|
+
### Observations
|
|
52
|
+
|
|
53
|
+
#### What went well
|
|
54
|
+
|
|
55
|
+
- Planning caught that the issue body and roadmap Step 5 both named an `agentActivity`/activity-map dependency that was already removed in [#422]; a `grep` of `src/` for `agentActivity` confirmed only `widget` remained.
|
|
56
|
+
The `/plan-issue` "treat the proposed change as a hypothesis, verify against current code" discipline worked exactly as intended and prevented planning around a phantom dependency.
|
|
57
|
+
- The plan correctly predicted the atomic-commit shape: removing the constructor parameter breaks `index.ts`, `make-deps.ts`, and `agent-tool.test.ts` at typecheck simultaneously, so the implementation folded all edits into one commit with zero rework.
|
|
58
|
+
- Verification ran incrementally — `pnpm run check` plus the affected test files after the edits, then the full suite + root `lint` + `fallow dead-code` at the end of TDD — so no feedback-loop gap.
|
|
59
|
+
- Clean handoff between stages: the planning retro entry flagged the stale `agentActivity` wording in advance, and the TDD stage acted on it without re-deriving the context.
|
|
60
|
+
|
|
61
|
+
#### What caused friction (agent side)
|
|
62
|
+
|
|
63
|
+
- `missing-context` (low impact, recurring) — the `package-pi-subagents` SKILL.md hardcoded test count (`994 tests across 63 files as of Phase 17 Step 4`) was stale by four issues.
|
|
64
|
+
The SKILL.md was edited by `docs:` commits in [#421], [#422], [#423], and [#424], yet none refreshed the count until the [#424] pre-completion reviewer flagged it.
|
|
65
|
+
Impact: one extra `docs:` commit (`18900d3f`) at ship time; no rework, but the precise count is structurally prone to per-step drift and the reviewer caught it only by chance at one of five Phase 18 steps.
|
|
66
|
+
|
|
67
|
+
#### What caused friction (user side)
|
|
68
|
+
|
|
69
|
+
- None.
|
|
70
|
+
The session was fully operator-authored and roadmap-driven; no user intervention or correction was needed at any stage.
|
|
71
|
+
|
|
72
|
+
### Diagnostic details
|
|
73
|
+
|
|
74
|
+
- **Model-performance correlation** — the `pre-completion-reviewer` subagent ran on `anthropic/claude-sonnet-4-6` (per its agent frontmatter), appropriate for judgment-heavy review; it produced a thorough, correctly-structured PASS/WARN report.
|
|
75
|
+
The parent session switched models several times (`opus-4-8` → `sonnet-4-6` → `deepseek-v4-flash` → `opus-4-8`); execution stayed clean throughout, so no model-quality mismatch was observable.
|
|
76
|
+
- **Escalation-delay tracking** — no `rabbit-hole` friction; no error sequence exceeded one tool call.
|
|
77
|
+
- **Unused-tool detection** — nothing notable; the work was fully specified by the plan and needed no exploratory dispatch.
|
|
78
|
+
- **Feedback-loop gap analysis** — verification was incremental (typecheck + affected tests per edit, full gates at end of TDD, root `lint` + `fallow` before push); no end-only verification gap.
|
|
79
|
+
|
|
80
|
+
### Changes made
|
|
81
|
+
|
|
82
|
+
1. Rewrote the opening sentence of the `## Testing` section in `.pi/skills/package-pi-subagents/SKILL.md` to drop both the hardcoded test count and the fork framing.
|
|
83
|
+
The operator judged the precise count (`1037 tests across 64 files as of Phase 18 Step 5`) to provide no real value and to be structurally prone to per-step drift — it had gone stale across four issues ([#421]–[#424]) before the [#424] reviewer caught it.
|
|
84
|
+
The fork is already introduced at the top of the skill, so the Testing section need not re-reference upstream; the sentence now reads "The package has an extensive `vitest` suite."
|
|
85
|
+
|
|
86
|
+
[#421]: https://github.com/gotgenes/pi-packages/issues/421
|
|
87
|
+
[#422]: https://github.com/gotgenes/pi-packages/issues/422
|
|
88
|
+
[#423]: https://github.com/gotgenes/pi-packages/issues/423
|
|
89
|
+
[#424]: https://github.com/gotgenes/pi-packages/issues/424
|