Memory Dynamics
π Repository: smartmemory-ai/smart-memory π Issues: Report bugs or request features
SmartMemory doesn't treat every stored item as equally important forever. Memories gain and lose prominence over time based on how you use them, and the retrieval layer uses those signals to surface what's actually relevant to a query β not just what lexically matches.
This page explains the memory-dynamics model: how activation, decay, compression, spreading, and centrality combine to decide what comes back from a get_working_context() call.
Why this matters
A naΓ―ve memory system treats retrieval as lexical search: you ask "why did we pick JWT?" and you get back items that contain the words "why," "pick," or "JWT." Everything you know that's related to JWT but doesn't contain the word β load-balancer constraints, session-cookie failure modes, the decision thread that referenced a specific RFC β stays invisible unless your query happens to match its text.
SmartMemory's dynamics model closes that gap. Frequently-used items get promoted; unused items fade; analytical questions pull in graph neighbors even when they don't lexically match; items you explicitly pin for a session are guaranteed on top. The result is that get_working_context() returns contextually relevant items, not just lexically matching items.
The four behaviors
Four things happen in the background and at retrieval time.
1. Memory you use stays warm
Every time you retrieve an item via search(), get(), or a spreading-activation pass, its activation score goes up. The boost is a small additive nudge (0.05 by default for a direct retrieval, smaller for surfacing and co-retrieval). Over a session of active use, frequently-touched items accumulate significantly higher activation than passive ones.
# Behind the scenes: every retrieval queues a boost to Redis.
# A background worker (ActivationFlushJob) drains the queue every 5s
# and writes the updated score to the item's metadata.
memory.search("authentication") # items in the results get a +0.05 boost
memory.get("item-123") # item-123 gets a +0.05 boostYou don't call a boost API directly. The system just tracks which memories actively participate in your workflow.
2. Unused memory fades (and eventually compresses)
Activation has a half-life (default: 30 days). A memory you haven't touched in a month is at roughly half the activation of when you last used it. After ~90 days of no activity, its activation approaches zero.
Fading is visible two ways:
-
Ranking β low-activation items rank lower in retrieval results (they have to be compensated for by strong lexical relevance or explicit pinning).
-
Compression β very cold items get progressively compressed through a five-tier ladder:
FULL β SUMMARY β SKELETON β TOMBSTONE β FORGOTTENAt the SUMMARY tier, the item's content is replaced with an LLM-generated digest. At SKELETON, just a title. TOMBSTONE keeps only the id. FORGOTTEN is removable by workspace policy. User-authored content (origin tier 1 β anything added via
memory.add(), CLI, or API) is never compressed past FULL regardless of activation.
Compression is opt-in (SmartMemory(tier_compaction=True) + SMARTMEMORY_COMPACTION_ENABLED=true env var). Decay is always on.
3. Related memory surfaces on analytical questions
When you ask a "why" or "how" question, SmartMemory's surfacing router classifies it as deep:multi-hop and does more than lexical search:
- Find the top lexical hits (the items that directly match your query's words).
- Spread from those seeds along the graph β bounded BFS, default 2 hops deep, 20-neighbor fan-out. Items reachable through
RELATED,MENTIONS,ANSWERED_BY,PART_OF, and similar edges contribute to the candidate set even if they don't lexically match. - Compute Personalized PageRank from the same seeds over the workspace's subgraph. Items with high graph centrality relative to the seeds get a ranking lift.
- Combine lexical relevance, activation, recency, spreading score, and PPR centrality into a single composite score.
The practical effect: ask "Why did we pick JWT?" and get back the JWT decision item, plus the load-balancer note, plus the session-cookie failure thread, plus the RFC reference β not because they all contain "JWT," but because they sit in the graph neighborhood of the items that do.
Two other strategies the router can pick:
fast:recencyβ short conversational queries. Skips spreading and PPR entirely; ranks by recent activity and lexical match. Sub-50ms p50.factual:point-queryβ short lookups like"users"or"list of decisions". Graph-only, skips vector semantic search (which would drown a single-entity query in noise).
Three more strategies (procedural:lookup, temporal:windowed) are reserved in the enum for a future release and currently soft-degrade to fast:recency.
4. You can pin items for a session
For conversational workflows, you often want to say "keep this on top for the rest of our chat." SmartMemory supports session-scoped pins:
memory.pin_item(session_id="my-session", item_id="project-requirements-doc")
# Now every get_working_context(session_id="my-session", ...) call
# force-includes project-requirements-doc in the results.
memory.unpin_item(session_id="my-session", item_id="project-requirements-doc")Pins are Redis-backed, TTL'd at 24 hours, and workspace-bound β the first pin binds the session to one workspace, and subsequent cross-workspace pins raise SessionPinWorkspaceConflict. Reads of another workspace's pin set via a reused session id return empty. This prevents a poisoned session from bridging memory across tenants.
The composite score
Every item in a get_working_context() response comes with a score breakdown showing exactly what contributed to its rank:
resp = memory.get_working_context(session_id="s1", query="Why did we pick JWT?")
resp["items"][0]["score_breakdown"]
# {
# "activation": 0.73, # how warm the item is
# "relevance": 0.92, # lexical match to query
# "recency": 0.88, # exponential decay on last modified
# "centrality": 0.65, # normalized PPR score over candidates
# "anchor_forced": false, # true = force-included anchor
# "session_pin_boost": 0.0, # pin_weight Γ 0.3 for pinned items
# "freshness_boost": 0.15, # linear decay over 24h for new items
# }The composite formula (surfacing_mode="router"):
base = max(activation, 0.05) Γ relevance Γ recency
centrality_lift = 1 + 0.5 Γ centrality_norm # 1.0 .. 1.5
additives = anchor_forced + session_pin_boost + freshness_boost
score = base Γ centrality_lift + additivesAdditives like pins and anchors don't multiply the score β they add a fixed lift and force the item into the returned set regardless of its base rank. A freshly-pinned item with zero activation still surfaces.
Opt-in and defaults
All of this is off by default. Every existing caller of get_working_context() gets the M1a legacy behavior (lexical Γ recency Γ activation-with-floor) until they explicitly opt in:
memory = SmartMemory(surfacing_mode="router") # turns on router + composite + pinsWith surfacing_mode="router" off:
- The router is bypassed; all queries use
fast:recency. session_pin_boost,freshness_boost, andcentralitystay zero in the response.- Spreading activation doesn't run.
- PPR doesn't run; no cache, no hook.
Legacy-mode callers see zero behavioral change. Opt-in is per-SmartMemory instance; a service can run both modes side-by-side during a migration.
The four milestone arc
This feature shipped across four milestones under the CORE-MEMORY-DYNAMICS-1 initiative:
| Milestone | What it added |
|---|---|
| M1a | get_working_context() API + contract, observational ConsolidationRouter, MCP/SDK surface |
| M1b | working β pending memory type rename (breaking change, contained) |
| M2a | Activation primitives β boost, decay, retrieval-boost hook, ranking cutover, five activation-driven evolvers |
| M2b | Flow expansion β ResolutionChainEvolver for QβA edges, 5-tier progressive compression, workspace capacity cap with competitive inhibition |
| M3 | Surfacing router, session pins, spreading activation, Personalized PageRank, graph write-hook invalidation |
The system is feature-complete as of M3. Additional refinements (LLM-based query classifier, async-refresh fallback, procedural:lookup / temporal:windowed strategy implementations, anchor drift warnings in-band) remain on the roadmap but don't change the overall shape.
Related documentation
api/surfacing.mdβ full API reference forget_working_context, pins, and surfacing modeguides/tuning-surfacing.mdβ task-oriented guide for configuring the systemconcepts/evolution-algorithms/retrieval-strengthening.mdβ deeper dive on the retrieval-boost mechanismconcepts/evolution-algorithms/hebbian-co-retrieval.mdβ how co-retrieved items get pairedconcepts/evolution-algorithms/exponential-decay.mdβ the decay math