Skip to content

Memory

Memory is the substrate agents use to remember. A memory is a typed, namespaced, confidence-scored record (src/memory/types.ts:78-104). Records carry temporal validity, a TTL, tiered bodies, and typed edges to other records — together they form a knowledge graph with a semantic-search pipeline over the top.

This page describes the data model and the default memory.query ranking. The retrieval enhancements (query router, hybrid keyword, reranker) all ship off — see the caution at the end.

memory_type is a closed enum of 11 (src/memory/types.ts:12-24):

Type Type Type
semantic episodic emotional
reflective charter skill
instruction resource decision
policy protocol

One legacy alias is normalized on input: procedural → skill (types.ts:28-30,34-46). Four of these — semantic, episodic, emotional, reflective — are the “general” types (isGeneralMemoryType, types.ts:52-55); they back the library /memories section. charter is special: one per ring, admin-only to write (enforced in memory.ts:450-474).

Every memory lives in exactly one namespace, and the namespace is the access boundary. There are four kinds (src/memory/acl.ts:10-17):

Namespace Shape Who can read
Private agent:{identity_id} the owning identity only
Team ring:{ring_id} members of that ring
Org org:{org_id} active members of that org
Federated federated:{source_org}:{source_claw} identities with an explicit share grant

Unknown formats are denied by default. Human-readable aliases resolve by name — agent:sophie, ring:core-team, org:my-org (acl.ts:19-24). All ACL checks delegate to RingScope in src/identity/ring-scope.ts; the acl.ts exports are thin wrappers for non-Hono callers (acl.ts:1-8).

Memories connect with five typed edge kinds (src/memory/types.ts:198-204), each carrying a strength in 0..1:

supports · contradicts · derives_from · supersedes · related_to

supersedes is load-bearing: memory.list and memory.recall follow these edges to the supersession tip and drop the superseded rows (gap-1 §4). Build edges with memory.relate, or let memory.remember classify them automatically (auto-edges are opt-in per call; see gap-1 §2).

Confidence decays with age. Decay is query-time only and never written back (src/memory/decay.ts:28-42):

decayed_confidence = confidence × e^(−ln2/halfLife × ageDays)

Half-life is 30 days by default (MEMORY_DECAY_HALF_LIFE_DAYS, decay.ts:51-55). A future-dated created_at returns the original confidence unchanged (decay.ts:37-38). The decay floor (MEMORY_DECAY_FLOOR, default 0.05) is a GC/audit threshold — it is not a query clamp (gap-1 §3). The garbage collector deletes memories that are TTL-expired (expires_at < now) or decayed below the floor (gap-1 §4).

Each memory has three body tiers — L0, L1, L2 (src/memory/types.ts:75). A read picks a tier from the request intent (gap-1 §5):

Intent Tier preference
preview L0 → L1 → L2
default L1 → L2 → L0
full L2 → L1 → L0

L0/L1 are the tier_l0/tier_l1 summary columns; L2 is the full body (object storage via body_key, else inline content).

memory.query is the semantic-search pipeline (src/commands/handlers/memory.ts:614-818). It embeds the query, over-fetches k = limit × 3 candidates by vector (KNN_OVERFETCH_MULTIPLIER=3, acl.ts:34), post-filters by namespace/category/circle, scores, sorts, and returns the top limit. The default score is:

score = min(1, cosine_similarity × decayed_confidence)

(memory.ts:695-716, with the enhancement multipliers all equal to 1 by default.) Pass include_trace=true for a per-match breakdown — but note the trace’s recency field is just an alias of decay_factor, and coactivation is always null (gap-1 F9). Neither is an independent signal.

memory.recall is the entity-keyed sibling: it fans out across every accessible namespace and orders by created_at (newest first) — recency, not relevance, and no embedding (src/db/surreal-adapter.ts:2183, ORDER BY created_at DESC). decayed_confidence is computed per match and reported as the score field (src/commands/handlers/memory.ts:851-872), but it does not drive the rank.