You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(memory): efficiency and correctness overhaul for extended + legacy memory (#88)
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.
-**Context-limit protection** — `trimToSurvival` drops oldest messages when approaching the model's context window, keeping the agent functional under extended sessions. Tool messages stay grouped with their parent assistant message.
-**Post-response async processing** — skill learningand episode extraction run in background goroutines, eliminating the hang after every`odek run`.
93
+
-**Post-response async processing** — skill learning, episode extraction, and per-turn extended-memory extraction run in background goroutines tracked by `MemoryManager.RunBackground`; `Agent.Close` drains them via `WaitForBackground` (bounded, ~15s) so the work survives CLI exit without hanging`odek run`.
-**Semantic session search** — the `session_search` tool uses go-vector RandomProjections + k-NN for semantic similarity search through session content, with a two-tier pipeline: vector index (fast, ~1ms) → deepSearch fallback (exhaustive, slower).
96
96
-**Security-first defaults** — the latest hardening closes the high/medium/low findings tracked in `sec_findings.md`: default `non_interactive` is `deny`, project-level `odek.json` cannot redirect backends or hijack delivery, project-level sandbox knobs require explicit operator approval, `~/.odek` trust anchors are protected, WebSocket upgrades require a per-instance CSRF token, and all untrusted content is wrapped before reaching the model. See Security Architecture below for the full list.
5. Optionally rerank the candidate set with the memory LLM.
177
-
6. If `follow_up_anticipation_enabled` is true, generate predicted intents from the current message, recent messages, and the user model, and union their recall results with the literal-query results (including type-targeted recall for `convention`, `file`, and `error` atoms).
178
-
7. Deduplicate, re-rank by retention score, and return the top-K atoms, bounded by `memory_budget_chars`.
176
+
5. Optionally rerank the literal query's candidate set with the memory LLM (predicted-intent queries are not reranked).
177
+
6. If `follow_up_anticipation_enabled` is true, generate predicted intents from the current message, recent messages, and the user model, and union their recall results with the literal-query results (including type-targeted recall for `convention`, `file`, and `error` atoms, filtered from the same candidate set).
178
+
7. Deduplicate by atom ID (keeping the best composite score), re-rank by the composite score, and return the top-K atoms, bounded by `memory_budget_chars`.
The final recall result is also bounded by `memory_budget_chars`. Tainted atoms are excluded regardless of score. When predictive intent recall is enabled, results from predicted intents are deduplicated with the literal-query results and then re-ranked by retention score before the top-K cut.
197
+
The final recall result is also bounded by `memory_budget_chars`. Tainted atoms are excluded regardless of score. When predictive intent recall is enabled, results from predicted intents are deduplicated with the literal-query results (keeping the best composite score per atom ID) and then re-ranked by the composite score before the top-K cut.
0 commit comments