Truss Memory & Identity — Implementation Plan
Status: build-ready, 2026-06-16. Companion to
memory-identity-architecture.md (the design)
and the drafted skeleton at crates/truss-core/src/memory/. Each milestone is
independently shippable, behind the same ADR/feature discipline as the provider
arc (v0.7→0.11). Acceptance = tests green + clippy/fmt clean.
Goal restated (from the #truss discussion): give Truss a typed,
trust-tiered, provenance-tracked memory the model reaches via the native
memory_20250818 tool, plus a layered, cache-aligned identity/context injection
(L0–L4) that replaces flat-markdown dumping — optimized for Fable-5/Mythos, the
model with the highest return on a real memory surface. Two user-selectable knobs:
VerificationPolicy and IdentityPolicy.
Naming: truss-core::identity = ed25519 signing key (existing). Agent persona =
memory::persona (new). Memory lives as a module in truss-core, reusing the
SQLite store, Signer, and delegation::Verdict.
M1 — Store + provenance (foundation)
Why first: everything else writes to or reads from the store. It’s self-contained, pure-ish, and fully unit-testable with no model in the loop — exactly how the durable ledger was built.
Tasks:
- Wire the module: add
pub mod memory;tolib.rs. Resolve any compile drift in the drafted skeleton (it referencesdelegation::Verdict,store,identity). SqliteMemoryStore: implement theMemoryStoretrait against the existing durable DB connection (shareArc<Mutex<Connection>>withcrate::store::Store, or open the same file). Add thememorytable from the schema sketch instore.rs(+write_seqmonotonic counter, index on(mem_key, trust)andwrite_seq).- Content-addressing:
MemoryId= hex sha256 overkind || key || body(reuse thesha2dep already instore.rs). - Supersede semantics:
writeof aFact/Lessonwith an existingmem_keymarks the prior entrysuperseded_by+ bumpswrite_seq. delta_since(cursor)=WHERE write_seq > cursor ORDER BY write_seq.- Provenance: persist
produced_by(session, seq) and (on promote) the fullVerdictJSON.
Tests (crates/truss-core/tests/memory_store_test.rs):
- write →
getround-trips; landsUnverified. - supersede-by-key sets
superseded_by, old entry no longer in default query. querydefault returnsVerifiedonly;min_trustfloor respected.promoteattaches verdict, flips tier, appears in nextdelta_since.delta_sincereturns only post-cursor changes (incl. tier changes).
Acceptance: new tests green; existing 60 unchanged; clippy/fmt clean. ADR 0004 “Governed memory substrate.”
M2 — Memory tool binding (memory_20250818)
Why second: makes the store reachable by the model using the tool Anthropic already trained Fable on — no bespoke protocol.
Tasks:
- Register a
memorytool (type: "memory_20250818") in the tool registry (tools/mod.rs), gated by a session flag so existing runs are unaffected. - Map commands →
MemoryStore:create/insert/str_replace→write(Unverified). Inferkindfrom the path prefix (/memories/goal/…,/decisions/…,/lessons/…,/facts/…,/artifacts/…, elsescratch).view→query(defaultVerified-only); render as a dir/file listing the model expects.delete/rename→ supersede /mark_stale.
- Provenance capture: stamp
produced_bywith the current journalseqat the moment the tool call is journaled (the binding runs inside the agent loop, which already writes the ledger). - Emit
AgentEvents for memory ops (extendevents.rs:MemoryWrite,MemoryRecall) so the UI/audit log sees them.
Tests (hermetic, fake provider emitting tool calls — mirror agent_loop_test.rs):
- model
createunder/memories/lessons/x→ entry exists,kind=Lesson, Unverified. - model
viewreturns only Verified entries. - path→kind inference table.
Acceptance: hermetic tests green; flag-off path identical to today.
M3 — Trust tiers + promotion (the moat)
Why third: turns “stored” into “trusted.” Reuses the N-reviewer panel + signed verdict end-to-end — the same code that gates contracts now gates belief.
Tasks:
VerificationPolicyresolution at session build (MemoryConfig), layered global → per-agent → per-contract (extend the contract[memory]block).- Promotion driver:
Eager: on write, run the N-reviewer panel (delegation) on the claim → PASS callspromote, FAIL callsreject.Lazy: onview/recall of anUnverifiedentry, verify-then-surface; cache the verdict so it runs once.Tiered: heuristic gate (cheap model or rule) always; full panel only forFact/Decision(MemoryConfig::verify_on_write).Off: never promote;viewmay still return Unverified but tagged.
- Contradiction handling: a new Verified entry that supersedes a key demotes the
prior to
Stale. - Recall honesty: entries surfaced to the model always carry their trust tier in the rendered view (no silent trust laundering).
Tests:
- Eager: a verdict-PASS claim promotes; FAIL rejects and is never surfaced.
- Lazy: Unverified entry triggers one verification on first recall, then cached.
- Off: Unverified entries surface tagged, never as Verified.
- supersede demotes prior to Stale.
Acceptance: each policy mode covered; verdict reuse verified by sharing
delegation types (no new verdict path).
M4 — Layered injection (L0–L4) + identity
Why fourth: assembles the prompt from the tiers and aligns them to the cache. This is where the “stop dumping 37KB markdown” win actually lands.
Tasks:
- Context assembler: build
tools → system(L0 core, L0 self, L1) → messages(L3 goal ledger, history, L2 pulls, L4 scratch). Placecache_controlso L0/L1 are the stable cached prefix. Persona: load/persistPersonaCore(operator-signed viaidentity::Signer)PersonaSelf;render()into L0. Verify the core signature on load.
- Goal ledger (L3): pin the active contract intent + DoD; re-inject every N steps; never compacted.
- Delta rehydration: on resume/re-pin, inject
delta_since(cursor)for L2, not the whole store. IdentityPolicy+ amendment flow:route_amendment→ bufferCommitAtBoundary; apply at next session build as a reversible journal event; verdict-checkselfagainstcorebefore commit. A dedicated signedamend_identitytool (deliberate, injection-resistant).
Tests:
- assembled prompt has stable L0/L1 prefix across turns (cache key unchanged when only L3/L4 change).
- core signature mismatch refuses to load.
- amendment under
Proposeneeds approval; underSelfAmendcommits at boundary and is rollback-able; an amendment targetingcoreisDenied.
Acceptance: a long-run example shows cache-read tokens dominating (prefix held), and goal-ledger re-pin keeps intent across compaction.
M5 — Fable tuning + salience
Tasks:
- Provider-aware L0/L3 memory-format instruction (Fable: “write learnings here,
this format, consult next session”); provider-agnostic fallback tool pair
(
read_memory/write_memory) for models withoutmemory_20250818. - Salience scoring + eviction (recency + recall-count + optional model score);
Staledecay policy. - Optional vector backing behind
MemoryStorefor semantic recall (no agent-loop change). - Async sub-agent shared-L2 plumbing (ties into the multi-agent thread).
Acceptance: Fable run uses the memory tool unprompted-but-guided; salience evicts without losing Verified high-salience entries.
Cross-cutting
- Config surface:
[memory]contract block + global config:verification,identity,format, re-pin cadenceN. - Open questions to settle before M4 (M1–M3 don’t depend on them): salience policy details, L2 scope (per-agent / per-project / shared trust boundaries), conflict resolution when two Verified facts collide.
- Each milestone = one PR + one ADR section, same cadence as the provider arc.
Where to start
M1, task 1–2: wire the module + SqliteMemoryStore. It’s the foundation
every later milestone reads/writes, it’s fully testable without a model or a
provider, and it reuses machinery that already exists (SQLite store, sha2,
Signer, delegation::Verdict). One PR, hermetic tests, zero risk to the green
build until pub mod memory; flips on. Ship M1 → M2 → M3 in order; M1–M3 are the
moat, M4 is the injection win, M5 is the Fable polish.