feat(memory): deterministic consolidation health pass (A6)#589
Merged
Conversation
The safe, non-LLM slice of Workstream A6. `MemorySpaceService.consolidate`
(editor+) + `POST /memory/spaces/{id}/consolidate` → a `ConsolidationReport`.
Auto-fixes only storage hygiene: orphaned content-addressed objects — keys
under a space's prefix that no manifest entry or the index pointer references
(leaks from crashed/raced writes) — are GC'd (new `MemorySpaceStore.list_keys`
drives it). Everything that needs a judgment call is *reported, not mutated*:
- duplicate content across slugs (same content hash) — which slug survives is
semantic, so it's flagged, never auto-merged;
- dead `[[slug]]` wikilinks in MEMORY.md — reported; opt-in `stripDeadLinks`
unlinks them (they point nowhere) while preserving the surrounding prose;
- over-cap entry counts (`MEMORY_SPACE_INDEX_CAP`, default 200) — flagged, never
auto-evicted.
This deliberately does not merge/evict/rewrite durable memory — that's deferred
to the LLM consolidation pass (Workstream B era), which extends this exact
`consolidate()` seam once agentic writes create real duplication/staleness to
act on. On-demand only for now; scheduler/threshold auto-run and SPA surfacing
are follow-ups.
Tests: 8 service (healthy report, orphan GC + skip, dup-report-no-merge,
dead-link report + strip-keeps-prose, over-cap flag, editor gate) + 4 route
(report shape, no-body, viewer 403, flag-off 404) + 3 store (list_keys prefix
scoping / empty / disabled). 104 memory + boundary tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Workstream A6 — the safe, non-LLM slice of consolidation, chosen deliberately (see Design note below). Adds
MemorySpaceService.consolidate(editor+) andPOST /memory/spaces/{id}/consolidate→ aConsolidationReport.The pass auto-fixes only storage hygiene, and reports everything that needs a judgment call:
[[slug]]wikilinks in MEMORY.mdstripDeadLinksunlinks them (they point nowhere) while keeping the proseMEMORY_SPACE_INDEX_CAP, default 200)overCap) — never auto-evictedNew
MemorySpaceStore.list_keys(space_id)(paginatedlist_objects_v2under the space prefix) drives the GC.Design note — why deterministic-only, and the reconciliation
A6 as specced also merges duplicates, fixes stale entries, and enforces a hard index cap. Those are all LLM-driven, destructive edits to a user's durable "second brain" — a bad merge loses info, a bad "stale" call deletes a live fact — and doing them well needs real corpus data to tune against. That data doesn't exist yet: nothing writes to a space automatically until Workstream B (agentic writes). So this PR ships only what's provably safe and useful today, and establishes the
consolidate()seam that the B-era LLM pass extends.One reconciliation from the plan: "hard index-cap enforcement" is implemented as detect-and-report, not auto-evict — deciding which entry to drop is a judgment call, and auto-deleting durable memory would violate "no rewrites of durable memory." The cap becomes a health signal (
overCap), not a deleter.Scope boundaries (intentional, called out per the no-silent-caps rule)
Tests
applyGc=falseskip, duplicate-reported-without-merge, dead-link report,stripDeadLinksunlinks-but-keeps-prose, over-cap flag (no eviction), editor gate.list_keysprefix scoping / empty space / disabled.104 memory + import-boundary tests green.
Spec
docs/specs/user-markdown-memory.md— A6 marked ✅ (deterministic slice), with the LLM pass explicitly deferred to Workstream B on the same seam.🤖 Generated with Claude Code