Importance-Based Forgetting
Assign every memory an importance score at write time and let low-value entries fade naturally. Critical facts survive indefinitely; resolved tickets, small talk, and ephemeral context drop out of recall automatically, with no manual pruning.
Start Free →- Running Dakera server (Quickstart)
- An agent ID to scope memories to
- Basic understanding of 0.0–1.0 importance scale
The Problem: Noisy, Unbounded Memory Stores
Memory stores grow indefinitely by default. After six months, a customer support agent for a SaaS product accumulates thousands of memories: resolved bug reports, closed tickets, one-off workarounds, support greetings, and fleeting context from conversations long since closed. Without a forgetting mechanism, every recall query returns a mix of critical knowledge and stale noise.
The result: agents confidently surface outdated information, recall latency increases as candidate sets balloon, and storage costs grow linearly with agent lifespan. The solution is not to delete memories — it is to score them so the right ones surface and the wrong ones fade.
Decay never deletes a memory: a background pass (hourly by default) lowers the importance of memories that are not being used, on a half-life you configure (DAKERA_DECAY_HALF_LIFE_HOURS, default 168), and stops at a floor of 0.01 (DAKERA_DECAY_MIN_IMPORTANCE). A recall with min_importance leaves out every memory below that threshold. The memory stays in storage, so you can audit or raise it again, while recall stays clean. Only a forget or a TTL deletes.
Architecture
Every store_memory call accepts an importance field (0.0–1.0). After that, importance changes in three ways: decay lowers it over time when the memory is not used (each access restarts the clock, and the memory type sets the speed: working 3×, episodic 1×, semantic 0.5×, procedural 0.3×), feedback moves it (an upvote adds 0.05 plus 5% of the current value, a downvote takes away 0.15), and explicit update_importance calls set it. Since v0.12 a recall does not raise importance. At recall time, min_importance is a filter: memories below the threshold are not returned.
- Score 1.0 — permanent facts: user identity, account details, critical decisions
- Score 0.7–0.9 — important context: user preferences, project milestones, escalations
- Score 0.4–0.6 — useful context: working notes, active tickets, project details
- Score 0.1–0.3 — ephemeral: greetings, resolved tickets, casual remarks
- Floor 0.01 — decay alone never takes a memory lower (
DAKERA_DECAY_MIN_IMPORTANCE)
Diagram: What a Recall Threshold Lets Through
Diagram: How a Memory Fades
Real-World Scenario: Customer Support Agent
Scenario: HelpDesk AI builds a support agent for a B2B SaaS product. The agent accumulates memories from thousands of support conversations: billing questions, bug reports, feature requests, and resolved tickets. After 12 months, the agent has 15,000+ memories. Recall quality degrades because closed tickets from last year surface alongside active incidents.
HelpDesk assigns importance by memory category: resolved tickets get 0.2, active bug workarounds get 0.6, product announcements get 0.85, and permanent account facts (plan tier, contract terms) get 1.0. The recall threshold is set to min_importance=0.5 by default, ensuring resolved tickets never surface in new conversations.
Result: new conversations recall account facts, announcements and open workarounds, while last year's closed tickets stay in storage for audit without reaching the prompt.
Step-by-Step Implementation
-
Define your importance taxonomyBefore writing code, map your memory categories to importance scores. A support agent might use: identity facts (1.0), escalations (0.9), active tickets (0.6), resolved tickets (0.2), greetings (0.1). This taxonomy is the most important design decision in the pattern.
-
Store memories with scored importanceAt every
store_memorycall, pass the appropriate importance score. Use tags alongside importance for multi-dimensional filtering later (e.g., tag resolved tickets with"resolved"so you can also filter by tag if needed). -
Configure recall thresholds per query typeNot all queries need the same threshold. A "what is this user's account tier?" query should use
min_importance=0.8(only the top of the taxonomy: identity facts, escalations, preferences). A "what bugs has this user reported?" query might usemin_importance=0.4to include recent working notes. -
Update importance when status changesWhen a ticket is resolved, call
update_importanceto drop its score from 0.6 to 0.2. This takes it below the 0.5 threshold at once, without deleting the memory. When a feature request is escalated to P0, raise its importance from 0.5 to 0.9. -
Batch forget truly irrelevant memoriesFor memories you know are permanently useless (GDPR deletion, test data, spam), hard-delete them rather than letting them decay:
forgetfor known IDs,batch_forgetfor everything matching a filter (a tag, an importance range). Decay is for gradual relevance loss; forget is for immediate removal.
Before & After: Recall Results
[
{ "content": "Hi there! How can I help?",
"importance": 0.1, "age_days": 45 },
{ "content": "Ticket #1022 resolved: billing glitch",
"importance": 0.2, "age_days": 38 },
{ "content": "Ticket #1104 resolved: login error",
"importance": 0.2, "age_days": 30 },
{ "content": "User plan: Pro ($299/mo)",
"importance": 1.0, "age_days": 180 },
{ "content": "API rate limit workaround documented",
"importance": 0.55, "age_days": 14 },
{ "content": "Good morning, happy to assist!",
"importance": 0.1, "age_days": 22 },
{ "content": "Ticket #982 resolved: CSV export bug",
"importance": 0.2, "age_days": 60 },
{ "content": "User prefers async Slack updates",
"importance": 0.8, "age_days": 90 }
]
// 5 of 8 results are stale noise
[
{ "content": "User plan: Pro ($299/mo)",
"importance": 1.0, "age_days": 180 },
{ "content": "User prefers async Slack updates",
"importance": 0.8, "age_days": 90 },
{ "content": "API rate limit workaround documented",
"importance": 0.55, "age_days": 14 }
]
// 3 signal memories, no stale noise
// All 3 directly relevant to new query
Implementation
# Store a permanent identity fact
curl -X POST http://localhost:3000/v1/memory/store \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{
"agent_id": "support-bot",
"content": "User: Alex Kim, Pro plan, enterprise contract, API access",
"importance": 1.0,
"memory_type": "semantic",
"tags": ["identity", "account", "alex-kim"]
}'
# Store an active ticket (medium importance)
curl -X POST http://localhost:3000/v1/memory/store \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{
"agent_id": "support-bot",
"content": "Ticket #2091 open: user cannot export to PDF, reproducible on Firefox",
"importance": 0.6,
"memory_type": "episodic",
"tags": ["ticket", "open", "alex-kim"]
}'
# When the ticket resolves: drop its importance below the recall threshold
curl -X PATCH http://localhost:3000/v1/memories/mem-id-123/importance \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{"agent_id": "support-bot", "importance": 0.2}'
# Recall with a threshold: resolved tickets are left out
curl -X POST http://localhost:3000/v1/memory/recall \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{"agent_id": "support-bot", "query": "PDF export issue", "min_importance": 0.5, "top_k": 10}'
# Hard-delete every memory about a user (e.g., GDPR request); a filter is required
curl -X DELETE http://localhost:3000/v1/memories/forget/batch \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{"agent_id": "support-bot", "filter": {"tags": ["alex-kim"]}}'from dakera import DakeraClient, BatchForgetRequest, BatchMemoryFilter
client = DakeraClient(base_url="http://localhost:3000", api_key="dk-...")
AGENT = "support-bot"
# --- Importance taxonomy (recall uses min_importance=0.5 by default) ---
IMPORTANCE = {
"identity": 1.0, # account facts: always above the threshold
"escalation": 0.9,
"preference": 0.8,
"active_ticket": 0.6, # above 0.5 while open
"resolved_ticket": 0.2, # below 0.5: left out of default recalls
"greeting": 0.1,
}
# Store permanent account fact
client.store_memory(
agent_id=AGENT,
content="User: Alex Kim, Pro plan $299/mo, enterprise contract, API access enabled",
importance=IMPORTANCE["identity"],
memory_type="semantic",
tags=["identity", "account", "alex-kim"]
)
# Store active ticket
ticket_mem = client.store_memory(
agent_id=AGENT,
content="Ticket #2091 OPEN: cannot export to PDF, reproducible Firefox 124, macOS",
importance=IMPORTANCE["active_ticket"],
memory_type="episodic",
tags=["ticket", "open", "pdf-export", "alex-kim"]
)
# When ticket resolves: drop importance below the recall threshold
def close_ticket(memory_id: str, resolution: str):
client.update_importance(
agent_id=AGENT,
memory_ids=[memory_id],
importance=IMPORTANCE["resolved_ticket"]
)
# Store resolution note below the threshold too, kept for audit
client.store_memory(
agent_id=AGENT,
content=f"RESOLVED: {resolution}",
importance=0.45,
memory_type="semantic",
tags=["ticket", "resolved", "alex-kim"]
)
close_ticket(ticket_mem["id"], "Ticket #2091: PDF export fixed in v2.14.1, deployed 2026-05-15")
# Recall: only active, relevant memories
active_context = client.recall(
agent_id=AGENT,
query="Alex Kim account and open issues",
min_importance=0.5,
top_k=10
)
for m in active_context.memories:
print(f"[{m.importance:.2f}] {m.content}")
# Hard-delete for GDPR compliance: every memory tagged with the user
def delete_user_memories(user_tag: str) -> int:
resp = client.batch_forget(BatchForgetRequest(
agent_id=AGENT,
filter=BatchMemoryFilter(tags=[user_tag]),
))
return resp.deleted_countimport { DakeraClient } from '@dakera-ai/dakera';
const client = new DakeraClient({ baseUrl: 'http://localhost:3000', apiKey: 'dk-...' });
const AGENT = 'support-bot';
const IMPORTANCE = {
identity: 1.0,
escalation: 0.9,
preference: 0.8,
activeTicket: 0.6,
resolvedTicket: 0.2,
greeting: 0.1,
} as const;
// Store permanent identity fact
await client.storeMemory(AGENT, {
content: 'User: Alex Kim, Pro plan $299/mo, enterprise contract, API access',
importance: IMPORTANCE.identity,
memory_type: 'semantic',
tags: ['identity', 'account', 'alex-kim'],
});
// Store active ticket
const { memory: ticketMem } = await client.storeMemory(AGENT, {
content: 'Ticket #2091 OPEN: cannot export to PDF, reproducible Firefox 124',
importance: IMPORTANCE.activeTicket,
memory_type: 'episodic',
tags: ['ticket', 'open', 'pdf-export', 'alex-kim'],
});
// On resolution: drop importance below the recall threshold
async function closeTicket(memoryId: string, resolution: string) {
await client.updateImportance(AGENT, {
memory_ids: [memoryId],
importance: IMPORTANCE.resolvedTicket,
});
await client.storeMemory(AGENT, {
content: `RESOLVED: ${resolution}`,
importance: 0.45,
memory_type: 'semantic',
tags: ['ticket', 'resolved', 'alex-kim'],
});
}
await closeTicket(ticketMem.id, 'Ticket #2091: PDF fix deployed in v2.14.1');
// Filtered recall: resolved tickets never appear
const { memories } = await client.recall(AGENT, 'Alex Kim account and open issues', {
min_importance: 0.5,
top_k: 10,
});
// GDPR hard delete: every memory tagged with the user (a filter is required)
const resp = await client.batchForget({
agent_id: AGENT,
filter: { tags: ['alex-kim'] },
});
console.log(`Deleted ${resp.deleted_count} memories`);use dakera_client::{BatchForgetRequest, BatchMemoryFilter, DakeraClient, MemoryType,
RecallRequest, StoreMemoryRequest};
let client = DakeraClient::builder("http://localhost:3000")
.api_key("dk-...")
.build()?;
let agent = "support-bot";
// Store permanent identity memory
client.store_memory(
StoreMemoryRequest::new(agent, "User: Alex Kim, Pro plan, enterprise contract, API access")
.with_type(MemoryType::Semantic)
.with_importance(1.0)
.with_tags(vec!["identity".into(), "account".into(), "alex-kim".into()]),
).await?;
// Store active ticket at medium importance
let ticket = client.store_memory(
StoreMemoryRequest::new(agent, "Ticket #2091 OPEN: PDF export fails in Firefox 124")
.with_type(MemoryType::Episodic)
.with_importance(0.6)
.with_tags(vec!["ticket".into(), "open".into(), "alex-kim".into()]),
).await?;
// On resolution: drop importance below the recall threshold
client.patch_memory_importance(&ticket.memory_id, agent, 0.2).await?;
// Recall with importance filter
let results = client.recall(
RecallRequest::new(agent, "Alex Kim open issues")
.with_min_importance(0.5)
.with_top_k(10),
).await?;
// Hard delete every memory tagged with the user (GDPR)
let deleted = client.batch_forget(BatchForgetRequest::new(
agent,
BatchMemoryFilter::default().with_tags(vec!["alex-kim".into()]),
)).await?;
println!("{} memories deleted", deleted.deleted_count);client := dakera.NewClientWithOptions(dakera.ClientOptions{
BaseURL: "http://localhost:3000",
APIKey: "dk-...",
})
ctx := context.Background()
agent := "support-bot"
f32 := func(v float32) *float32 { return &v }
// Permanent identity fact
client.StoreMemory(ctx, agent, dakera.StoreMemoryRequest{
Content: "User: Alex Kim, Pro plan, enterprise contract, API access",
Importance: f32(1.0),
MemoryType: "semantic",
Tags: []string{"identity", "account", "alex-kim"},
})
// Active ticket at medium importance
ticket, _ := client.StoreMemory(ctx, agent, dakera.StoreMemoryRequest{
Content: "Ticket #2091 OPEN: PDF export fails in Firefox 124",
Importance: f32(0.6),
MemoryType: "episodic",
Tags: []string{"ticket", "open", "alex-kim"},
})
// On resolution: drop importance below the recall threshold
client.PatchMemoryImportance(ctx, ticket.Memory.ID, agent, 0.2)
// Recall with filter
results, _ := client.Recall(ctx, agent, dakera.RecallRequest{
Query: "Alex Kim open issues",
MinImportance: f32(0.5),
TopK: 10,
})
// GDPR: hard delete every memory tagged with the user
resp, _ := client.BatchForget(ctx, dakera.BatchForgetRequest{
AgentID: agent,
Filter: dakera.BatchMemoryFilter{Tags: []string{"alex-kim"}},
})
fmt.Printf("Deleted %d memories\n", resp.DeletedCount)Recall that stays focused as the store grows
Importance-based decay runs server-side automatically — zero maintenance required.
SDK Reference
| Method | SDK | Purpose |
|---|---|---|
store_memory(agent_id, content, importance, memory_type, tags) | Python | Store memory with importance score |
storeMemory(agentId, {content, importance, memoryType, tags}) | TypeScript | Store memory with importance score |
recall(agent_id, query, min_importance, top_k) | Python | Retrieve memories above importance threshold |
recall(agentId, query, {min_importance, top_k}) | TypeScript | Retrieve memories above importance threshold |
update_importance(agent_id, memory_ids, importance) | Python | Reassign importance score to trigger/resist decay |
updateImportance(agentId, {memory_ids, importance}) | TypeScript | Reassign importance score to trigger/resist decay |
forget(agent_id, memory_id) | Python | Hard-delete a single memory permanently |
forget(agentId, memoryId) | TypeScript | Hard-delete a single memory permanently |
batch_forget(request) | Python | Hard-delete every memory matching a filter (GDPR deletion) |
batchForget(request) | TypeScript | Hard-delete every memory matching a filter (GDPR deletion) |
Performance Considerations
- Decay works in the background. The decay pass runs on the server (every
DAKERA_DECAY_INTERVAL_SECS, default 3600 s), so keeping recall clean adds no work to your request path. - One batch call per deletion.
batch_forgetremoves every memory that matches a filter (tags, importance range) in one request, instead of oneforgetcall per ID. It requires at least one filter. - update_importance changes the score only. The content and tags stay as they are, so call it whenever ticket or task status changes. An importance you set decays from the moment you set it.
Edge Cases
Developers often set importance=1.0 "to be safe," but this defeats the entire pattern. If everything is permanent, recall becomes noisy again. Reserve 1.0 strictly for facts that should never leave the store: user identity, account constants, irrevocable decisions. For everything else, use 0.5–0.9 and let decay do its job.
An active ticket stored at importance 0.6 keeps passing a 0.5 threshold after resolution, until decay brings it down, if you forget to call update_importance. Integrate the importance downgrade into your ticket-close webhook or event handler; treat it as part of the "mark resolved" workflow, not an afterthought.
Since v0.12 a recall does not raise importance, but upvotes do: each one adds 0.05 plus 5% of the current value (at most 1.0). A low-importance memory that users keep upvoting can climb back over your threshold. If you're seeing stale memories resurface, check their feedback history (GET /v1/memories/{id}/feedback) and use update_importance to set them back to 0.1 after investigation.
Setting min_importance=0.9 on all queries will return only identity facts — you'll miss active tickets and preferences. Use context-sensitive thresholds: high threshold (0.8) for factual lookups, lower threshold (0.4–0.5) for open-ended context queries. Tune per query intent.
Soft decay (importance drops below threshold) does not physically remove the memory from storage; it only hides it from recall. For GDPR Article 17 right-to-erasure compliance, you must hard-delete: forget for known IDs, or batch_forget with a filter such as a per-user tag. Document which memories are subject to erasure obligations and automate hard deletion on user account closure.
Advanced Configuration: Decay Settings & Memory Policies
Server-Wide Decay (admin)
# Python: read and change the decay engine (needs an admin key with no namespace restriction)
decay = client.decay_config()
client.decay_update_config(strategy="exponential", half_life_hours=168, min_importance=0.01)
# TypeScript
const decay = await client.decayConfig();
await client.decayUpdateConfig({ strategy: 'exponential', half_life_hours: 168, min_importance: 0.01 });
Per-Namespace Memory Policy
An agent's memories live in the namespace _dakera_agent_{agent_id}. Its memory policy (PUT /v1/namespaces/{ns}/memory_policy, get_memory_policy() / set_memory_policy() in the SDKs) can give each memory type its own decay curve (working_decay, episodic_decay, semantic_decay, procedural_decay) and a default TTL. Read the policy, change the fields you need and write it back: the write replaces the stored policy.
Decay Settings Reference
| Setting | Default | Effect |
|---|---|---|
| DAKERA_DECAY_HALF_LIFE_HOURS | 168 | Half-life of the decay curve |
| DAKERA_DECAY_STRATEGY | exponential | Server-wide curve (exponential, linear or step) |
| DAKERA_DECAY_MIN_IMPORTANCE | 0.01 | Floor that decay never goes below |
| DAKERA_DECAY_INTERVAL_SECS | 3600 | How often the background decay pass runs |
Run this pattern against a real Dakera.
The Playground executes live API calls in your browser. No account, no install. Then run it on your own server in five minutes.