@gotgenes/pi-subagents 18.0.0 → 18.0.2
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 +14 -0
- package/README.md +22 -31
- package/docs/architecture/architecture.md +157 -204
- package/docs/architecture/history/phase-19-implement-ui-decisions.md +282 -0
- package/package.json +5 -6
- package/src/lifecycle/subagent.ts +5 -9
- package/src/observation/notification.ts +23 -10
- package/src/observation/subagent-events-observer.ts +1 -3
- package/src/tools/get-result-report.ts +60 -0
- package/src/tools/get-result-tool.ts +25 -40
- package/src/types.ts +1 -1
- package/src/ui/session-navigator.ts +0 -2
- package/.prettierignore +0 -5
- package/AGENTS.md +0 -7
- package/docs/plans/0048-implement-subagents-api.md +0 -304
- package/docs/plans/0049-remove-group-join-output-file-rpc.md +0 -180
- package/docs/plans/0051-update-adr-0001-hard-fork.md +0 -77
- package/docs/plans/0052-remove-scheduled-subagents.md +0 -133
- package/docs/plans/0053-extract-model-resolution-from-execute.md +0 -181
- package/docs/plans/0054-decompose-index-into-modules.md +0 -302
- package/docs/plans/0057-structured-debug-logging.md +0 -154
- package/docs/plans/0061-session-format-transcript.md +0 -284
- package/docs/plans/0066-replace-as-any-with-sdk-types.md +0 -215
- package/docs/plans/0069-create-subagent-runtime.md +0 -345
- package/docs/plans/0070-extract-event-handlers.md +0 -306
- package/docs/plans/0071-extract-session-config-assembler.md +0 -362
- package/docs/plans/0072-inject-agent-manager-collaborators.md +0 -329
- package/docs/plans/0076-inject-cwd-into-agent-manager.md +0 -102
- package/docs/plans/0077-inject-project-agents-dir.md +0 -115
- package/docs/plans/0080-consolidate-agent-config-lookup.md +0 -247
- package/docs/plans/0084-extract-git-worktree-manager.md +0 -142
- package/docs/plans/0087-evolve-subagent-runtime-methods.md +0 -240
- package/docs/plans/0098-extract-agent-record-state-machine.md +0 -435
- package/docs/plans/0099-replace-ctx-with-parent-snapshot.md +0 -488
- package/docs/plans/0100-replace-callback-threading-with-session-subscription.md +0 -454
- package/docs/plans/0102-consolidate-test-record-factory.md +0 -176
- package/docs/plans/0108-extract-agent-type-registry.md +0 -322
- package/docs/plans/0109-extract-settings-manager.md +0 -276
- package/docs/plans/0110-agent-activity-tracker.md +0 -297
- package/docs/plans/0111-split-agent-record-lifecycle.md +0 -582
- package/docs/plans/0112-replace-agent-manager-callbacks.md +0 -241
- package/docs/plans/0113-disambiguate-spawn-options.md +0 -155
- package/docs/plans/0114-narrow-agent-tool-menu-deps.md +0 -279
- package/docs/plans/0115-decompose-agent-tool.md +0 -337
- package/docs/plans/0116-type-housekeeping.md +0 -351
- package/docs/plans/0118-settings-manager-apply-methods.md +0 -271
- package/docs/plans/0123-remove-vi-fn-cast-smell.md +0 -179
- package/docs/plans/0131-consolidate-shared-test-fixtures.md +0 -207
- package/docs/plans/0132-inject-io-into-session-config.md +0 -219
- package/docs/plans/0133-inject-sdk-boundary-into-agent-runner.md +0 -373
- package/docs/plans/0134-reduce-as-any-casts.md +0 -366
- package/docs/plans/0135-extract-display-helpers.md +0 -182
- package/docs/plans/0136-decompose-agent-menu.md +0 -300
- package/docs/plans/0144-consolidate-observation-model.md +0 -263
- package/docs/plans/0145-decompose-execute-push-ctx-to-boundary.md +0 -290
- package/docs/plans/0146-narrow-ui-context.md +0 -319
- package/docs/plans/0147-inject-wrap-text-into-conversation-viewer.md +0 -166
- package/docs/plans/0148-split-agent-widget-rendering.md +0 -255
- package/docs/plans/0152-add-prompt-snippet.md +0 -91
- package/docs/plans/0164-reorganize-into-domain-directories.md +0 -409
- package/docs/plans/0165-decompose-resolved-spawn-config.md +0 -157
- package/docs/plans/0166-extract-parent-session-info.md +0 -231
- package/docs/plans/0167-narrow-runner-io.md +0 -150
- package/docs/plans/0168-extract-tool-filter-config.md +0 -173
- package/docs/plans/0169-extract-run-context-from-run-options.md +0 -194
- package/docs/plans/0170-reduce-build-content-lines-complexity.md +0 -225
- package/docs/plans/0171-reduce-render-result-complexity.md +0 -200
- package/docs/plans/0172-extract-turn-formatting.md +0 -206
- package/docs/plans/0180-reorder-append-prompt-for-kv-cache.md +0 -100
- package/docs/plans/0185-remove-persistent-agent-memory.md +0 -161
- package/docs/plans/0188-replace-any-casts-with-sdk-types.md +0 -162
- package/docs/plans/0192-define-session-context-interface.md +0 -107
- package/docs/plans/0193-runtime-owns-context-queries.md +0 -245
- package/docs/plans/0194-align-tool-interfaces-for-structural-typing.md +0 -139
- package/docs/plans/0195-convert-tool-factories-to-classes.md +0 -264
- package/docs/plans/0196-convert-runner-menu-to-classes.md +0 -268
- package/docs/plans/0205-decompose-render-widget-lines.md +0 -140
- package/docs/plans/0206-decompose-show-agent-detail.md +0 -211
- package/docs/plans/0207-decompose-agent-widget-update.md +0 -242
- package/docs/plans/0208-extract-shared-test-fixtures.md +0 -298
- package/docs/plans/0214-convert-remaining-closure-factories-to-classes.md +0 -261
- package/docs/plans/0215-decompose-build-parent-context.md +0 -166
- package/docs/plans/0216-decompose-start-agent.md +0 -255
- package/docs/plans/0217-extract-overwrite-guard.md +0 -176
- package/docs/plans/0218-push-sdk-boundary-in-settings.md +0 -172
- package/docs/plans/0219-reduce-test-duplication-top-3-clone-families.md +0 -162
- package/docs/plans/0227-evolve-agent-record-into-agent.md +0 -322
- package/docs/plans/0228-async-start-agent-dissolve-run-handle.md +0 -288
- package/docs/plans/0229-agent-born-complete.md +0 -564
- package/docs/plans/0230-extract-concurrency-queue.md +0 -265
- package/docs/plans/0231-push-exec-registry-to-runner.md +0 -245
- package/docs/plans/0232-agent-resume-internal-observer-lifecycle.md +0 -180
- package/docs/plans/0237-remove-disallowed-tools.md +0 -180
- package/docs/plans/0238-remove-extensions-filtering.md +0 -191
- package/docs/plans/0239-collapse-filter-active-tools.md +0 -217
- package/docs/plans/0242-rename-agent-tool-to-subagent.md +0 -165
- package/docs/plans/0256-extract-worktree-isolation.md +0 -256
- package/docs/plans/0257-extract-child-session-factory.md +0 -285
- package/docs/plans/0262-add-workspace-provider-seam.md +0 -264
- package/docs/plans/0264-remove-extension-lifecycle-control.md +0 -277
- package/docs/plans/0265-born-complete-subagent-session.md +0 -333
- package/docs/plans/0270-type-consumable-public-surface.md +0 -204
- package/docs/plans/0272-export-workspace-collaborator-types.md +0 -147
- package/docs/plans/0277-encapsulate-agent-session.md +0 -304
- package/docs/plans/0280-rename-agent-to-subagent.md +0 -197
- package/docs/plans/0360-custom-agents-default-append.md +0 -132
- package/docs/plans/0373-extract-subagent-state.md +0 -250
- package/docs/plans/0374-encapsulate-subagent-start-notification.md +0 -268
- package/docs/plans/0375-extract-run-listener-workspace-bracket.md +0 -300
- package/docs/plans/0376-extract-manager-observer.md +0 -232
- package/docs/plans/0377-split-widget-delegation-from-runtime.md +0 -254
- package/docs/plans/0378-consolidate-lifecycle-test-fixtures.md +0 -243
- package/docs/plans/0379-consolidate-ui-tools-test-fixtures.md +0 -335
- package/docs/plans/0380-extract-layered-settings-loader.md +0 -223
- package/docs/plans/0381-replace-concurrency-queue-with-limiter.md +0 -267
- package/docs/plans/0400-include-parent-prompt-in-replace-mode.md +0 -199
- package/docs/plans/0403-abort-subagents-on-interrupt.md +0 -180
- package/docs/plans/0412-unify-session-mock-builders.md +0 -146
- package/docs/plans/0420-fold-run-metrics-live-activity-onto-record.md +0 -228
- package/docs/plans/0421-migrate-activity-readers-to-record-getters.md +0 -309
- package/docs/plans/0422-delete-activity-tracker-ui-observer.md +0 -204
- package/docs/plans/0423-widget-self-drive-from-lifecycle.md +0 -264
- package/docs/plans/0424-drop-widget-dep-from-subagent-tool.md +0 -166
- package/docs/plans/0425-reconcile-subagent-events-contract.md +0 -144
- package/docs/plans/0426-consolidate-residual-test-clone-families.md +0 -235
- package/docs/plans/0427-reconsider-ui-direction.md +0 -212
- package/docs/plans/0441-remove-agent-definition-management-subtree.md +0 -180
- package/docs/plans/0442-dissolve-agents-conversation-viewer.md +0 -195
- package/docs/plans/0444-shrink-widget-to-background-agents.md +0 -156
- package/docs/plans/0445-native-session-navigation.md +0 -365
- package/docs/plans/0446-spike-session-navigation-entry-criteria.md +0 -171
- package/docs/plans/0447-extract-subagents-settings-command.md +0 -220
- package/docs/plans/0448-block-disabled-agent-spawn.md +0 -207
- package/docs/plans/0462-navigation-renderer-tui-components.md +0 -338
- package/docs/plans/0463-file-snapshot-source-evicted-agents.md +0 -306
- package/docs/retro/0048-implement-subagents-api.md +0 -44
- package/docs/retro/0049-remove-group-join-output-file-rpc.md +0 -49
- package/docs/retro/0051-update-adr-0001-hard-fork.md +0 -39
- package/docs/retro/0053-extract-model-resolution-from-execute.md +0 -40
- package/docs/retro/0054-decompose-index-into-modules.md +0 -53
- package/docs/retro/0057-structured-debug-logging.md +0 -77
- package/docs/retro/0061-session-format-transcript.md +0 -41
- package/docs/retro/0066-replace-as-any-with-sdk-types.md +0 -44
- package/docs/retro/0069-create-subagent-runtime.md +0 -43
- package/docs/retro/0070-extract-event-handlers.md +0 -49
- package/docs/retro/0071-extract-session-config-assembler.md +0 -60
- package/docs/retro/0072-inject-agent-manager-collaborators.md +0 -46
- package/docs/retro/0076-inject-cwd-into-agent-manager.md +0 -35
- package/docs/retro/0077-inject-project-agents-dir.md +0 -33
- package/docs/retro/0080-consolidate-agent-config-lookup.md +0 -35
- package/docs/retro/0084-extract-git-worktree-manager.md +0 -37
- package/docs/retro/0087-evolve-subagent-runtime-methods.md +0 -42
- package/docs/retro/0098-extract-agent-record-state-machine.md +0 -46
- package/docs/retro/0099-replace-ctx-with-parent-snapshot.md +0 -37
- package/docs/retro/0100-replace-callback-threading-with-session-subscription.md +0 -36
- package/docs/retro/0102-consolidate-test-record-factory.md +0 -30
- package/docs/retro/0108-extract-agent-type-registry.md +0 -41
- package/docs/retro/0109-extract-settings-manager.md +0 -55
- package/docs/retro/0110-agent-activity-tracker.md +0 -44
- package/docs/retro/0111-split-agent-record-lifecycle.md +0 -61
- package/docs/retro/0112-replace-agent-manager-callbacks.md +0 -35
- package/docs/retro/0113-disambiguate-spawn-options.md +0 -29
- package/docs/retro/0114-narrow-agent-tool-menu-deps.md +0 -38
- package/docs/retro/0115-decompose-agent-tool.md +0 -51
- package/docs/retro/0116-type-housekeeping.md +0 -42
- package/docs/retro/0118-settings-manager-apply-methods.md +0 -40
- package/docs/retro/0123-remove-vi-fn-cast-smell.md +0 -49
- package/docs/retro/0131-consolidate-shared-test-fixtures.md +0 -46
- package/docs/retro/0132-inject-io-into-session-config.md +0 -33
- package/docs/retro/0133-inject-sdk-boundary-into-agent-runner.md +0 -45
- package/docs/retro/0134-reduce-as-any-casts.md +0 -56
- package/docs/retro/0135-extract-display-helpers.md +0 -38
- package/docs/retro/0136-decompose-agent-menu.md +0 -43
- package/docs/retro/0144-consolidate-observation-model.md +0 -39
- package/docs/retro/0145-decompose-execute-push-ctx-to-boundary.md +0 -56
- package/docs/retro/0146-narrow-ui-context.md +0 -70
- package/docs/retro/0147-inject-wrap-text-into-conversation-viewer.md +0 -90
- package/docs/retro/0148-split-agent-widget-rendering.md +0 -39
- package/docs/retro/0152-add-prompt-snippet.md +0 -34
- package/docs/retro/0164-reorganize-into-domain-directories.md +0 -82
- package/docs/retro/0165-decompose-resolved-spawn-config.md +0 -78
- package/docs/retro/0166-extract-parent-session-info.md +0 -66
- package/docs/retro/0167-narrow-runner-io.md +0 -63
- package/docs/retro/0168-extract-tool-filter-config.md +0 -76
- package/docs/retro/0169-extract-run-context-from-run-options.md +0 -68
- package/docs/retro/0170-reduce-build-content-lines-complexity.md +0 -72
- package/docs/retro/0171-reduce-render-result-complexity.md +0 -77
- package/docs/retro/0172-extract-turn-formatting.md +0 -80
- package/docs/retro/0180-reorder-append-prompt-for-kv-cache.md +0 -62
- package/docs/retro/0185-remove-persistent-agent-memory.md +0 -73
- package/docs/retro/0188-replace-any-casts-with-sdk-types.md +0 -69
- package/docs/retro/0192-define-session-context-interface.md +0 -59
- package/docs/retro/0193-runtime-owns-context-queries.md +0 -75
- package/docs/retro/0194-align-tool-interfaces-for-structural-typing.md +0 -62
- package/docs/retro/0195-convert-tool-factories-to-classes.md +0 -84
- package/docs/retro/0196-convert-runner-menu-to-classes.md +0 -73
- package/docs/retro/0205-decompose-render-widget-lines.md +0 -64
- package/docs/retro/0206-decompose-show-agent-detail.md +0 -67
- package/docs/retro/0207-decompose-agent-widget-update.md +0 -92
- package/docs/retro/0208-extract-shared-test-fixtures.md +0 -102
- package/docs/retro/0214-convert-remaining-closure-factories-to-classes.md +0 -66
- package/docs/retro/0215-decompose-build-parent-context.md +0 -61
- package/docs/retro/0216-decompose-start-agent.md +0 -80
- package/docs/retro/0217-extract-overwrite-guard.md +0 -63
- package/docs/retro/0218-push-sdk-boundary-in-settings.md +0 -72
- package/docs/retro/0219-reduce-test-duplication-top-3-clone-families.md +0 -64
- package/docs/retro/0227-evolve-agent-record-into-agent.md +0 -80
- package/docs/retro/0228-async-start-agent-dissolve-run-handle.md +0 -80
- package/docs/retro/0229-agent-born-complete.md +0 -89
- package/docs/retro/0230-extract-concurrency-queue.md +0 -38
- package/docs/retro/0231-push-exec-registry-to-runner.md +0 -71
- package/docs/retro/0232-agent-resume-internal-observer-lifecycle.md +0 -109
- package/docs/retro/0237-remove-disallowed-tools.md +0 -78
- package/docs/retro/0238-remove-extensions-filtering.md +0 -72
- package/docs/retro/0239-collapse-filter-active-tools.md +0 -70
- package/docs/retro/0242-rename-agent-tool-to-subagent.md +0 -82
- package/docs/retro/0256-extract-worktree-isolation.md +0 -89
- package/docs/retro/0257-extract-child-session-factory.md +0 -33
- package/docs/retro/0262-add-workspace-provider-seam.md +0 -89
- package/docs/retro/0264-remove-extension-lifecycle-control.md +0 -91
- package/docs/retro/0265-born-complete-subagent-session.md +0 -97
- package/docs/retro/0270-type-consumable-public-surface.md +0 -108
- package/docs/retro/0272-export-workspace-collaborator-types.md +0 -38
- package/docs/retro/0277-encapsulate-agent-session.md +0 -80
- package/docs/retro/0280-rename-agent-to-subagent.md +0 -96
- package/docs/retro/0360-custom-agents-default-append.md +0 -81
- package/docs/retro/0373-extract-subagent-state.md +0 -94
- package/docs/retro/0374-encapsulate-subagent-start-notification.md +0 -171
- package/docs/retro/0375-extract-run-listener-workspace-bracket.md +0 -104
- package/docs/retro/0376-extract-manager-observer.md +0 -81
- package/docs/retro/0377-split-widget-delegation-from-runtime.md +0 -105
- package/docs/retro/0378-consolidate-lifecycle-test-fixtures.md +0 -100
- package/docs/retro/0379-consolidate-ui-tools-test-fixtures.md +0 -52
- package/docs/retro/0380-extract-layered-settings-loader.md +0 -97
- package/docs/retro/0381-replace-concurrency-queue-with-limiter.md +0 -95
- package/docs/retro/0400-include-parent-prompt-in-replace-mode.md +0 -84
- package/docs/retro/0403-abort-subagents-on-interrupt.md +0 -90
- package/docs/retro/0412-unify-session-mock-builders.md +0 -80
- package/docs/retro/0420-fold-run-metrics-live-activity-onto-record.md +0 -90
- package/docs/retro/0421-migrate-activity-readers-to-record-getters.md +0 -105
- package/docs/retro/0422-delete-activity-tracker-ui-observer.md +0 -97
- package/docs/retro/0423-widget-self-drive-from-lifecycle.md +0 -112
- package/docs/retro/0424-drop-widget-dep-from-subagent-tool.md +0 -89
- package/docs/retro/0425-reconcile-subagent-events-contract.md +0 -101
- package/docs/retro/0426-consolidate-residual-test-clone-families.md +0 -100
- package/docs/retro/0427-reconsider-ui-direction.md +0 -102
- package/docs/retro/0441-remove-agent-definition-management-subtree.md +0 -45
- package/docs/retro/0442-dissolve-agents-conversation-viewer.md +0 -108
- package/docs/retro/0444-shrink-widget-to-background-agents.md +0 -81
- package/docs/retro/0445-native-session-navigation.md +0 -122
- package/docs/retro/0446-spike-session-navigation-entry-criteria.md +0 -99
- package/docs/retro/0447-extract-subagents-settings-command.md +0 -91
- package/docs/retro/0448-block-disabled-agent-spawn.md +0 -75
- package/docs/retro/0462-navigation-renderer-tui-components.md +0 -105
- package/docs/retro/0463-file-snapshot-source-evicted-agents.md +0 -103
- package/src/observation/notification-state.ts +0 -27
- package/vitest.config.ts +0 -14
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
issue: 374
|
|
3
|
-
issue_title: "Encapsulate run start and notification attachment on Subagent"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Retro: #374 — Encapsulate run start and notification attachment on Subagent
|
|
7
|
-
|
|
8
|
-
## Stage: Planning (2026-06-14T00:00:00Z)
|
|
9
|
-
|
|
10
|
-
### Session summary
|
|
11
|
-
|
|
12
|
-
Read issue #374 (Phase 17 Step 3 — output-argument encapsulation), loaded skills, explored `subagent.ts`, `subagent-manager.ts`, `notification-state.ts`, and all seven test files with external writes.
|
|
13
|
-
Produced a 4-step TDD plan in `packages/pi-subagents/docs/plans/0374-encapsulate-subagent-start-notification.md`.
|
|
14
|
-
|
|
15
|
-
### Observations
|
|
16
|
-
|
|
17
|
-
- The `notification` field was already constructor-wired in Phase 17 Step 2 (from `execution.parentSession?.toolCallId`); the remaining work is making both `promise` and `notification` externally read-only and updating the 7 + 3 test write sites.
|
|
18
|
-
- Steps 1 and 2 in the TDD order are effectively merged: introducing `private _promise` alongside the existing public `promise?` field is a TypeScript duplicate-identifier error, so the public field removal and all consumer updates must land in one atomic commit (`feat: make Subagent.promise read-only, add start() (#374)`).
|
|
19
|
-
- The status guard (`if (status !== "queued" && status !== "running")`) in `start()` allows foreground agents (constructed with `status: "running"`) to pass through cleanly, while stopping aborted-while-queued agents; this folds the inline guard out of the `SubagentManager` limiter callback.
|
|
20
|
-
- `service-adapter.test.ts` tests that set `record.promise = Promise.resolve()` only test that `toSubagentRecord()` strips the field — the setup is vestigial once `promise` becomes a getter; simply removing it is sufficient.
|
|
21
|
-
- The "waits for promise when wait=true" test in `get-result-tool.test.ts` needs a more realistic execution stub (`runTurnLoop` returning `{ responseText: "Finished after wait.", aborted: false, steered: false }`) so `record.start()` triggers the full run pipeline and calls `markCompleted()` internally.
|
|
22
|
-
- `TestSubagentOptions.toolCallId?: string` is the cleanest shorthand for the 5 test files that create passive records but need a `NotificationState`; it routes through `makeStubExecution({ parentSession: { toolCallId } })`, matching the production constructor path exactly.
|
|
23
|
-
|
|
24
|
-
## Stage: Implementation — TDD (2026-06-14T16:31:00Z)
|
|
25
|
-
|
|
26
|
-
### Session summary
|
|
27
|
-
|
|
28
|
-
Implemented all 4 plan steps in 2 substantive commits: one atomic `feat:` commit for `start()` + `promise`/`notification` read-only + all test site updates, and one `docs:` commit for the architecture doc.
|
|
29
|
-
Test count went from 975 to 981 (+6 new `start()` unit tests).
|
|
30
|
-
Pre-completion reviewer returned PASS with one WARN (stale test count in `package-pi-subagents` SKILL.md — fixed immediately).
|
|
31
|
-
|
|
32
|
-
### Observations
|
|
33
|
-
|
|
34
|
-
- Plan steps 1–3 landed in a single commit because making `notification` private required the same `subagent.ts` file as making `promise` private; splitting would have required complex partial staging.
|
|
35
|
-
- The `void record.start()` and `void this.limiter.schedule(...)` patterns were needed in `subagent-manager.ts` to satisfy `@typescript-eslint/no-floating-promises` — `start()` returns a `Promise<void>` but the manager stores the state internally; callers don't need to await it.
|
|
36
|
-
- The "waits for promise when wait=true" test in `get-result-tool.test.ts` required `void record.start()` (intentional fire-and-forget) for the same reason.
|
|
37
|
-
- Grep-verifiable outcome confirmed: `\.promise =` and `\.notification =` appear only inside `subagent.ts` (as `this._promise =` and `this._notification =`).
|
|
38
|
-
- Pre-completion reviewer: PASS (no FAIL findings; WARN on stale skill test count addressed inline).
|
|
39
|
-
|
|
40
|
-
## Stage: Final Retrospective (2026-06-14T17:30:00Z)
|
|
41
|
-
|
|
42
|
-
### Session summary
|
|
43
|
-
|
|
44
|
-
Shipped issue #374 (Phase 17 Step 3) cleanly across three stages: a planning session produced a 4-step plan, a TDD session implemented it in 2 substantive commits (test count 975 → 981), and a ship session pushed, verified CI, closed the issue, and released `pi-subagents-v16.2.0`.
|
|
45
|
-
No rework, no user corrections, no CI failures — the only mid-stream fixes were two self-identified lint adjustments inside the TDD session.
|
|
46
|
-
|
|
47
|
-
### Observations
|
|
48
|
-
|
|
49
|
-
#### What went well
|
|
50
|
-
|
|
51
|
-
- The cross-session retro bridge worked exactly as designed: the planning stage wrote three concrete breadcrumbs — steps 1+2 must merge (TypeScript duplicate-identifier), the `get-result-tool.test.ts` "waits for promise" test needs a realistic `runTurnLoop` stub, and `TestSubagentOptions.toolCallId` is the cleanest notification shorthand — and the TDD stage consumed all three directly without re-deriving them.
|
|
52
|
-
This is the first time the breadcrumb-to-implementation handoff is visibly load-bearing rather than ceremonial.
|
|
53
|
-
- The grep-verifiable completion criterion (`\.promise =` and `\.notification =` appear only inside `subagent.ts`) gave an objective, one-command done-check (session turns 123–124) instead of a subjective "looks encapsulated."
|
|
54
|
-
|
|
55
|
-
#### What caused friction (agent side)
|
|
56
|
-
|
|
57
|
-
- `missing-context` — the plan did not anticipate that replacing `record.promise = record.run()` (assignment consumes the promise) with a bare `record.start()` call would trip `@typescript-eslint/no-floating-promises` at the two manager call sites and one test site.
|
|
58
|
-
Self-identified via lint.
|
|
59
|
-
Impact: two extra `void` edits (turns 106, 111) inside the same commit — no commit reorder, no rework beyond the fixups.
|
|
60
|
-
- `wrong-abstraction` (surfaced by operator pushback during the retro) — the `void this.limiter.schedule(() => record.start())` fix is safe but marks an unfinished design seam, not a tidy idiom.
|
|
61
|
-
`run()`/`start()` carry a hard "always resolves, errors captured internally" contract, so `void` is the ESLint-sanctioned annotation (no unhandled-rejection risk).
|
|
62
|
-
But the encapsulation regressed the promise-capture timing: before #374, `record.promise = this.limiter.schedule(...)` set the handle eagerly at spawn (even queued agents had `.promise`); after #374, `.promise` stays undefined until the limiter fires the thunk and `start()` runs, so promise ownership is now split between `Subagent._promise` and the limiter's internal completion handle (which the `void` discards).
|
|
63
|
-
`waitForAll()` still worked only via `pendingPromises()`'s `null`-filter plus re-poll loop, so the in-code comment "a single `allSettled` covers the queued case" became inaccurate.
|
|
64
|
-
This regressed Step 1's (#381) documented invariant "every spawned agent has a `promise` at spawn" — a cross-step regression within the same phase, not (as first claimed in this retro) work deferred to a future domain split.
|
|
65
|
-
Corrected in a follow-up `fix:` (see the second retrospective stage below): `Subagent.scheduleVia(schedule)` captures the limiter promise eagerly inside the agent, restoring the invariant without reintroducing an external `.promise =` write.
|
|
66
|
-
Impact: one follow-up `fix:` commit (+1 regression test, +2 `scheduleVia` unit tests).
|
|
67
|
-
- `wrong-abstraction` — the plan decomposed the work into 3 TDD steps (promise read-only, notification read-only, doc) but steps 1–3 collapsed into a single atomic commit because both fields live in `subagent.ts` and making each read-only is one type-level change.
|
|
68
|
-
The planning retro half-anticipated this (it flagged steps 1+2 merging) but did not extend the reasoning to step 3.
|
|
69
|
-
Impact: plan/reality granularity mismatch, documented as a deviation; no rework.
|
|
70
|
-
|
|
71
|
-
#### What caused friction (user side)
|
|
72
|
-
|
|
73
|
-
- None.
|
|
74
|
-
User involvement was a single well-placed strategic decision (the batch-vs-release-now `ask_user` gate in the ship stage), not mechanical oversight.
|
|
75
|
-
|
|
76
|
-
### Diagnostic details
|
|
77
|
-
|
|
78
|
-
- **Model-performance correlation** — Planning and TDD ran on `claude-sonnet-4-6` (appropriate); the pre-completion reviewer subagent ran on its frontmatter default; the final retro ran on `claude-opus-4-8` (appropriate for judgment-heavy synthesis).
|
|
79
|
-
The **ship stage ran on `opencode-go/deepseek-v4-flash`** — a weak model driving release management (interpreting the `UNSTABLE` merge state, the batch-vs-release decision, merging the release PR, closing the issue).
|
|
80
|
-
It executed cleanly this time, but these are irreversible high-stakes operations; pinning a stronger model for `/ship-issue` would de-risk them.
|
|
81
|
-
- **Escalation-delay tracking** — no rabbit-holes; each lint failure resolved within 1–2 tool calls.
|
|
82
|
-
- **Unused-tool detection** — exploration used `cat`/`grep` via `bash` rather than the `Read` tool or `colgrep`; for finding exact `.promise =` write sites, grep is the correct choice (exact-pattern matching), so colgrep non-use was appropriate.
|
|
83
|
-
The `cat`-via-`bash` habit (vs `Read`) added no harm here but bypasses structured-read benefits.
|
|
84
|
-
- **Feedback-loop gap analysis** — verification was incremental: affected-file tests after the first edits (turn 81), full suite + `check` + `lint` mid-cycle (turns 102–113), and the `fallow dead-code` gate before review.
|
|
85
|
-
No end-only-verification gap.
|
|
86
|
-
|
|
87
|
-
### Follow-ups
|
|
88
|
-
|
|
89
|
-
1. The `start()` / limiter promise-ownership split was reclassified as a regression and **fixed** via `scheduleVia` (see the second retrospective stage), not deferred.
|
|
90
|
-
|
|
91
|
-
### Considered but not proposed
|
|
92
|
-
|
|
93
|
-
1. **Floating-promise ESLint rule** (proposed, then retracted on operator pushback): codifying "replace the assignment with `void record.start()`" as a `code-design` idiom would train reflexive lint suppression without checking the always-resolves contract.
|
|
94
|
-
The `void` is correct here but signals unfinished domain work; the lesson lives in this retro, not in the skill.
|
|
95
|
-
2. **Pin a stronger model for `/ship-issue`**: an operator model-selection choice, not an `AGENTS.md`/prompt rule; noted in Diagnostic details for awareness.
|
|
96
|
-
|
|
97
|
-
### Changes made
|
|
98
|
-
|
|
99
|
-
1. `packages/pi-subagents/docs/retro/0374-encapsulate-subagent-start-notification.md` — added the Final Retrospective stage entry (session summary, friction points, diagnostic lenses, follow-ups); no skill or prompt edits landed (the sole proposal was retracted on operator pushback).
|
|
100
|
-
|
|
101
|
-
## Stage: Regression Correction — Process Retrospective (2026-06-14T18:00:00Z)
|
|
102
|
-
|
|
103
|
-
### Session summary
|
|
104
|
-
|
|
105
|
-
Operator pushback on the first retro's "`void` is safe, defer it" framing surfaced that #374 had **regressed a sibling step's invariant**: Step 1 (#381) guaranteed "every spawned agent has a `promise` at spawn," and #374's `void limiter.schedule(() => record.start())` made a queued agent's promise lazy.
|
|
106
|
-
Fixed via `Subagent.scheduleVia` (eager capture, control inverted so no external `.promise =` write returns) in commit `4f08c6c3` (+1 regression test, +2 unit tests; suite 981 → 982), then ran this process retrospective on how the regression slipped through plan, implementation, and review.
|
|
107
|
-
|
|
108
|
-
### How the regression happened (root-cause chain)
|
|
109
|
-
|
|
110
|
-
1. **Planning blind spot.**
|
|
111
|
-
The #374 plan's acceptance criterion was grep-verifiable encapsulation (`\.promise =` only in `subagent.ts`).
|
|
112
|
-
That measured the *goal* (hide the field) but never the *invariant at risk* (the field is an awaitable handle with an at-spawn timing contract that #381 established).
|
|
113
|
-
The plan treated `promise` as a field to hide, not as a contract to preserve.
|
|
114
|
-
2. **Implementation masked the semantics.**
|
|
115
|
-
Converting `record.promise = limiter.schedule(...)` to a bare `limiter.schedule(() => record.start())` tripped `no-floating-promises`; the reflexive `void` fix silenced the lint *and* discarded the eager handle in the same stroke.
|
|
116
|
-
The lint fix was the exact site of the behavior change, which made it feel mechanical rather than semantic.
|
|
117
|
-
3. **No executable guard.**
|
|
118
|
-
The #381 invariant lived only in an architecture-doc "Outcome:" bullet (prose).
|
|
119
|
-
No test pinned "a queued agent has a `promise` at spawn," so the full suite stayed green through the regression.
|
|
120
|
-
4. **Review inherited the blind spot.**
|
|
121
|
-
The pre-completion reviewer checks deterministic gates + the plan's acceptance criteria; since the criteria never named the cross-step invariant, criteria-driven review could not flag its loss.
|
|
122
|
-
|
|
123
|
-
The through-line: in a phased refactor, each step's "Outcome:" bullets establish invariants later steps inherit implicitly, and nothing converts those prose invariants into executable guards — so a later step regresses an earlier one with a green suite and a passing review.
|
|
124
|
-
|
|
125
|
-
### Observations
|
|
126
|
-
|
|
127
|
-
#### What caused friction (agent side)
|
|
128
|
-
|
|
129
|
-
- `missing-context` — the plan did not enumerate the invariants that prior Phase 17 steps had established on the shared `Subagent`/limiter surface, so the at-spawn-promise contract was invisible during both planning and implementation.
|
|
130
|
-
Impact: a shipped regression (latent — `waitForAll` re-polls — but a real invariant break), caught only by operator pushback during the retro, requiring a follow-up `fix:`.
|
|
131
|
-
- `wrong-abstraction` — the proximate trigger was treating a `void` lint fix as mechanical.
|
|
132
|
-
`void` on a promise-returning call discards whatever the promise carried (here: the eager capture handle); it deserves a semantic check, not a reflex.
|
|
133
|
-
|
|
134
|
-
#### What went well
|
|
135
|
-
|
|
136
|
-
- Operator pushback ("I'm sure that rule exists for a reason… are we heading toward a better design, or an awkward intermediary state?") was the single intervention that converted a rationalized smell into a found regression.
|
|
137
|
-
This is the bidirectional-feedback ideal: a redirecting question, not a correction, that reframed the agent's own analysis.
|
|
138
|
-
|
|
139
|
-
### Diagnostic details
|
|
140
|
-
|
|
141
|
-
- **Feedback-loop gap analysis** — every gate (check, lint, test, fallow, pre-completion review) was green across #374; none could see the regression because the invariant was prose, never a test.
|
|
142
|
-
The gap is upstream of the gates: the invariant was never made executable.
|
|
143
|
-
|
|
144
|
-
### Diagnostic details — model assignment
|
|
145
|
-
|
|
146
|
-
- Operator pushback also corrected a misconception: planning was assumed to run on Opus, but session turns 2–45 (`/plan-issue`) ran on `claude-sonnet-4-6`, and `.pi/prompts/plan-issue.md` had **no `model:` directive** — so planning silently inherited the session model.
|
|
147
|
-
The judgment-heaviest, highest-leverage stage (where this regression originated) was running on an inherited, weaker model by default.
|
|
148
|
-
Resolved by pinning `/plan-issue` and `/retro` to Opus via frontmatter (the `pi-prompt-template-model` extension was already loaded but unused for model selection).
|
|
149
|
-
Caveat recorded: a stronger planner raises the odds of noticing an unstated invariant but is not a substitute for the explicit rule — the rule is the dependable fix, the model is a complementary lever.
|
|
150
|
-
|
|
151
|
-
### Proposals (all accepted and implemented)
|
|
152
|
-
|
|
153
|
-
1. `/plan-issue` prompt — "Invariants at risk" plan section: list prior phase steps' documented invariants (roadmap `Outcome:`/`Landed:` bullets) and pin each with a named test.
|
|
154
|
-
2. `code-design` skill (ESLint section) — "void on a promise-returning call" guard: before `void`-ing to silence `no-floating-promises`, confirm the discarded promise carried no semantics.
|
|
155
|
-
3. `pre-completion-reviewer` agent — new section `2h. Cross-step invariant preservation`: FAIL on a regressed prior-step invariant, WARN when an invariant holds but is pinned only by prose.
|
|
156
|
-
4. Model pinning — `/plan-issue` and `/retro` pinned to `anthropic/claude-opus-4-8`.
|
|
157
|
-
|
|
158
|
-
### Changes made
|
|
159
|
-
|
|
160
|
-
1. `packages/pi-subagents/src/lifecycle/subagent.ts` — added `scheduleVia(schedule)` (eager limiter-promise capture) and `guardedRun()` (shared abort-while-queued guard); `start()` now returns `void`.
|
|
161
|
-
2. `packages/pi-subagents/src/lifecycle/subagent-manager.ts` — `spawn()` queued path uses `record.scheduleVia(...)`; removed the `void` workarounds.
|
|
162
|
-
3. `packages/pi-subagents/test/lifecycle/subagent-manager.test.ts` — added regression test (queued agent has a `promise` at spawn).
|
|
163
|
-
4. `packages/pi-subagents/test/lifecycle/subagent.test.ts` — rewrote `start()` tests for the `void` return; added two `scheduleVia` unit tests.
|
|
164
|
-
5. `packages/pi-subagents/test/helpers/make-subagent.test.ts`, `packages/pi-subagents/test/tools/get-result-tool.test.ts` — updated for the `void`-returning `start()`.
|
|
165
|
-
6. `packages/pi-subagents/docs/architecture/architecture.md` — Step 3 `Landed`/`Correction` notes record the regression and `scheduleVia` fix.
|
|
166
|
-
7. `.pi/prompts/plan-issue.md` — added the "Invariants at risk" section and pinned `model: anthropic/claude-opus-4-8`.
|
|
167
|
-
8. `.pi/prompts/retro.md` — pinned `model: anthropic/claude-opus-4-8`.
|
|
168
|
-
9. `.pi/skills/code-design/SKILL.md` — added the "void on a promise-returning call" ESLint guard.
|
|
169
|
-
10. `.pi/agents/pre-completion-reviewer.md` — added section `2h`, its output block, and severity-model entries.
|
|
170
|
-
11. `AGENTS.md` — added cross-step invariant preservation to the reviewer's documented coverage.
|
|
171
|
-
12. The regression fix landed in commit `4f08c6c3` (`fix:`); these retro/process changes land in the `docs(retro):` commit.
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
issue: 375
|
|
3
|
-
issue_title: "Extract run-listener and workspace-bracket collaborators from Subagent"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Retro: #375 — Extract run-listener and workspace-bracket collaborators from Subagent
|
|
7
|
-
|
|
8
|
-
## Stage: Planning (2026-06-14T19:25:00Z)
|
|
9
|
-
|
|
10
|
-
### Session summary
|
|
11
|
-
|
|
12
|
-
Read issue #375 (Phase 17 Step 4 — core consolidation), loaded the package, code-design, design-review, testing, colgrep, and markdown skills, and explored `subagent.ts`, `workspace.ts`, `subagent-manager.ts`, and `subagent.test.ts`.
|
|
13
|
-
Produced a 4-step plan in `packages/pi-subagents/docs/plans/0375-extract-run-listener-workspace-bracket.md` extracting a `RunListeners` collaborator and a `WorkspaceBracket` collaborator out of the 488-LOC `Subagent` class.
|
|
14
|
-
|
|
15
|
-
### Observations
|
|
16
|
-
|
|
17
|
-
- The issue's first-cut `attach(unsub, detach)` sketch does not match the real call pattern: `wireSignal` fires at run-start and `attachObserver` after session creation, and `resume()` only attaches the observer — so `RunListeners` exposes the two attach points separately (`wireSignal` / `attachObserver` / `release`), not a single combined `attach`.
|
|
18
|
-
- The issue's "three dispose paths" is really **two** `dispose()` call sites (`completeRun`, `failRun`); `run()`'s prepare-failure catch has no prepared workspace to dispose.
|
|
19
|
-
- The two dispose sites have genuinely different lifecycle semantics — `completeRun` derives status from the result, folds the addendum, and lets a throw propagate; `failRun` hardcodes `"error"`, discards the addendum, and is best-effort `try/catch`.
|
|
20
|
-
Per the code-design structural-duplication heuristic, I kept them separate: `WorkspaceBracket.dispose()` centralizes the *logic* (the `if (prepared)` guard + addendum unwrap) in one place but deliberately does **not** wrap `try/catch`, so each caller's error handling is preserved line-for-line.
|
|
21
|
-
This honestly satisfies the issue's "disposal logic in exactly one place" without forcing a discriminator parameter.
|
|
22
|
-
- `WorkspaceBracket` captures the provider *resolver* (`execution.getWorkspaceProvider`), not the provider, so resolution stays at run-start — matching today's `getWorkspaceProvider?.()` timing — while letting the bracket be constructed in the `Subagent` constructor (construct-complete, preserving the Step 2 invariant).
|
|
23
|
-
- Per the #374 retro lesson, I added an "Invariants at risk" section: the three prior Phase 17 invariants (at-spawn `promise`, construct-complete, zero external field writes) are each already pinned by a named test and are low-risk here because this step does not touch `start`/`scheduleVia`/`_promise` or add optional init fields.
|
|
24
|
-
- Step 3 (wiring) must be atomic: removing the public `wireSignal`/`attachObserver`/`releaseListeners` methods breaks `subagent.test.ts` at the type level, so the `describe`-block deletions land in the same commit.
|
|
25
|
-
- Suite is at 982 tests (verified by running the suite); expect roughly +5 net (≈ −7 redundant `Subagent` listener tests, +6 each new collaborator suite).
|
|
26
|
-
- First-party issue (author `gotgenes` == gh user) with an unambiguous proposed change, so the `ask-user` gate was skipped.
|
|
27
|
-
- Commit types are `test:`/`refactor:`/`docs:` — internal-only, no release-please bump; release cadence is a ship-time decision flagged in Risks.
|
|
28
|
-
|
|
29
|
-
## Stage: Implementation — TDD (2026-06-14T21:40:00Z)
|
|
30
|
-
|
|
31
|
-
### Session summary
|
|
32
|
-
|
|
33
|
-
Implemented all 4 plan steps in 4 commits (2 `refactor:`, 1 additional `refactor:` for the wiring step, 1 `docs:`).
|
|
34
|
-
Test count went from 982 to 994 (+12: 7 `RunListeners` tests + 13 `WorkspaceBracket` tests − 8 redundant `Subagent` listener tests removed).
|
|
35
|
-
`subagent.ts` landed at 448 LOC (target was ≤ 450).
|
|
36
|
-
Pre-completion reviewer returned WARN (2 non-blocking findings; the doc metric rows were fixed inline before committing).
|
|
37
|
-
|
|
38
|
-
### Observations
|
|
39
|
-
|
|
40
|
-
- **Microtask-boundary deviation from plan**: the plan showed `const cwd = await this.workspaceBracket.prepare(...)` unconditionally in `run()`.
|
|
41
|
-
`async` functions always create a microtask boundary even when they return immediately (no-provider path), which broke `subagent-manager.test.ts`'s synchronous assertion that `factory.toHaveBeenCalledOnce()` — the factory call had been deferred to the next microtask tick.
|
|
42
|
-
Fix: added `WorkspaceBracket.hasProvider()` (a synchronous provider-existence check) and guarded the `await` with `if (this.workspaceBracket.hasProvider())`, restoring the original timing semantics.
|
|
43
|
-
The `hasProvider()` method is a mild Tell-Don't-Ask trade-off (the caller queries bracket state to decide whether to call it), documented at the call site and noted as a WARN by the pre-completion reviewer.
|
|
44
|
-
The underlying cause: `SubagentManager.spawn()` always injects `getWorkspaceProvider: () => this._workspaceProvider` as a function, even when no provider is registered, so the naïve `if (this.execution.getWorkspaceProvider)` guard was always true.
|
|
45
|
-
- **Step 3 collapsed into one commit as expected**: removing `wireSignal`/`attachObserver`/`releaseListeners` from `Subagent` broke `subagent.test.ts` at the type level; the redundant `describe` block deletions and the production wiring landed atomically.
|
|
46
|
-
- **LOC target met**: `subagent.ts` went from 488 → 448 (plan estimated ≤ 450; actual 448).
|
|
47
|
-
The gap between estimated removal (≈ 40 lines) and actual (40 lines) was closed by trimming the stale module-level doc comment and redundant field-level comments.
|
|
48
|
-
- **Prior-step invariants held**: all three Phase 17 cross-step invariants (at-spawn `promise`, construct-complete, zero external field writes) passed grep-verification and the 994-test suite confirms no regressions.
|
|
49
|
-
- **Pre-completion reviewer WARN findings** (both addressed inline):
|
|
50
|
-
1. `architecture.md` health-metric rows still carried "→ 59 after Step 4" annotations after landing — updated to actual counts (60 files, 8,356 LOC) and the docs commit amended.
|
|
51
|
-
2. `WorkspaceBracket.hasProvider()` TDA trade-off — documented in the `run()` call-site comment; noted here for Phase 18 awareness.
|
|
52
|
-
|
|
53
|
-
## Stage: Final Retrospective (2026-06-15T02:04:42Z)
|
|
54
|
-
|
|
55
|
-
### Session summary
|
|
56
|
-
|
|
57
|
-
Shipped issue #375 (Phase 17 Step 4) cleanly across four stages in one continuous session: planning produced a 4-step plan, TDD implemented it in 4 commits (suite 982 → 994), shipping pushed/verified-CI/closed-the-issue and merged release-please PR #406 (`pi-subagents-v16.2.1`, which actually carried #374's `fix:` — #375's `refactor:`/`docs:` commits trigger no bump).
|
|
58
|
-
The only substantive friction was a single self-identified plan deviation in TDD (a microtask-boundary timing trap), resolved inside the same commit with no rework, reorder, or user correction.
|
|
59
|
-
|
|
60
|
-
### Observations
|
|
61
|
-
|
|
62
|
-
#### What went well
|
|
63
|
-
|
|
64
|
-
- **The cross-step-invariant discipline from the #374 retro paid off a second time.**
|
|
65
|
-
The #374 process retro added the "Invariants at risk" plan section; the #375 plan used it to list the three prior Phase 17 invariants (at-spawn `promise`, construct-complete, zero external field writes), each already pinned by a named test.
|
|
66
|
-
All three held through the extraction with a green suite — the regression class that bit #374 did not recur.
|
|
67
|
-
This is the first time the new section was load-bearing on a *fresh* issue rather than as a post-hoc correction.
|
|
68
|
-
- **Planning corrected the issue's own design sketch instead of implementing it literally.**
|
|
69
|
-
The issue proposed `RunListeners.attach(unsub, detach)` and "three dispose paths collapse into one"; the plan recognized the two handles attach at different lifecycle moments (so `attach` had to split into `wireSignal`/`attachObserver`/`release`) and that the two dispose sites have genuinely different error-handling semantics (so they stay separate per the structural-duplication heuristic).
|
|
70
|
-
Treating the issue body as a hypothesis, not a spec, avoided a wrong abstraction.
|
|
71
|
-
|
|
72
|
-
#### What caused friction (agent side)
|
|
73
|
-
|
|
74
|
-
- `missing-context` — the plan's Design Overview sketched `const cwd = await this.workspaceBracket.prepare(...)` **unconditionally**, but the original `run()` only awaited inside `if (provider) { ... }`, keeping the no-provider path synchronous up to the factory call.
|
|
75
|
-
An always-`async` helper adds a microtask boundary even when it returns immediately, so the queued-abort test in `subagent-manager.test.ts` ("abort removes a queued agent without ever running it") failed: it asserts `factory.toHaveBeenCalledOnce()` synchronously, and the factory call had been deferred a tick.
|
|
76
|
-
The first fix attempt (`if (this.execution.getWorkspaceProvider)`) also failed because `SubagentManager.spawn()` always injects `getWorkspaceProvider: () => this._workspaceProvider` as a function regardless of whether a provider is registered, so the guard was always true.
|
|
77
|
-
Resolved by adding `WorkspaceBracket.hasProvider()` (a synchronous predicate) and guarding the `await` with it.
|
|
78
|
-
Impact: ~2 test-run cycles inside TDD step 3, one extra method (`hasProvider`) plus 2 unit tests not in the plan, and a mild Tell-Don't-Ask trade-off the pre-completion reviewer flagged as WARN.
|
|
79
|
-
Self-identified via the failing test; no commit reorder, no rework beyond the in-commit fix.
|
|
80
|
-
- `other` (minor) — `subagent.ts` first landed at 469 LOC, above the plan's ≤ 450 gate; two trim passes (stale module-level doc comment, redundant field comments) brought it to 448.
|
|
81
|
-
The trimmed comments were genuinely stale post-extraction, so the trim was legitimate, but the LOC estimate ("≈ 40 lines removed") was optimistic about the structural change alone.
|
|
82
|
-
Impact: 2 extra edits, no rework.
|
|
83
|
-
|
|
84
|
-
#### What caused friction (user side)
|
|
85
|
-
|
|
86
|
-
- None.
|
|
87
|
-
The four-stage flow ran end-to-end on prompt templates with zero mid-stream user corrections or strategic interventions — the work was clean enough that none were needed.
|
|
88
|
-
|
|
89
|
-
### Diagnostic details
|
|
90
|
-
|
|
91
|
-
- **Model-performance correlation** — Planning ran on `anthropic/claude-opus-4-8` (pinned via `plan-issue.md`, appropriate for judgment-heavy design); TDD on `anthropic/claude-sonnet-4-6` (appropriate); the pre-completion reviewer subagent on its frontmatter default; Retro on `anthropic/claude-opus-4-8` (pinned).
|
|
92
|
-
The **ship stage ran on `opencode-go/deepseek-v4-flash`** — the same weak-model-on-release-management pattern flagged in the #374 retro.
|
|
93
|
-
It again executed cleanly, including the non-trivial reasoning that PR #406 mapped to #374 not #375 and the `UNSTABLE`/empty-rollup `GITHUB_TOKEN` diagnosis.
|
|
94
|
-
Second consecutive clean run, so the risk remains theoretical (irreversible ops on a weak model) rather than demonstrated harm.
|
|
95
|
-
- **Escalation-delay tracking** — the microtask friction was not a rabbit hole: the diagnosis moved methodically (failing test → `concurrency-limiter.ts` → `subagent-manager.ts:156` injection point → root cause) across ~2 test-run cycles, under the 5-call flag threshold.
|
|
96
|
-
- **Unused-tool detection** — the friction was exact-symbol tracing (`grep` for `getWorkspaceProvider`), which `grep` handled correctly; no Explore/colgrep dispatch was warranted.
|
|
97
|
-
- **Feedback-loop gap analysis** — verification was incremental throughout: per-file `vitest` after each red/green, `pnpm run check` after the interface-change step, full suite + lint + `fallow dead-code` before the pre-completion review.
|
|
98
|
-
No end-only-verification gap.
|
|
99
|
-
|
|
100
|
-
### Changes made
|
|
101
|
-
|
|
102
|
-
1. `.pi/skills/testing/SKILL.md` — added a bullet under `### Interface and type changes` on the conditional-`await` → always-`async` microtask-boundary trap (sibling to the existing runtime-vs-typecheck timing rule).
|
|
103
|
-
2. `packages/pi-subagents/docs/retro/0375-extract-run-listener-workspace-bracket.md` — added this Final Retrospective stage entry.
|
|
104
|
-
3. Considered but not landed (operator-declined or out of scope): pinning `/ship-issue` to a stronger model (recurrence of the #374 finding, no demonstrated harm this session), a `plan-issue` rule on preserving conditional awaits in extracted-method sketches, and a rule against trimming comments to hit a LOC gate.
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
issue: 376
|
|
3
|
-
issue_title: "Extract the manager observer from index.ts into a class"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Retro: #376 — Extract the manager observer from index.ts into a class
|
|
7
|
-
|
|
8
|
-
## Stage: Planning (2026-06-15T00:00:00Z)
|
|
9
|
-
|
|
10
|
-
### Session summary
|
|
11
|
-
|
|
12
|
-
Planned Phase 17 Step 5: extracting the inline `SubagentManagerObserver` literal from `index.ts` into a `SubagentEventsObserver` class under `src/observation/`, constructed with narrow `emit` / `appendEntry` / `NotificationSystem` deps.
|
|
13
|
-
The plan is a single red→green→commit extraction (class + tests + `index.ts` wiring in one commit) plus a docs commit marking the step complete.
|
|
14
|
-
Classified as a non-breaking, pure internal extraction with no observable behavior change.
|
|
15
|
-
|
|
16
|
-
### Observations
|
|
17
|
-
|
|
18
|
-
- The issue is the operator's own (author `gotgenes` matches the gh user) and the architecture doc already specifies Step 5 precisely, so the `ask-user` gate was skipped.
|
|
19
|
-
- The class + `index.ts` swap must land in one commit: `index.ts` is the sole call site of the literal being replaced, and the new class needs a consumer to satisfy `pnpm fallow dead-code`.
|
|
20
|
-
- Kept `buildEventData` in `notification.ts` (it is tested there) and imported it into the new module — avoids churning `notification.test.ts`.
|
|
21
|
-
- Used `refactor:` for the extraction commit, matching the precedent of Phase 17 Step 4 (#375); `refactor` is hidden from the release-please changelog.
|
|
22
|
-
- Two structural smells were noted as out of scope: the `record.notification?.resultConsumed` Law-of-Demeter chain (track-and-watch) and narrowing `NotificationSystem` to a two-method `CompletionNotifier` per ISP (the issue prescribes passing `NotificationSystem`).
|
|
23
|
-
- Wiring `pi.events.emit` / `pi.appendEntry` as arrow callbacks in `index.ts` avoids the `@typescript-eslint/unbound-method` trap; mirrors the existing `SettingsManager` emit pattern.
|
|
24
|
-
- Step 6 (#377) depends on this step; the plan pins the previously-untested event/notification dispatch invariants so Step 6 cannot regress them.
|
|
25
|
-
|
|
26
|
-
## Stage: Implementation — TDD (2026-06-15T18:40:00Z)
|
|
27
|
-
|
|
28
|
-
### Session summary
|
|
29
|
-
|
|
30
|
-
Completed 2 TDD cycles: (1) extracted `SubagentEventsObserver` into `src/observation/subagent-events-observer.ts`, added 15 tests covering all four observer methods, and updated `src/index.ts` to replace the inline literal — all in one coupled commit; (2) marked Phase 17 Step 5 ✅ Complete in the architecture doc with a Landed note.
|
|
31
|
-
Test delta: 994 → 1009 (+15); file count 63 → 64 test files; `index.ts` 226 → 177 lines.
|
|
32
|
-
Pre-completion reviewer: PASS.
|
|
33
|
-
|
|
34
|
-
### Observations
|
|
35
|
-
|
|
36
|
-
- The typed `vi.fn<(channel: string, data: unknown) => void>()` mock triggered `@typescript-eslint/no-unnecessary-type-assertion` on `mock.calls[0]!` indexing — fixed by switching the error-status assertions to `toHaveBeenCalledWith("subagents:failed", expect.anything())` and the success-status assertion to `toHaveBeenCalledWith("subagents:completed", buildEventData(record))`.
|
|
37
|
-
This eliminated all raw `mock.calls[0]` indexing from the file, which is cleaner.
|
|
38
|
-
- The autoformatter (`pi-autoformat`) ran after writing `index.ts`, so the import block was reflowed; re-reading before further edits would be required in any follow-up session.
|
|
39
|
-
- Architecture doc: Step 4's header was already missing its ✅ Complete marker (it had a Landed note); the reviewer noted this was corrected correctly, not a regression.
|
|
40
|
-
- All five previously-untested observer-behavior invariants are now pinned by tests for the first time.
|
|
41
|
-
|
|
42
|
-
## Stage: Final Retrospective (2026-06-15T22:55:15Z)
|
|
43
|
-
|
|
44
|
-
### Session summary
|
|
45
|
-
|
|
46
|
-
Phase 17 Step 5 ran cleanly end-to-end across planning, TDD, ship, and retro: one `refactor:` extraction commit plus a `docs:` roadmap update, +15 tests, `index.ts` 226 → 177 lines, released as `pi-subagents-v16.2.2`.
|
|
47
|
-
The plan correctly anticipated the class/`index.ts` coupling (one commit) and the `unbound-method` trap (arrow callbacks), so implementation followed the plan with a single minor test-assertion adjustment.
|
|
48
|
-
The pre-completion reviewer (on `claude-sonnet-4-6`) returned PASS and incidentally flagged a stale Step 4 checkmark that was corrected.
|
|
49
|
-
|
|
50
|
-
### Observations
|
|
51
|
-
|
|
52
|
-
#### What went well
|
|
53
|
-
|
|
54
|
-
- The lint feedback loop caught the only defect before it was committed: typed `vi.fn` made `mock.calls[0]!` an unnecessary assertion, surfaced by `pnpm run lint` between green and commit, fixed by switching to `toHaveBeenCalledWith(...)`.
|
|
55
|
-
No follow-up commit was needed — verification ran incrementally (lint + test before each commit, `fallow dead-code` before push).
|
|
56
|
-
- The `pre-completion-reviewer` subagent earned its dispatch: beyond confirming the deterministic gates, it caught that Phase 17 Step 4's header was missing its `✅ Complete` marker (a prior-session oversight) and validated all six Mermaid diagrams.
|
|
57
|
-
- The plan's pre-work paid off — the coupling note (class + consumer + import removal in one commit to satisfy `fallow`) and the arrow-callback note for `emit`/`appendEntry` meant zero rework at those two known-risk points.
|
|
58
|
-
|
|
59
|
-
#### What caused friction (agent side)
|
|
60
|
-
|
|
61
|
-
- `missing-context` (self-identified) — the TDD test was written with `mock.calls[0]!` non-null assertions; typed `vi.fn<(channel: string, data: unknown) => void>()` makes the call tuple non-optional, so `@typescript-eslint/no-unnecessary-type-assertion` fired on every `!`.
|
|
62
|
-
Impact: ~5 tool calls (turns 1–6) to diagnose and rewrite four assertions to `toHaveBeenCalledWith(...)`; caught by lint before commit, no rework.
|
|
63
|
-
- `missing-context` (self-identified) — the ship stage used `grep -oP` (Perl regex) to extract issue numbers; macOS BSD `grep` has no `-P`.
|
|
64
|
-
Impact: one wasted tool call (turn 38), self-recovered with `sed` on turn 39.
|
|
65
|
-
|
|
66
|
-
#### What caused friction (user side)
|
|
67
|
-
|
|
68
|
-
- None.
|
|
69
|
-
The single `ask_user` gate (release batching) was the appropriate strategic checkpoint and the user answered "close now"; no mechanical oversight was required.
|
|
70
|
-
|
|
71
|
-
### Diagnostic details
|
|
72
|
-
|
|
73
|
-
- **Model-performance correlation** — TDD ran on `claude-sonnet-4-6` (appropriate for judgment + code authoring); the `pre-completion-reviewer` subagent ran on `claude-sonnet-4-6` per its agent frontmatter (appropriate for review).
|
|
74
|
-
The ship stage ran on `opencode-go/deepseek-v4-flash` — a cheaper model on a mostly-mechanical workflow (git, CI watch, issue close).
|
|
75
|
-
It handled the one judgment call (release batching) correctly by asking the user and read the release-PR body for sibling bumps, but the `grep -oP` slip is the kind of environment mistake a weaker model is likelier to make; net match was acceptable.
|
|
76
|
-
- **Escalation-delay tracking** — no `rabbit-hole`; the longest single-issue run was the ~5-call `mock.calls[0]!` diagnosis, which was methodical (read → read → edit → lint → test), not thrashing.
|
|
77
|
-
- **Feedback-loop gap analysis** — no gap: `pnpm run lint` and `vitest run` ran before each commit and `fallow dead-code` ran from the repo root before push, exactly as the templates prescribe.
|
|
78
|
-
|
|
79
|
-
### Changes made
|
|
80
|
-
|
|
81
|
-
1. `.pi/skills/testing/SKILL.md` — added a § Test assertions bullet: assert mock calls with `toHaveBeenCalledWith(...)` rather than `fn.mock.calls[0]![0]`, since a typed `vi.fn` makes the call tuple non-optional and the `!` trips `@typescript-eslint/no-unnecessary-type-assertion`.
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
issue: 377
|
|
3
|
-
issue_title: "Split widget delegation out of SubagentRuntime"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Retro: #377 — Split widget delegation out of SubagentRuntime
|
|
7
|
-
|
|
8
|
-
## Stage: Planning (2026-06-15T23:16:29Z)
|
|
9
|
-
|
|
10
|
-
### Session summary
|
|
11
|
-
|
|
12
|
-
Produced the implementation plan for removing the `widget` field and five relay methods from `SubagentRuntime`.
|
|
13
|
-
Investigation surfaced that the issue's stated approach ("construct the widget before its consumers, pass the handle to `NotificationManager`") is infeasible as written — `NotificationManager` is a transitive *dependency* of the widget, forming a genuine construction cycle (`NotificationManager → widget → manager → observer → NotificationManager`).
|
|
14
|
-
The plan dissolves the cycle instead of relocating its late seam.
|
|
15
|
-
|
|
16
|
-
### Observations
|
|
17
|
-
|
|
18
|
-
- **Operator steer #1 (seam placement):** rejected both a setter on the observer and a forward-referenced `let widget` closure, citing "no setters, instantiate ready-to-work, constructor DI" (principle 8).
|
|
19
|
-
The forward-ref option would also have trip `prefer-const` and reintroduced the exact eslint-disable smell Phase 17 Step 1 deleted.
|
|
20
|
-
- **Operator steer #2 (tidy-first, Kent Beck):** prompted the prep/easy decomposition.
|
|
21
|
-
The hard, cycle-breaking work (dissolve `NotificationManager`'s widget dependency by giving `AgentWidget` self-seeding of `finishedTurnAge`) lands first as a behavior-preserving commit; the relay-method removal then becomes a mechanical "easy change."
|
|
22
|
-
- **Behavior-preservation argument:** the widget's 80ms timer is always running at a background completion (the agent was active), and linger expiry is turn-based, so seeding ≤80ms later lands in the same turn — rendered outcome is identical.
|
|
23
|
-
This is the load-bearing claim; the new `agent-widget.test.ts` self-seed test pins it.
|
|
24
|
-
- **Three seam options recorded** in the plan's Design Overview for traceability: late-observer setter (rejected), forward-ref closure (rejected), dissolve (chosen).
|
|
25
|
-
- **Scope guard:** kept foreground-runner's explicit `markFinished` (idempotent) rather than removing it, and deferred the event-subscription widget model to Phase 18.
|
|
26
|
-
Two open questions logged (single `AgentToolWidget` vs. split deps; redundancy of foreground `markFinished`).
|
|
27
|
-
- **Not breaking / not public:** `runtime.ts` is internal (not in the rolled `service.ts` type bundle), so `verify:public-types` is not required.
|
|
28
|
-
- **Invariants at risk** flagged against Phase 17 Steps 1 and 5 (forward-ref dance, `index.ts` line budget); grep acceptance checks fold into the final implementation step.
|
|
29
|
-
|
|
30
|
-
## Stage: Implementation — TDD (2026-06-15T20:19:00Z)
|
|
31
|
-
|
|
32
|
-
### Session summary
|
|
33
|
-
|
|
34
|
-
Executed all four planned steps in order: widget self-seeding (`feat`), `NotificationManager` widget-dependency dissolve (`refactor`), direct widget injection + relay-method removal (`refactor`), and the architecture-roadmap update (`docs`).
|
|
35
|
-
Test count went 1009 → 1005 (+3 widget self-seed tests, −7 removed relay/field tests).
|
|
36
|
-
All deterministic checks green; pre-completion reviewer returned WARN (non-blocking).
|
|
37
|
-
|
|
38
|
-
### Observations
|
|
39
|
-
|
|
40
|
-
- The tidy-first sequencing held up exactly as planned: Step 2 broke the cycle while the runtime relay methods were still in place (repo stayed green), making Step 3's export/field removal a clean atomic change.
|
|
41
|
-
- The cycle dissolve produced two **stale fallow suppressions** on `AgentWidget.setUICtx`/`onTurnStart` — they were `unused-class-member`-suppressed because the methods were previously reached only through the runtime relay; direct injection made them visibly used.
|
|
42
|
-
Removed both; amended into the Step 3 refactor commit.
|
|
43
|
-
- Widget-class testing required constructing `AgentWidget` with a cast manager stub (`as unknown as SubagentManager`) and a recording `UICtx`; observability of the private `finishedTurnAge` is via the `setWidget` clear-vs-register signal, which cleanly distinguishes seeded-then-aged-out from never-seeded.
|
|
44
|
-
- `sed` was needed for the runner/spawner test files because the widget arg appeared both inline and as standalone multiline-call lines; a `^\s*runtime,$` line match safely retargeted only the widget positional arg (never `runtime.agentActivity`).
|
|
45
|
-
- Two commit-hygiene corrections: an `index.ts` comment-trim fixup was first amended into the `docs` commit by mistake, then moved into the Step 3 `refactor` commit via `reset --soft` + selective re-stage (fixups must not land in `docs:` commits).
|
|
46
|
-
- **Reviewer verdict: WARN.**
|
|
47
|
-
Sole finding: `index.ts` is 177 lines (Step 5's aspirational "<170" was already overshot at its own landing, 177); the comment trim kept Step 6 net-neutral.
|
|
48
|
-
Cosmetic, prose-pinned only, non-blocking.
|
|
49
|
-
- Cross-step invariants verified by grep: no `let widget` / `prefer-const` forward-ref (Step 1), no `runtime.widget` / `.widget =` / `WidgetLike` anywhere (the issue's core outcome).
|
|
50
|
-
|
|
51
|
-
## Stage: Final Retrospective (2026-06-16T00:30:08Z)
|
|
52
|
-
|
|
53
|
-
### Session summary
|
|
54
|
-
|
|
55
|
-
The issue shipped cleanly across plan → TDD → ship as `pi-subagents-v16.3.0`, closing #377 and resolving Phase 17 Step 6.
|
|
56
|
-
The design arc was the highlight: an operator-driven pivot from the issue's stated approach (relocate the late seam) to dissolving the construction cycle entirely, sequenced tidy-first so the hard cycle-break landed before the mechanical relay removal.
|
|
57
|
-
The friction was concentrated in mechanical `Edit`-tool execution, not in design or verification.
|
|
58
|
-
|
|
59
|
-
### Observations
|
|
60
|
-
|
|
61
|
-
#### What went well
|
|
62
|
-
|
|
63
|
-
- **Operator design steers compounded into the right architecture.**
|
|
64
|
-
The planning `ask_user` surfaced the construction cycle; the operator's "no setters, instantiate ready-to-work" steer eliminated two of three seam options, and the Kent Beck "make the change easy" prompt (turn 21) produced the tidy-first decomposition.
|
|
65
|
-
This is the pattern working as intended: the agent finds the constraint, the operator picks the principle, the agent verifies feasibility before committing.
|
|
66
|
-
- **Behavior-preservation was argued before it was coded.**
|
|
67
|
-
The ≤80ms-within-same-turn linger argument was established in planning and pinned by three new `agent-widget.test.ts` tests in TDD — no trial-and-error on the load-bearing claim.
|
|
68
|
-
- **Tidy-first sequencing kept the repo green at every commit.**
|
|
69
|
-
Step 2 broke the cycle while the relay methods still existed, making Step 3's export removal a clean atomic change.
|
|
70
|
-
- **Cheap model handled ship correctly.**
|
|
71
|
-
Ship ran on `opencode-go/deepseek-v4-flash` and executed the full mechanical sequence (push, CI watch, release-now `ask_user`, the `UNSTABLE` merge-state edge case via `gh pr merge`) with zero errors — an appropriate model-task match, not a mismatch.
|
|
72
|
-
|
|
73
|
-
#### What caused friction (agent side)
|
|
74
|
-
|
|
75
|
-
- `other` (tool friction) — `Edit` on `runtime.ts` failed twice (turns 45, 47) trying to delete the widget-delegation block bounded by a decorative `// ── Widget delegation methods ────` comment rule; the long run of `─` could not be reproduced exactly.
|
|
76
|
-
Fell back to `sed -i '' '75,104d'`, which over-deleted the class-closing brace and the next doc-comment opener (turn 51), needing a repair edit (turn 52).
|
|
77
|
-
Impact: ~5 extra tool calls and a near-miss structural breakage on a single block deletion.
|
|
78
|
-
- `instruction-violation` (user-caught) — the operator interjected "Use unicode literal characters, not escapes, per system prompt" (turn 49).
|
|
79
|
-
The system prompt already carries an "Edit Tool and Unicode Characters" section; the box-drawing match failures read to the operator as escape misuse.
|
|
80
|
-
Impact: one user correction; reinforced the same `Edit`-matching friction above.
|
|
81
|
-
- `other` (malformed payload) — the first `architecture.md` Step 6 edit was rejected for an invalid `oldText_unused` property I invented (turn 87), followed by empty assistant turns and two operator "Try again" nudges (turns 89, 94) before a clean single edit landed (turn 95).
|
|
82
|
-
Impact: ~4 turns + 2 user nudges on one prose edit.
|
|
83
|
-
- `other` (commit hygiene) — the `index.ts` comment-trim fixup was first amended into the `docs` commit (turn 108), then moved into the `refactor` commit via `reset --soft` (turn 109); separately, the stale-suppression fixup required the same maneuver during TDD.
|
|
84
|
-
Both self-identified and corrected.
|
|
85
|
-
Impact: two extra `reset --soft` + re-stage cycles; no rework to shipped code.
|
|
86
|
-
|
|
87
|
-
#### What caused friction (user side)
|
|
88
|
-
|
|
89
|
-
- The "no setters, instantiate ready-to-work" principle that drove the entire design only surfaced when the planning `ask_user` forced it.
|
|
90
|
-
Stated in the issue body or a standing design note, it would have let planning reach the dissolve approach without the round-trip — though the round-trip was cheap and the outcome correct.
|
|
91
|
-
|
|
92
|
-
### Diagnostic details
|
|
93
|
-
|
|
94
|
-
- **Model-performance correlation** — Planning and TDD ran on `anthropic/claude-opus-4-8` (judgment-heavy design + implementation; appropriate).
|
|
95
|
-
The pre-completion-reviewer subagent ran on `anthropic/claude-sonnet-4-6` per its frontmatter (appropriate for review).
|
|
96
|
-
Ship ran on `opencode-go/deepseek-v4-flash` — a cheap model on mechanical orchestration, completed without error.
|
|
97
|
-
No mismatch found.
|
|
98
|
-
- **Escalation-delay tracking** — no `rabbit-hole` exceeded the 5-consecutive-call threshold; the `Edit`/`sed` friction was 2–3 calls per location and self-resolved.
|
|
99
|
-
- **Unused-tool detection** — nothing notable; the friction points were mechanical edits, not missing context, so no Explore/`colgrep` dispatch would have helped.
|
|
100
|
-
- **Feedback-loop gap analysis** — verification was incremental and exemplary: `pnpm run check` after the shared-interface change in Step 2 (turn 43) and again mid-Step-3 (turns 69, 75, 78, 81); per-file `vitest run` after each Red and Green; `fallow dead-code` from repo root before ship.
|
|
101
|
-
No end-only verification anti-pattern.
|
|
102
|
-
|
|
103
|
-
### Changes made
|
|
104
|
-
|
|
105
|
-
1. `AGENTS.md` — appended two sentences to the "Edit tool batches" subsection: anchor edits on adjacent unique code lines rather than decorative `─`/`═` comment rules, and re-read after a `sed` line-number deletion to confirm an enclosing brace survived.
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
issue: 378
|
|
3
|
-
issue_title: "Consolidate lifecycle test fixtures"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Retro: #378 — Consolidate lifecycle test fixtures
|
|
7
|
-
|
|
8
|
-
## Stage: Planning (2026-06-15T00:00:00Z)
|
|
9
|
-
|
|
10
|
-
### Session summary
|
|
11
|
-
|
|
12
|
-
Planned Phase 17 Step 7 — consolidating the lifecycle test clone families into shared/file-local helpers.
|
|
13
|
-
Produced `docs/plans/0378-consolidate-lifecycle-test-fixtures.md` with a six-step lift-and-shift TDD order and committed it.
|
|
14
|
-
|
|
15
|
-
### Observations
|
|
16
|
-
|
|
17
|
-
- The issue body is stale relative to `main`: it cites five families across six files (including `concurrency-queue.test.ts` and a 766-LOC `subagent-manager.test.ts`).
|
|
18
|
-
Measuring with `fallow dupes -r packages/pi-subagents` against today's `main` shows **four** lifecycle families — Steps 1–6 already removed the queue (`concurrency-queue.test.ts` → `concurrency-limiter.test.ts`) and the `subagent.test.ts`/`concurrency-limiter.test.ts` families.
|
|
19
|
-
The plan is written against the measured current state, not the issue snapshot.
|
|
20
|
-
- Design call: promote to `test/helpers/` only the genuinely cross-file duplication (the `createSubagentSession`-test mock-session builder, shared by `create-subagent-session.test.ts` and `create-subagent-session-extension-tools.test.ts` — `createFactorySession`).
|
|
21
|
-
The manager and `subagent-session` families are intra-file (fallow recommends same-file extraction), so they get file-local helpers.
|
|
22
|
-
Force-promoting intra-file families to `test/helpers/` would manufacture cross-file coupling that does not exist.
|
|
23
|
-
- Resisted extracting the `io.createSession.mockResolvedValue(...)` + `createSubagentSession(...)` invoke pair into a helper — two lines with per-test varying overrides; wrapping the system-under-test call would be procedure-splitting, not design improvement.
|
|
24
|
-
- Invariants at risk flagged: Step 1/Step 3's "every spawned agent has a `promise` at spawn" (pinned by the queued-promise test) and Step 3's "zero external `.promise`/`.notification` writes outside `subagent.ts`" (grep-verifiable).
|
|
25
|
-
`arrangeQueuedPair()` must return the queued id; Step 4 folds in a re-grep.
|
|
26
|
-
- Baseline: package test duplication 669 lines / 3.3% across 20 files; the four lifecycle families total ~122 lines, so Step 7 alone should land below the 600-line goal (~547).
|
|
27
|
-
Flagged as an Open Question pending the Step 6 `fallow dupes` measurement.
|
|
28
|
-
- Not breaking — test-only, no `src/`, public-surface, or behavior change.
|
|
29
|
-
|
|
30
|
-
## Stage: Implementation — TDD (2026-06-15T23:10:00Z)
|
|
31
|
-
|
|
32
|
-
### Session summary
|
|
33
|
-
|
|
34
|
-
Executed the lifecycle test fixture consolidation across 8 commits.
|
|
35
|
-
Added a shared `createFactorySession` builder, migrated the four lifecycle clone families, and (on operator steer) folded `create-subagent-session-extension-tools.test.ts` into `create-subagent-session.test.ts`, deleting the file.
|
|
36
|
-
Package test duplication dropped 669 → 512 lines (under the 600 goal); test count 1005 → 1010 (+5 `createFactorySession` self-tests); test files 64 → 63.
|
|
37
|
-
Pre-completion reviewer: PASS.
|
|
38
|
-
|
|
39
|
-
### Observations
|
|
40
|
-
|
|
41
|
-
- Plan premise was wrong on one point: extracting `createFactorySession` alone did **not** collapse the create-subagent-session families — the dominant clone was the arrange-act invoke block, not the builder.
|
|
42
|
-
I first extracted a `runCreate`/`runCreateWith` act-helper to hit the metric, then the operator flagged it: mixing arrange + act hides the system under test, and arranges should be grouped by `describe`.
|
|
43
|
-
Reworked to AAA — describe-scoped `beforeEach` for arrange, `createSubagentSession(...)` act kept explicit per test.
|
|
44
|
-
- The operator relaxed the roadmap's "lifecycle families ≤ 1" Outcome.
|
|
45
|
-
Two families remain by design (the repeated act with test-specific arrange); documented as intentional in `architecture.md` Step 7.
|
|
46
|
-
Lesson recorded: a clone-count metric is a weak signal for *test* code — AAA structure beats it, and chasing the metric produced the wrong abstraction before it was caught.
|
|
47
|
-
- `programTurns(session, listeners, turns)` is a legitimate arrange helper for the turn-limit tests (turn count is the meaningful input, not a discriminator flag); removed the restated-boundary comments per `code-design` (names/args over comments).
|
|
48
|
-
- Folding the extension-tools tests was safe because the recursion guard reads only the session mock's `getActiveToolNames`/`setActiveToolsByName`; the agent config and `type` don't affect those assertions (mocked `io.createSession` ignores `cfg.toolNames`).
|
|
49
|
-
- Surfaced three overlapping session-mock builders (`createMockSession`, `createSubagentSessionStub`, `createFactorySession`); filed [#412] as a follow-up rather than expanding #378 scope.
|
|
50
|
-
- Cross-step invariants verified intact: queued-agent "promise at spawn" ([#374]) test preserved through the `arrangeQueuedPair` extraction; zero external `.promise`/`.notification` writes in `test/lifecycle/`.
|
|
51
|
-
|
|
52
|
-
## Stage: Final Retrospective (2026-06-15T23:45:00Z)
|
|
53
|
-
|
|
54
|
-
### Session summary
|
|
55
|
-
|
|
56
|
-
Shipped Phase 17 Step 7 across plan → TDD → ship in one continuous session: 11 `test:`/`docs:` commits, package test duplication 669 → 512 lines, test files 64 → 63, tests 1005 → 1010.
|
|
57
|
-
The dominant arc was a metric-driven detour — an act-wrapping helper extracted to satisfy the roadmap's "families ≤ 1" target, caught by the operator and reworked into an AAA structure (describe-scoped arrange, visible act).
|
|
58
|
-
CI passed, issue closed, no release (test/docs commits only); follow-up [#412] filed.
|
|
59
|
-
|
|
60
|
-
### Observations
|
|
61
|
-
|
|
62
|
-
#### What went well
|
|
63
|
-
|
|
64
|
-
- The "no sacred cows" invitation → structured `ask_user` multi-select produced a real improvement: deleting the redundant `create-subagent-session-extension-tools.test.ts` entirely (#1) and filing [#412], rather than accepting the status quo.
|
|
65
|
-
Self-critique under explicit invitation surfaced a whole-file removal the plan had not considered.
|
|
66
|
-
- Verification was incremental, not end-only: `pnpm run check` plus a per-file `vitest run` plus a `fallow dupes` re-measurement ran between every TDD step, so each consolidation's effect on the clone count was visible immediately.
|
|
67
|
-
- Cross-step invariant discipline held: the queued-agent "promise at spawn" ([#374]) test survived the `arrangeQueuedPair` extraction, and the grep for external `.promise`/`.notification` writes stayed at zero.
|
|
68
|
-
- The release-timing gate correctly fired on the phased roadmap (Phase 17 Step 7 of 9) and surfaced the batch-vs-now choice instead of releasing silently.
|
|
69
|
-
|
|
70
|
-
#### What caused friction (agent side)
|
|
71
|
-
|
|
72
|
-
- `wrong-abstraction` — extracted a `runCreate`/`runCreateWith` act-helper that bundled arrange + the `createSubagentSession(...)` act, hiding the system under test, purely to drive the clone count to the roadmap's "families ≤ 1" target.
|
|
73
|
-
User-caught.
|
|
74
|
-
Impact: one commit (`96acc6c7`) committed then undone via `git reset --soft HEAD~1` and reworked into the AAA structure (`14375b07`); ~1 commit of rework, caught before ship.
|
|
75
|
-
- `missing-context` — the plan asserted `createFactorySession` was "the larger half of each clone" without decomposing the clone; the post-Step-3 `fallow dupes` measurement showed the arrange-act invoke block was the dominant half, which is what drove the wrong-abstraction detour.
|
|
76
|
-
Impact: a wrong planning premise that fed the rework above.
|
|
77
|
-
A `fallow dupes --trace <file:line>` per-line decomposition during planning would have shown the split.
|
|
78
|
-
- `other` (tool misuse) — first invoked `set_session_name` as a shell command (`command not found`), then self-corrected to the tool call.
|
|
79
|
-
Impact: one wasted call, no rework.
|
|
80
|
-
|
|
81
|
-
#### What caused friction (user side)
|
|
82
|
-
|
|
83
|
-
- The operator's redirect on arrange/act mixing landed after the act-helper was already committed.
|
|
84
|
-
The natural self-checkpoint was earlier: when the Step-3 measurement contradicted the plan's "builder is the larger half" premise, that contradiction was itself a deviation worth surfacing via `ask_user` ("wrap the act to hit the metric, or accept the residual for better structure?") rather than pushing forward to the helper.
|
|
85
|
-
Framed as opportunity: treat a plan's quantitative premise proving wrong as a stop-and-ask trigger, not a cue to escalate the abstraction.
|
|
86
|
-
|
|
87
|
-
### Diagnostic details
|
|
88
|
-
|
|
89
|
-
- **Model-performance correlation** — one subagent dispatch (`pre-completion-reviewer`) on a judgment-heavy review task; appropriate match, no mismatch.
|
|
90
|
-
- **Escalation-delay tracking** — no rabbit-hole: the wrong-abstraction was a single committed approach redirected by the operator, not a repeated-error loop, so no 5+ consecutive same-error sequence.
|
|
91
|
-
- **Unused-tool detection** — `fallow dupes --trace <file:line>` was available and would have decomposed the clone into builder-vs-act portions during planning; it was not used, and its absence is what let the wrong premise stand.
|
|
92
|
-
- **Feedback-loop gap analysis** — no gap; checks ran incrementally after each step.
|
|
93
|
-
|
|
94
|
-
### Changes made
|
|
95
|
-
|
|
96
|
-
1. `.pi/skills/testing/SKILL.md` (§ Test organization) — added a rule to group shared arrange in a describe-scoped `beforeEach`, keep the act explicit, and not wrap the system-under-test call to eliminate a duplication-metric clone.
|
|
97
|
-
2. `.pi/prompts/tdd-plan.md` — sharpened the deviation rule: a quantitative target (LOC, clone count, complexity) that does not fall out as predicted is a stop-and-ask deviation, re-decided via `ask_user` rather than forcing the number.
|
|
98
|
-
|
|
99
|
-
[#374]: https://github.com/gotgenes/pi-packages/issues/374
|
|
100
|
-
[#412]: https://github.com/gotgenes/pi-packages/issues/412
|