fix(memory): efficiency and correctness overhaul for extended + legacy memory#88
Merged
Merged
Conversation
…y memory Consolidates fixes for 21 audited bugs in the memory system. Correctness: - extended: scan-rejected atoms now go through enforceCap before quarantine (regression from #87: quarantine could exceed max_size_mb and wedge the store, since the evictor only evicts trusted atoms) - extended: ReturnAfterBreak summarized the 5 OLDEST atoms (backwards iteration over a newest-first list) instead of the most recent - extended: final recall union was re-sorted by pure RetentionScore, discarding the blended 0.6*similarity+0.4*retention score and the LLM rerank order; the union is now deduped by ID and sorted by composite - extended: extracted atoms dedup by normalized text (refresh CreatedAt, keep higher confidence) instead of accumulating duplicates - extended: eviction now removes association links (no dangling IDs), ForgetAtom works for quarantine-only atoms, UTF-8-safe truncation, quarantine TTL eviction no longer writes under a read lock - loop: skill/episode recall no longer re-runs on every iteration when the result is empty (dedup key was only set on non-empty results) - loop: per-message dedup keys reset between Run/RunWithMessages calls (a repeated identical REPL message previously skipped all memory hooks) - legacy: merge_on_write no longer drops facts on 30-char prefix collisions (new index-based FactStore.ReplaceAt) - legacy: episode Search query cache invalidated on write/promote/prune - legacy: BuildSystemPrompt includes the extended user-state block even when legacy facts are empty, and caches the empty result - legacy: LLM ranker's explicit 'none relevant' is honored instead of being overridden with unranked candidates - legacy: Consolidate enforces the facts char cap and actually trims - extended: fenced/preamble-wrapped LLM JSON (extractor, user-model inference, predictor) now parses instead of silently dropping output Efficiency: - extended: batch per-turn atom adds - one index rebuild per turn instead of one per atom, one assoc.Persist per batch - extended: vector index reuses one embedder across rebuilds so the HTTP embedding cache actually warms (was a full-corpus re-embed over the network per added atom); same sharing for episode dedup + index via embedding.Shared (HTTP backend only; RP stays per-corpus) - extended: recall fan-out cut from ~8 LLM calls to 2 (type filtering over the existing candidate set, no rerank for predicted intents) - extended: one store load per recall query instead of a full atoms.json re-parse per candidate; AnaphoraResolve drops its duplicate search - extended: per-turn extraction + anaphora run async via MemoryManager.RunBackground instead of blocking the ReAct loop - legacy: session-end episode extraction/consolidation are tracked and drained with a bounded 15s wait in Agent.Close, so episodes survive CLI exit instead of dying with the process - extended: background user-model inference guarded against overlap and pending-review duplicates Tests: 30+ new tests across memory/extended/loop/embedding/odek; full suite, -race on memory+loop+embedding, and repo-wide golangci-lint all clean.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
odek | 1931bfe | Commit Preview URL Branch Preview URL |
Jul 22 2026, 09:49 AM |
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.
Consolidated fix for 21 bugs found in a memory-system audit (5 parallel audit agents, top findings re-verified against the code before fixing). All fixes include regression tests.
Correctness
Extended memory
enforceCapran, so a rejection storm could grow quarantine pastmax_size_mband wedge the store (the evictor only evicts trusted atoms). Cap is now enforced on both quarantine paths.ReturnAfterBreaksummarized the 5 OLDEST atoms — backwards iteration over a newest-first list.RetentionScore(degenerating to sort-by-recency with extractor confidence defaulting to 1.0), throwing away the blended0.6·sim + 0.4·retentionscore and the paid LLM rerank order. The union is now deduped by ID and sorted by composite score (docs/EXTENDED_MEMORY.mdupdated accordingly).CreatedAt, keep higher confidence and original ID).ForgetAtomworks for quarantine-only atoms; atom truncation is rune-safe; quarantine TTL eviction no longer writes under a read lock; background user-model inference can't overlap itself or append duplicate pending reviews.extractJSONhelper now strips fences and extracts the first balanced JSON span.Agent loop
Run/RunWithMessages— a REPL user sending the same text twice got no memory hooks the second time.Legacy memory
merge_on_writedropped facts when two entries shared a 30-char prefix (substringReplace→ ambiguous) — new index-basedFactStore.ReplaceAt.Searchquery cache was never invalidated on write/promote/prune — promoted episodes stayed invisible to repeat searches.BuildSystemPromptskipped the extended user-state block when legacy facts were empty, and re-read fact files every iteration because the empty result was never cached.Consolidatebypassed the facts char cap (and persisted untrimmed entries).Efficiency
markDirty, singleassoc.Persist).embedding.Shared(HTTP backend only — RP is corpus-fitted and stays per-corpus).MemoryManager.RunBackground(context.WithoutCancel), keeping the loop responsive.os.Exit) — session-end work is now tracked and drained with a bounded 15s wait inAgent.Close, so episodes surviveodek run/continue/REPL exit.API additions (no signature changes)
MemoryManager.RunBackground,MemoryManager.WaitForBackground,FactStore.ReplaceAt,embedding.Shared.NewLLMRanker's "none" case returns an unexported sentinel error.Verification
go test ./...— 27 packages, all greengo test -race ./internal/memory/... ./internal/loop/... ./internal/embedding/...— greengolangci-lint run ./...— 0 issues