Memory Evolution Path (simple now → complex later, swap not rewrite)
Status: forward-compat contract. Captured 2026-06-17 per Matt: “the simple memory is OK for now, but know the path forward and build with it in mind — make it easy to update to a more complex memory scheme.” This doc names the seam, the staged upgrade path, and the invariants that must hold so each upgrade is a backend swap behind a stable interface.
The upgrade seam (already in place)
Everything above the substrate talks to memory through three stable types in
truss-core::memory. A more complex scheme is a new implementation behind these,
never a change to the callers:
trait MemoryStore—write / promote / reject / mark_stale / get / query / delta_since / write_policy / get_policy. The ONE thing the rest of the harness depends on.SqliteMemoryStoreis the current impl; a complex scheme is a new impl (or a decorator) implementing the same trait.MemoryEntry— already carriestrust,provenance,salience(0–1 for ranking/eviction),key(supersede slug),superseded_by. The fields a richer scheme needs already exist.MemoryQuery—kind / key / min_trust / contains / limit.containsis already annotated “backed by a vector index” — semantic recall slots in here without changing the call sites.
Rule we build by: callers (SI-1 extraction, SI-2 promotion, SI-3 recall, M4
injection) only ever go through MemoryStore + MemoryQuery. No caller reaches
into SQLite or assumes exact-key lookup. (Verified: SI-3 recall is query-shaped
and token-budgeted, not key-coupled.)
Invariants every future scheme must preserve
These are the moat; an upgrade may NOT weaken them:
- Writes always enter
Unverified. Promotion stays a separate, panel-gated step. Verifiedalways means “N-reviewer panel signed off.” Retrieval can get smarter; the trust label’s meaning is fixed.- Provenance (
LedgerRef) on every entry, validated under--verify. - Reversibility — supersede/demote-to-
Stalestays traceable in the ledger. - Verified-only default view — richer recall must still default to trusted-only.
So long as a new backend honors these, it can change how entries are stored and ranked freely.
Staged path forward (each stage = new/extended MemoryStore, callers untouched)
- S0 — current (simple). SQLite content-addressed store; exact-key +
containssubstring; trust tiers + provenance + supersede. Good enough for L2 now. - S1 — shipped in 0.55.0: semantic recall (vector). Adds an embedder seam,
embedding-on-write for semantic stores, explicit
truss memory reindexbackfill, and vector-rankedMemoryQuery.containsbehindquery(). The config gate ismemory.recall = "exact" | "semantic" | "hybrid", withexactas the default. The baseline ranker is exact brute-force cosine over stored vectors; sqlite-vec / ANN remains the S2-era scale swap. - S2 — hybrid ranking. BM25 + vector +
salience+ recency blend inquery()(mirrors OpenClaw’s hybrid search). Still justquery()internals. - S3 — salience & eviction / compaction. Activate the existing
saliencefield: a scoring pass + budgeted eviction/summarization of cold entries (hierarchical/episodic compaction). Caps already exist (recall token budget). - S4 — episodic vs semantic split / associative (graph) memory. New
EntryKinds and/or a graph decorator overMemoryStorefor linked recall. Additive — new kinds, same trait. - S5 — scope tiers as backends. Per-agent / per-project / shared L2 can each be
a different
MemoryStoreimpl composed behind one facade (already an enumerated scope knob).
What this means for current building
- Keep all memory access behind
MemoryStore+MemoryQuery(enforced now). - Treat
containsas the semantic-recall entry point; don’t add a parallel retrieval API. - Keep
saliencepopulated-or-zero so S3 can turn on without a migration. - S1 is now in place behind config. Keep future recall improvements behind
MemoryStore::query; do not claim task-outcome gains until SI-4 paired eval measures them.
Net: the simple scheme ships now; the complex scheme is a backend swap we’ve already cleared the runway for.