7.4 KiB
issue, issue_title
| issue | issue_title |
|---|---|
| 365 | Encapsulate agent-start cache keys in a `CacheKeyGate` class |
Retro: #365 — Encapsulate agent-start cache keys in a CacheKeyGate class
Stage: Planning (2026-06-09T00:00:00Z)
Session summary
Produced a four-step plan to extract a CacheKeyGate class, replace PermissionSession's four anemic cache methods + two fields with two exposed gate sub-objects, collapse AgentPrepHandler's two ask-then-tell pairs into runIfChanged tells, and remove the test-only-alive shouldApplyCachedAgentStartState.
Confirmed Track A (#362–#364) is closed and shipped, so no permission-session.ts merge coordination is needed despite the roadmap's note.
Observations
- Resolved one genuine design ambiguity via
ask_user: the handler reaches the gates throughreadonlyproperties onPermissionSession(session.activeToolsGate.runIfChanged(...)) rather than through two thin delegating methods. This matches the roadmap's "0 anemic cache accessors / 2 ownedCacheKeyGatesub-objects" target. - Chose run-then-commit ordering for
runIfChanged, unifying the two paths. The prompt path previously committed before its sanitization work; the only observable change is on the throw path (now retried, strictly safer). Flagged in Risks. - Grep confirmed the four session methods and
shouldApplyCachedAgentStartStateare referenced only inbefore-agent-start.tsand three test files — noSKILL.mdor composition-root references. - Step 2 is deliberately a single combined commit: removing the four methods breaks the handler and both test files at once, so the extraction + consumer updates + consumer-test updates must land together.
- The key builders (
createActiveToolsCacheKey,createBeforeAgentStartPromptStateKey) stay; only the comparison helper is removed. - Noted that Track A steps were not individually marked
✓ completeinarchitecture.md; Step 4 of the plan marks this step complete per the package-skill convention, leaving back-fill out of scope.
Stage: Implementation — TDD (2026-06-09T23:41:00Z)
Session summary
Completed all four TDD steps: added CacheKeyGate with 7 unit tests; migrated PermissionSession and AgentPrepHandler to use two readonly gate sub-objects; removed the dead-in-production shouldApplyCachedAgentStartState; marked Phase 5 Step 4 complete in the architecture doc.
Test count: 1903 → 1902 (net −1: removed the dedupe test and four spy-based handler tests; added 7 CacheKeyGate unit tests and 2 behavior-driven handler tests).
All checks pass: pnpm run check, pnpm run lint, pnpm run test, pnpm fallow dead-code.
Observations
- Step 2 was implemented as a single combined commit as planned: removing the four
PermissionSessionmethods broke the handler and both test files simultaneously at the type level, so all consumer updates landed together. Thepnpm run checktype error list cleanly identified exactly the lines to rewrite. - The
cache key methodsdescribe block inpermission-session.test.ts(5 tests) was removed; the three lifecycle "clears cache keys" tests were rewritten to prime gates viarunIfChangedand assert re-arming after the lifecycle call. - The four spy-based handler tests (
vi.spyOn(session, "commitActiveToolsCacheKey")etc.) were replaced with two behavior-driven tests: one assertingsetActiveis called exactly once across repeated identical calls, and one asserting repeated calls return{}. - Pre-completion reviewer returned WARN (not FAIL): one unused
createActiveToolsCacheKeyimport left over intest/before-agent-start-cache.test.tsafter the dedupe test was removed; fixed by amending the step 3 commit before shipping.
Stage: Final Retrospective (2026-06-10T03:51:36Z)
Session summary
Shipped #365 end-to-end across three stages (Planning, TDD, Ship) with one prescriptive roadmap already in place, one ask_user design decision, four clean TDD commits, and a clean release (pi-permission-system-v10.8.0).
The only rework was a single amend to drop an orphaned test import flagged by the pre-completion reviewer.
Execution was clean overall; the notable findings are diagnostic (model routing, lint-output truncation) rather than design or scope failures.
Observations
What went well
- The planning stage found the design already prescribed in
docs/architecture/architecture.md(Phase 5 Track B Step 4) and verified Track A (#362–#364) was closed before assuming no merge coordination — avoided a speculative concurrency worry the roadmap flagged. - The
ask_usergate on the gate-access pattern (readonlyproperties vs. delegating methods) resolved a genuine Law-of-Demeter trade-off up front, so the TDD stage had zero design churn. - Step 2 was correctly planned and executed as one combined commit: the
pnpm run checktype-error list pinpointed exactly the call sites to migrate when the fourPermissionSessionmethods were removed, so the big-bang consumer update landed in one green commit.
What caused friction (agent side)
missing-context(feedback-loop) — the TDD stage ranpnpm run lint 2>&1 | tail -5, which kept only therumdl(markdown) tail and theFound 3 infossummary, truncating away the biomenoUnusedImportswarning for the orphanedcreateActiveToolsCacheKeyimport left after the dedupe test was deleted. Biome reports unused imports at warning level (exit 0), so the lint gate stayed green and the pre-commit hook passed; only the pre-completion reviewer's full-output lint caught it. Impact: one--amendto the step 3 commit, no rework or follow-up commit.
What caused friction (user side)
- None.
User involvement was a single design decision via
ask_user(gate-access pattern), which was the right strategic call to surface.
Diagnostic details
- Model-performance correlation — the entire Ship stage (push, CI watch, stacked-release batching judgment, release-PR body verification,
release_pr_merge) ran onopencode-go/deepseek-v4-flash, a reasoning-weak model. It executed correctly, including the judgment call that#365is an independent track needing no release batching, but release-merge decisions are judgment-bearing; the prescriptive/ship-issueprompt and guard-railedrelease_pr_*tools carried most of the safety. Planning and TDD ran on stronger models (claude-opus-4-8/claude-sonnet-4-6). - Feedback-loop gap analysis —
pnpm run check,pnpm run test, andpnpm run lintwere all run incrementally after each TDD step, not just at the end. The only gap was output truncation (tail -5) on the lint step, not timing — the verification ran at the right moment but its signal was clipped. - Escalation-delay / unused-tool lenses — no
rabbit-holeormissing-context-from-unexplored-code findings; no long error loops; no subagent that should have been dispatched but wasn't (the pre-completion reviewer was dispatched as designed).
Changes made
- Added a TDD-planning rule to
.pi/skills/testing/SKILL.md: when a step deletes a test or test helper, re-check the file's remaining imports for orphans, since biome'snoUnusedImportsis warning-level (exit 0) and the pre-completion reviewer is the only backstop. - Recorded a bidirectional observation (no file change): the Ship stage ran on
opencode-go/deepseek-v4-flash; routing the release-merge stage to a stronger model would harden the batch-vs-release-now judgment in/ship-issue.