The Problem with Timeless Memory
Most memory systems treat every stored piece of information as permanently important. A vector database doesn't know — or care — whether a memory was created 5 minutes ago or 5 months ago. When you search, all memories compete equally regardless of age.
This creates two problems for AI agents:
- Stale context dominance — old, possibly outdated information outranks recent updates because it has had more time to accumulate and has many variations that match different queries.
- Context window pollution — when an agent retrieves memories to inject into its prompt, stale memories consume tokens that should be used for fresh, relevant context.
Temporal memory solves this by treating time as a first-class dimension of retrieval. Memories have a lifecycle: they're created with full strength, can be reinforced by access, and gradually decay when no longer relevant.
The Scoring Formula
When Dakera retrieves memories, the final ranking score is a composite of multiple signals:
final_score = (relevance_weight * relevance_score)
+ (importance_weight * importance_score)
+ (recency_weight * recency_score)
Each component contributes different information:
- Relevance score — semantic similarity (HNSW) + keyword match (BM25), fused and reranked
- Recency score — how recently the memory was created
- Importance score — the criticality you set when storing the memory, adjusted by feedback
- Decay — not a separate term: the decay engine lowers the stored importance over time, following the configured strategy
Memory Decay Strategies
Dakera implements 6 decay strategies, each modeling a different forgetting curve. The choice of strategy depends on the type of memory you're storing.
Exponential Decay
Memories lose strength rapidly at first, then more slowly. Models the classic Ebbinghaus forgetting curve. Best for working memory — scratchpads, intermediate reasoning steps, transient observations.
# importance(t) = initial_importance * 0.5^(t / half_life)
# Half-life of 24 hours: memory is at 50% after 1 day, 25% after 2 days
# (the half-life is server-wide: DAKERA_DECAY_HALF_LIFE_HOURS, default 168;
# working memories decay 3x faster than it, semantic 2x slower)
def set_decay(agent_id: str, memory_type: str, strategy: str):
"""Set one memory type's decay curve in the agent's memory policy."""
ns = f"_dakera_agent_{agent_id}"
policy = client.get_memory_policy(ns)
setattr(policy, f"{memory_type}_decay", strategy)
client.set_memory_policy(ns, policy)
set_decay("agent-scratch", "working", "exponential")
| Time | Strength |
|---|---|
| 0 hours | 100% |
| 24 hours | 50% |
| 48 hours | 25% |
| 72 hours | 12.5% |
| 7 days | 1% (the 0.01 floor) |
Logarithmic Decay
Slow initial decay that accelerates over time. Memories stay strong for a while, then fade quickly. Best for medium-term context — project notes, meeting summaries, weekly updates.
# Importance falls slowly at first, then faster,
# reaching the floor at one half-life
set_decay("project-notes", "episodic", "logarithmic")
Linear Decay
Constant decay rate — memories lose the same amount of strength per unit time. Simple and predictable. Best for time-boxed contexts where you want memories to fade over a known duration. Decay never deletes a memory; pair it with a TTL when the memory should actually expire.
# importance(t) = max(0.01, initial_importance - 0.5 * t / half_life)
set_decay("daily-standup", "episodic", "linear")
# Delete standup notes after 48 hours (a TTL, not decay)
policy = client.get_memory_policy("_dakera_agent_daily-standup")
policy.episodic_ttl_seconds = 172800
client.set_memory_policy("_dakera_agent_daily-standup", policy)
Step Decay
Discrete importance levels that downshift on schedule. Memory stays at full strength for a period, then drops to the next level. Models how we categorize memories into "recent", "relevant", "archive".
# importance(t) = initial_importance * 0.5^floor(t / half_life)
# Example with the default 168-hour half-life: 1.0 for 7 days, 0.5 for the next 7, then 0.25
set_decay("customer-interactions", "episodic", "step_function")
Power-Law Decay
Importance falls quickly at first, then ever more slowly, so a memory keeps a long tail instead of reaching the floor. Access patterns matter under every strategy: each recall restarts the decay clock, and a memory that has never been accessed decays faster than one that has. A memory policy can add spaced repetition on top, extending a memory's TTL each time it is recalled.
# importance(t) = initial_importance / (1 + t / half_life)
# Each recall restarts the decay clock; never-accessed memories decay faster
set_decay("user-preferences", "semantic", "power_law")
# Spaced repetition: each recall extends a TTL by access_count x factor x base interval
policy = client.get_memory_policy("_dakera_agent_user-preferences")
policy.spaced_repetition_factor = 1.0
policy.spaced_repetition_base_interval_seconds = 86400 # 1 day
client.set_memory_policy("_dakera_agent_user-preferences", policy)
No Decay
Some memories should never fade — user identity, critical facts, system configuration. Use no decay (flat) for truly permanent memories:
set_decay("core-identity", "semantic", "flat")
# These memories will always compete at full strength
client.store_memory(
agent_id="core-identity",
content="The user's name is Alice Chen and she prefers to be called Alice",
memory_type="semantic",
importance=1.0
)
Importance Scoring
Importance scoring determines how much weight a memory carries independent of its age or relevance to the current query. There are two sources: the score set at write time, and the feedback your application sends afterwards.
Explicit Importance
The agent or application assigns an importance score at write time:
# Critical operational knowledge
rotation = client.store_memory(
agent_id="ops",
content="Production database password was rotated on 2026-05-16. New credentials in vault.",
importance=1.0 # Maximum importance
)
# Casual observation — fine to forget
client.store_memory(
agent_id="ops",
content="The user mentioned they enjoy the new dashboard color scheme",
importance=0.2 # Low importance
)
Feedback-Adjusted Importance
Dakera does not score content on its own: a memory stored without an importance gets 0.5. After that, importance moves with use:
- An
upvoteraises it by 0.05 plus 5% of the current value - A
downvotelowers it by 0.15 - A
flagmarks the memory as irrelevant for decay without changing its importance update_importancesets it directly- The decay engine lowers it over time, down to the 0.01 floor
# The memory helped answer the question: reinforce it
client.feedback_memory(memory_id=rotation["id"], agent_id="ops", signal="upvote")
# Set importance explicitly for one or more memories
client.update_importance(agent_id="ops", memory_ids=[rotation["id"]], importance=0.9)
Recency Weighting at Query Time
Beyond passive decay, Dakera weighs time at query time: a query that asks "when" or names a period ("last week", "in March") is classified as temporal and scored with temporal weighting, and every recall can take a time window and a memory type. This lets the same namespace serve different query patterns:
from datetime import datetime, timedelta, timezone
# "What happened recently?": only memories from the last 7 days
week_ago = datetime.now(timezone.utc) - timedelta(days=7)
recent_events = client.recall(
agent_id="project-log",
query="recent progress updates",
top_k=5,
since=week_ago.strftime("%Y-%m-%dT%H:%M:%SZ")
)
# "What's the overall architecture?": durable facts, any age
architecture = client.recall(
agent_id="project-log",
query="system architecture decisions",
top_k=5,
memory_type="semantic"
)
Temporal Queries
Sometimes the agent needs to answer explicitly temporal questions: "what did the user say last week about their budget?" or "has this error happened before?" Dakera supports temporal filtering alongside semantic search:
# Search within a time range
last_week = client.recall(
agent_id="conversations",
query="budget discussion",
top_k=5,
since="2026-05-11T00:00:00Z",
until="2026-05-17T23:59:59Z"
)
# Find the earliest memory matching a pattern
matches = client.recall(
agent_id="incidents",
query="database connection timeout",
top_k=20
)
first_occurrence = min(matches.memories, key=lambda m: m.created_at)
How Temporal Memory Affects Benchmark Scores
The LoCoMo benchmark specifically tests temporal reasoning — one of four question categories (alongside single-hop, multi-hop and open-domain). Systems without temporal awareness systematically fail questions like:
- "When did Alice change her mind about the project?"
- "What was the most recent update about the deadline?"
- "Has the user's preference on this topic changed over time?"
Dakera's temporal memory system is a significant contributor to its 88.2% Recall@20 on LoCoMo (v0.11.107), particularly on Category 3 (temporal inference) questions, where it reaches 73.9%, its hardest category.
For more details on how we benchmark temporal reasoning, see our benchmark methodology post.
Choosing the Right Decay Strategy
| Use Case | Strategy | Memory type (decay speed) |
|---|---|---|
| Scratch/working memory | Exponential | working (3×) |
| Conversation context | Exponential | episodic (1×) |
| Project notes | Logarithmic | episodic (1×) |
| Customer interactions | Step function | episodic (1×) |
| User preferences | Power law | semantic (0.5×) |
| Identity/facts | Flat | semantic (no decay) |
Implementation Pattern: Multi-Layer Memory
A production agent typically uses multiple namespaces with different decay strategies, forming a memory hierarchy:
from dakera import DakeraClient
client = DakeraClient(base_url="http://localhost:3000", api_key="your-api-key")
class AgentMemory:
"""Multi-layer memory with different temporal behaviors."""
def __init__(self, agent_id: str):
self.agent_id = agent_id
# Configure layers: one agent each, decay set with set_decay() above
self.layers = {
"working": f"{agent_id}-working", # exponential, working type
"session": f"{agent_id}-session", # exponential, episodic type
"learned": f"{agent_id}-learned", # power_law, semantic type
"identity": f"{agent_id}-identity", # flat (permanent)
}
self.memory_types = {"working": "working", "session": "episodic",
"learned": "semantic", "identity": "semantic"}
def remember(self, content: str, layer: str = "session", importance: float = 0.5):
"""Store a memory in the appropriate layer."""
client.store_memory(
agent_id=self.layers[layer],
content=content,
memory_type=self.memory_types[layer],
importance=importance
)
def recall(self, query: str):
"""Search across all layers, merging results."""
all_results = []
for layer_name, namespace in self.layers.items():
results = client.recall(
agent_id=namespace,
query=query,
top_k=3,
)
for r in results.memories:
r.layer = layer_name
all_results.extend(results.memories)
# Sort by composite score across layers
all_results.sort(key=lambda r: r.score, reverse=True)
return all_results[:5]
# Usage
memory = AgentMemory("support-agent-1")
memory.remember("User's name is Alice", layer="identity", importance=1.0)
memory.remember("Alice asked about API rate limits", layer="session")
memory.remember("Working on response draft...", layer="working", importance=0.1)
Conclusion
Temporal memory transforms agents from stateless responders into systems that understand the passage of time. The key insight is that memory strength should not be binary (present or absent) — it exists on a spectrum that naturally evolves. Important memories persist. Ephemeral context fades. And the agent's recall patterns start to resemble the intelligent, adaptive behavior we expect from systems that truly "remember."
Implementation patterns: Temporal Event Tracking · Memory Compression · Session Handoff
Add memory decay to your agents — the only framework that ships it natively
Dakera's half-life scheduler runs in-process. No cron jobs, no external calls. Important context persists; stale context fades on its own.



