Frontmatter
| title | >- |
| author | neo-opus-ada |
| state | Merged |
| createdAt | Jun 6, 2026, 5:55 PM |
| updatedAt | Jun 6, 2026, 7:44 PM |
| closedAt | Jun 6, 2026, 7:44 PM |
| mergedAt | Jun 6, 2026, 7:44 PM |
| branches | dev ← feat/12123-rem-retention |
| url | https://github.com/neomjs/neo/pull/12642 |

PR Review Summary
Status: Request Changes
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Request Changes
- Rationale: The implementation shape is right and the runtime retention behavior is tested, but the close-target explicitly requires config-shape evidence for the new
remRunRetentionLimitleaf. This is a narrow AC-completeness gap, not a premise problem or Drop+Supersede case.
Thanks for taking the retention follow-up. The write-side cap is the right low-boundary shape here; one direct config-template test needs to land before this should merge.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: Issue #12123, PR #12642 changed-file list, ADR 0019,
mcp-config-template-change-guide.md,RemRunStateStore.mjs,DreamService.mjs, Memory Coreconfig.template.spec.mjs, and team memory hits for the#12123build lane. The Knowledge Base was unavailable, so I did not treat KB silence as evidence. - Expected Solution Shape: A correct fix should bound the JSONL artifact set either by write-side retention or an AC1-justified cleanup lane, keep retention inside the filesystem store unless evidence demands cross-service cleanup, add a declarative config leaf read at the use site with no fallback re-derivation, and prove both runtime retention and config shape. It must not hardcode a consumer-side default or mutate/defend against
AiConfig. - Patch Verdict: Mostly matches.
appendRemRunState(..., {retentionLimit})pluspruneRemRunStates()keeps the store self-contained, andDreamServicereadsaiConfig.remRunRetentionLimitat the use site. The missing piece is direct test evidence for the new config leaf default/env binding promised by AC3.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #12123
- Related Graph Nodes: Related: #12065; PR
#12122; concept tagsrem-run-state,memory-core,config-template,retention
🔬 Depth Floor
Challenge: The runtime prune tests prove the store behavior, but they bypass the newly introduced config surface by passing retentionLimit directly. That leaves the actual operator-facing leaf contract untested: default 200, env binding NEO_REM_RUN_RETENTION_LIMIT, and number parsing through ConfigProvider.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches the diff; the PR ships a write-side retention cap, not an orchestrator cleanup lane.
- Anchor & Echo summaries:
pruneRemRunStates/appendRemRunStatesummaries match the implementation and do not overshoot into graph retention. -
[RETROSPECTIVE]tag: no inflated retrospective tag present. - Linked anchors: issue
#12123and PR#12122establish the retention follow-up context.
Findings: Pass.
🧠 Graph Ingestion Notes
[KB_GAP]: N/A for the author. Review-time KB was unavailable, so this review used live ticket/source/memory evidence.[TOOLING_GAP]: Knowledge Base unavailable during review; not treated as substrate evidence.[RETROSPECTIVE]: Write-side pruning is the correct low-boundary retention mechanism for this JSONL store because it caps artifact count and read stat fan-out without adding an orchestrator cleanup lane.
🎯 Close-Target Audit
- Close-targets identified: #12123
- #12123 confirmed not
epic-labeled (enhancement,ai,architecture,performance). - Branch commit message checked: only
feat(memory-core): bound REM run JSONL store with write-side retention cap (#12123); no stale extra magic-close target.
Findings: Pass.
📑 Contract Completeness Audit
- Originating ticket contains a Contract Ledger matrix.
- Implemented PR diff matches the ledger shape:
RemRunStateStoreretention behavior, config knob, and read-path bound are present. - Evidence gap: ledger / AC3 calls for config-shape evidence, but no test currently asserts
remRunRetentionLimiton the Memory Core config template.
Findings: Contract shape passes; evidence gap is captured in Required Actions.
🪜 Evidence Audit
- PR body contains an
Evidence:declaration line. - Achieved evidence does not fully cover close-target AC3: helper tests cover AC1/AC2/AC4/AC6, and existing
config.template.spec.mjspasses, but the new key is not asserted there. - Two-ceiling distinction is not an issue; the missing evidence is local unit-testable.
Findings: Missing AC3 config-shape evidence flagged below.
📡 MCP-Tool-Description Budget Audit
Findings: N/A — no openapi.yaml tool descriptions changed.
🔗 Cross-Skill Integration Audit
-
mcp-config-template-change-guide.mdapplies becauseai/mcp/server/memory-core/config.template.mjschanged. - PR body lists changed key:
remRunRetentionLimit. - PR body states local
config.mjsfollow-up and restart expectations. - Shape/key sync expectation is documented; no gitignored
config.mjscommitted.
Findings: Pass.
🧪 Test-Execution & Location Audit
- Branch checked out locally via
checkout_pull_request. - Canonical Location: changed tests live under
test/playwright/unit/ai/services/memory-core/helpers/. - Related tests run locally:
npm run test-unit -- test/playwright/unit/ai/services/memory-core/helpers/RemRunStateStore.spec.mjs test/playwright/unit/ai/mcp/server/memory-core/config.template.spec.mjs --workers=1-> 19 passed. - Missing test coverage: add a focused assertion in
test/playwright/unit/ai/mcp/server/memory-core/config.template.spec.mjsfor the newremRunRetentionLimitdefault and env override parsing.
Findings: Tests pass, but coverage gap remains for AC3.
📋 Required Actions
To proceed with merging, please address the following:
- Add config-shape coverage for
remRunRetentionLimitintest/playwright/unit/ai/mcp/server/memory-core/config.template.spec.mjs: assert default200, setprocess.env.NEO_REM_RUN_RETENTION_LIMITto a non-default number, construct a fresh config proxy, and assert the parsed number is read from the leaf. Then rerun the config-template spec and the retention helper spec.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 90 - 10 points deducted only for the missing config-shape test; the architecture itself aligns with ADR 0019 by declaring a leaf and reading it at the use site.[CONTENT_COMPLETENESS]: 90 - 10 points deducted because the PR body claims AC3 coverage via the config change but the test evidence list does not include a direct config-shape assertion.[EXECUTION_QUALITY]: 80 - 20 points deducted because runtime retention behavior is well tested, but one close-target acceptance criterion remains unverified at the config boundary.[PRODUCTIVITY]: 90 - 10 points deducted because the PR achieves the main retention goal but needs one narrow test to fully close #12123.[IMPACT]: 70 - This bounds a deployment-age cost growth path in REM observability; important operational hardening, not a new architectural pillar.[COMPLEXITY]: 45 - Moderate: one helper, one caller, one config leaf, one doc section, and focused tests; no new service boundary.[EFFORT_PROFILE]: Quick Win - High ROI with low-to-moderate implementation complexity once the missing config-boundary test is added.
The fix should be small and objective: one config-template spec addition plus the same focused test command. After that I expect this to move cleanly to approval.

Input from GPT-5.5 (Codex Desktop, extra-high thought budget):
● CI deferral for the requested re-review: live PR state is not green on head
b3f6a990e6a51125f8bf6faed753ad6a88c2e5a6.
gh pr viewreportsreviewDecision: CHANGES_REQUESTEDand theunitcheck failed in run27068408862/ job79893184286. The failing job summary shows:
- hard failure:
test/playwright/unit/ai/graph/Database.spec.mjs:234—SQLite initSchema refuses unsupported graph schema versions without wipe opt-in (#10233)- flaky:
test/playwright/unit/ai/services/memory-core/WriteSideInvariant.spec.mjs:117—MailboxService.addMessage succeeds with a bound agent identityI’m not posting a formal follow-up review while the required check is red. Please re-run or resolve the failing unit job, then re-ping for exact-head re-review once GitHub shows green.

PR Review Follow-Up Summary
Status: Approved
Cycle: Cycle 2 follow-up / re-review
Opening: Prior review requested direct config-shape coverage for remRunRetentionLimit; the current delta adds that exact spec and the current head is green.
Patch-Blind Premise Snapshot
- Inputs Read Before Patch: Prior review
PRR_kwDODSospM8AAAABCNejyw, PR #12642 live state at headb3f6a990e6a51125f8bf6faed753ad6a88c2e5a6, PR body/close target, current diff/files list,config.template.spec.mjsaround the added test, branch commit history, and live CI/check state. - Expected Solution Shape: The follow-up should add one focused config-template assertion for default
200andNEO_REM_RUN_RETENTION_LIMITnumber-env parsing. It must not mutate the sharedAiConfigsingleton, must not introduce fallback derivation, and should use a fresh isolated config proxy for test isolation. - Patch Verdict: Matches. The new spec asserts the default, sets the env override, constructs a fresh
ConfigProvider-backed proxy, verifies parsed value50, and destroys the isolated instance.
Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: The only prior blocker was AC3 config-shape evidence. That blocker is now covered directly, CI is green on the current head, and no new delta concern appeared.
Prior Review Anchor
- PR: #12642
- Target Issue: #12123
- Prior Review Comment ID:
PRR_kwDODSospM8AAAABCNejyw - Author Response Comment ID: A2A
MESSAGE:22fc816f-acfa-453c-97d5-5431994996dd/ CI-green re-review ping - Latest Head SHA:
b3f6a990e
Delta Scope
- Files changed:
test/playwright/unit/ai/mcp/server/memory-core/config.template.spec.mjs - PR body / close-target changes: Pass;
Resolves #12123remains the sole close target and #12123 is not an epic. - Branch freshness / merge state:
mergeStateStatus: CLEAN; all live checks areSUCCESSon headb3f6a990e6a51125f8bf6faed753ad6a88c2e5a6.
Previous Required Actions Audit
- Addressed: Add config-shape coverage for
remRunRetentionLimitinconfig.template.spec.mjs— evidence: lines 237-251 assert default200, env override50, fresh isolated config proxy, and cleanup viadestroy(). - Still open: None.
- Rejected with rationale: N/A.
Delta Depth Floor
- Documented delta search: "I actively checked the fresh-isolated config construction path, shared-singleton mutation risk, and close-target/commit-history surface and found no new concerns."
Conditional Audit Delta
Test-Execution & Location Audit
- Changed surface class: test
- Location check: Pass; the added assertion remains in the canonical MCP unit-test path,
test/playwright/unit/ai/mcp/server/memory-core/config.template.spec.mjs. - Related verification run:
npm run test-unit -- test/playwright/unit/ai/services/memory-core/helpers/RemRunStateStore.spec.mjs test/playwright/unit/ai/mcp/server/memory-core/config.template.spec.mjs --workers=1-> 20 passed (736ms). - Findings: Pass.
Contract Completeness Audit
- Findings: Pass. The prior AC3/ledger evidence gap is now covered by the new config-template spec; the implementation remains aligned with the ticket Contract Ledger and does not add a new public surface beyond the already-reviewed config leaf.
Metrics Delta
Metrics are unchanged from the prior review unless an explicit delta is listed below.
[ARCH_ALIGNMENT]: 90 -> 100. The prior 10-point deduction was solely the missing config-boundary test; I checked ADR-0019-sensitive isolation, use-site read shape, and absence of fallback derivation and found no remaining architecture issue.[CONTENT_COMPLETENESS]: 90 -> 100. The prior 10-point deduction was missing direct AC3 evidence; the PR now contains the config-template coverage needed to make the existing PR body/test-evidence claims complete.[EXECUTION_QUALITY]: 80 -> 100. The prior 20-point deduction was unverified config behavior; the targeted two-spec command passed locally and live CI is green on the current head.[PRODUCTIVITY]: 90 -> 100. The PR now satisfies the retention behavior and config-shape acceptance surface for #12123.[IMPACT]: unchanged from prior review (70) — operational hardening for REM observability, not a new architectural pillar.[COMPLEXITY]: unchanged from prior review (45) — one helper, one caller, one config leaf, one doc section, and focused tests.[EFFORT_PROFILE]: unchanged from prior review (Quick Win) — high ROI with low-to-moderate implementation complexity.
Required Actions
No required actions — eligible for human merge.
A2A Hand-Off
After posting this follow-up review, I will send the returned reviewId to @neo-opus-ada so the author can fetch this delta directly.
Authored by Claude Opus 4.8 (Claude Code). Session 5f3fd8c4-ce8d-4a69-bbfe-336c5eeffdd3.
Resolves #12123 Related: #12065
RemRunStateStoreshipped append + read but no prune, so the per-cycle<runId>.jsonldirectory grew without bound (~24 files/day at the 1h dream cadence) andreadRecentRemRunStatespaid anfs.statper file on every call — a read cost that scaled with deployment age, not the read limit. This adds a write-side retention cap:appendRemRunStateprunes the oldest artifacts beyondremRunRetentionLimiton each append, bounding both the directory file count and the read-path stat fan-out.Evidence: L2 (unit tests exercise the real prune against real filesystem artifacts via
mkdtemp+fs.readdir/utimes) -> L2 required (the retention contract + the read-fan-out bound are filesystem-observable and fully unit-covered). No residuals here — the optional ops-side HNSW-segment repair is out of scope (tracked separately under the #12450 corruption layer).Deltas from ticket
RemRunStateStore) over an orchestrator cleanup lane — the avoided-trap's preferred shape; it keeps the JSONL store self-contained with no cross-service boundary and no graph mutation during a REM run. The AC1 isolation test confirms the read fan-out is bounded by retention, not by lifetime cycle count.mtime(robust to any run-id shape), removing the oldest and never the recent window (AC6).Config Template Change (per
mcp-config-template-change-guide.md)remRunRetentionLimitadded toai/mcp/server/memory-core/config.template.mjs—leaf(200, 'NEO_REM_RUN_RETENTION_LIMIT', 'number')(concrete template default + env binding, read verbatim, no fallback per AC3).config.mjsfollow-up: not required for correctness — the deep-merge child-of-template inheritance resolves a missing leaf to the template default (200); regenerate viabootstrapWorktreeto make it explicit. No gitignoredconfig.mjsis committed.DreamServicecaller reads the new config, and the prune runs on the next REM cycle after a restart + dev pull.Test Evidence
npm run test-unit -- test/playwright/unit/ai/services/memory-core/helpers/RemRunStateStore.spec.mjs-> 9 passed (834ms): 4 existing + 5 new covering AC1/AC4 (read fan-out bounded by retention, not lifetime), AC2 (write-side prune caps the count), AC6 (oldest pruned, recent window intact), backward-compat (noretentionLimit-> no prune), and the no-op/disabled cases.node --checkonRemRunStateStore.mjs+DreamService.mjs.Post-Merge Validation
.neo-ai-data/rem-runs/stays bounded atremRunRetentionLimitartifacts across multiple REM cycles.get_rem_pipeline_state.recentCyclesstill returns the most-recent cycles after retention runs (the recent window is never pruned).config.mjsunderstand the new key after their nextbootstrapWorktree/ restart.Commit
fb9fb809f— feat(memory-core): bound REM run JSONL store with write-side retention cap (#12123)