Conversation Summarization & Decay
Transform long conversation histories into compact, persistent summaries while raw transcripts decay naturally. Ideal for legal, support, and long-running project workflows where outcomes matter more than raw dialogue.
Start Free →- Running Dakera server (Quickstart)
- An agent ID scoped per user or case
- Optional: LLM client (OpenAI, Anthropic) for custom summarization prompts
The Problem: Unbounded Conversation Growth
Raw conversation logs are verbose. A single 90-minute legal consultation can generate 8,000+ tokens of dialogue. Storing every turn verbatim creates three compounding problems: recall noise (low-value messages compete with critical facts for retrieval slots), unbounded storage growth, and context window bloat when reconstructing agent state.
Human memory solves this naturally — you remember that a client wants to sue for wrongful termination, not the exact words they used in the second sentence of the third paragraph. Your AI agent needs the same ability: compress the transcript, persist the insight.
This pattern works on two timescales simultaneously: real-time decay (raw messages fade as importance drops) and batch summarization (periodic consolidation compresses accumulated turns into a durable summary). Use both together for the best results.
Architecture Overview
The pattern combines two Dakera primitives: importance-weighted storage and built-in consolidation. Raw conversation turns are stored with low importance (0.2–0.4); decay lowers it further, so they stay below the recall threshold. Decay never deletes a memory: it lowers importance toward a floor of 0.01. LLM-generated summaries are stored at high importance (0.92 here) as semantic memories, and the agent's memory policy sets semantic_decay to flat so they keep that importance while the episodic turns fade.
- Store each conversation turn immediately with
memory_type: "episodic"and low importance - Every 15 turns (and at session end), generate an LLM summary and store it at high importance
- Tag summaries with
"summary"and the case or session ID (for example"case-247") for targeted recall - Let consolidation merge old turns: AutoPilot does it every 12 hours, and
consolidate_agent()runs it now - At recall time, use
min_importance=0.8to surface only summaries and key facts
Diagram: Summarization Pipeline
Diagram: How a Raw Turn Decays
Real-World Scenario: Legal Case Assistant
Scenario: Lawbridge AI builds a legal assistant that attends client consultations. Each consultation generates 50–200 conversation turns. Attorneys need the case summary to persist across months; small talk and pleasantries should vanish within days.
Lawbridge stores each consultation turn at importance 0.25–0.35. After each consultation, they run a GPT-4 summarization prompt that extracts: client claims, relevant dates, requested remedies, and attorney notes. That summary is stored at importance 0.92 with tags ["summary", "case-{id}", "client-{id}"]. Semantic decay is set to flat for the agent, so when an attorney opens a case six months later, recall(min_importance=0.8) still returns the summaries, and no raw dialogue noise.
Result: attorneys get the complete case context back from one recall, without raw dialogue in the way.
Step-by-Step Implementation
-
Store each conversation turn with low importanceAs the conversation progresses, write each user/assistant turn to Dakera with
memory_type: "episodic"and importance 0.2–0.4. These are raw and verbose. Decay keeps lowering their importance (toward a floor of 0.01; it never deletes them), so they stay out of recall atmin_importance=0.8. Leavettl_secondsunset: AutoPilot never consolidates a memory that has a TTL. -
Detect summarization triggerTrigger summarization every 15 turns, at session end, or when a topic shift is detected. For legal workflows, always summarize at the explicit close of each consultation, not mid-conversation.
-
Generate summary with an LLMRetrieve recent turns using
search_memoriesor by recalling with lowmin_importance. Pass them to your LLM with a structured extraction prompt: "Extract key facts, decisions, dates, and action items." Validate the output before storing. -
Store the summary at high importanceStore the LLM summary with
importance: 0.92,memory_type: "semantic", and structured tags. Decay applies to every memory, so setsemantic_decaytoflatin the agent namespace's memory policy (_dakera_agent_{agent_id}); summaries then keep their importance and surface first in recall, while episodic turns keep decaying. -
Consolidate the old turnsAutoPilot consolidates every 12 hours: clusters of three or more memories that share at least two tags (here
turnand the case ID) and are all below importance 0.3 become onesemanticmemory. To run consolidation right after storing a summary, callconsolidate_agent()(POST /v1/agents/{id}/consolidate). -
Recall with min_importance filterAt the start of any future session, recall with
min_importance=0.8and relevant tags. This returns only durable summaries and key facts — no ephemeral noise from old conversations.
Before & After: Memory State
[
{
"id": "m-001",
"content": "Client: Hi, thanks for seeing me",
"importance": 0.2,
"memory_type": "episodic",
"created_at": "2026-03-10T09:00:00Z"
},
{
"id": "m-002",
"content": "Attorney: Of course, what brings you in?",
"importance": 0.15,
"memory_type": "episodic"
},
{
"id": "m-003",
"content": "Client: I was fired last Tuesday...",
"importance": 0.35,
"memory_type": "episodic"
},
{
"id": "m-004",
"content": "...after 8 years at the company.",
"importance": 0.35,
"memory_type": "episodic"
},
{
"id": "m-005",
"content": "Client: No severance was offered.",
"importance": 0.4,
"memory_type": "episodic"
},
{
"id": "m-006",
"content": "Attorney: Was there a written contract?",
"importance": 0.25,
"memory_type": "episodic"
}
]
// Recall noise: 6 low-importance entries
// compete with critical facts
[
{
"id": "m-sum-001",
"content": "CASE SUMMARY — March 10 2026: Client
Jane Doe, employed at Acme Corp for 8
years, terminated March 4 2026 without
cause or severance. No written contract.
Client seeks wrongful termination claim.
Attorney requested: employment records,
last 3 pay stubs, termination letter.
Next step: review docs by March 20.",
"importance": 0.92,
"memory_type": "semantic",
"tags": [
"summary",
"case-247",
"client-jane-doe",
"wrongful-termination"
],
"created_at": "2026-03-10T10:15:00Z"
}
]
// Clean recall: 1 high-signal memory
// surfaces immediately at min_imp=0.8
Implementation
# 0. Once per agent: summaries (semantic) keep their importance, raw turns (episodic) still decay.
# PUT replaces the whole policy; omitted fields take their defaults.
curl -X PUT http://localhost:3000/v1/namespaces/_dakera_agent_legal-assistant/memory_policy \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{"semantic_decay": "flat"}'
# 1. Store a conversation turn (low importance, decays)
curl -X POST http://localhost:3000/v1/memory/store \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{
"agent_id": "legal-assistant",
"content": "Client was terminated March 4 after 8 years. No severance.",
"importance": 0.3,
"memory_type": "episodic",
"tags": ["turn", "case-247"]
}'
# 2. Store the LLM-generated summary (high importance)
curl -X POST http://localhost:3000/v1/memory/store \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{
"agent_id": "legal-assistant",
"content": "CASE SUMMARY [case-247]: Jane Doe v Acme Corp: wrongful termination, 8yr tenure, no severance, no written contract. Docs requested: employment records, pay stubs, termination letter. Review by March 20.",
"importance": 0.92,
"memory_type": "semantic",
"tags": ["summary", "case-247"]
}'
# 3. Consolidate the agent's memories now (AutoPilot also does it every 12 hours)
curl -X POST http://localhost:3000/v1/agents/legal-assistant/consolidate \
-H "Authorization: Bearer dk-..."
# 4. Next session: recall only summaries and key facts
curl -X POST http://localhost:3000/v1/memory/recall \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{"agent_id": "legal-assistant", "query": "Jane Doe case details", "min_importance": 0.8, "top_k": 5}'from dakera import DakeraClient
import openai
client = DakeraClient(base_url="http://localhost:3000", api_key="dk-...")
oai = openai.OpenAI()
AGENT = "legal-assistant"
CASE_ID = "case-247"
SUMMARY_TRIGGER = 15 # summarize every 15 turns
MIN_RECALL_IMPORTANCE = 0.8
# Once per agent: summaries (semantic) keep their importance; raw turns (episodic) still decay
policy = client.get_memory_policy(f"_dakera_agent_{AGENT}")
policy.semantic_decay = "flat"
client.set_memory_policy(f"_dakera_agent_{AGENT}", policy)
conversation_turns = []
def store_turn(role: str, content: str):
"""Store a single conversation turn at low importance."""
turn_text = f"{role.upper()}: {content}"
conversation_turns.append(turn_text)
client.store_memory(
agent_id=AGENT,
content=turn_text,
importance=0.3,
memory_type="episodic",
tags=["turn", CASE_ID]
)
if len(conversation_turns) % SUMMARY_TRIGGER == 0:
summarize_and_store()
def summarize_and_store():
"""Generate an LLM summary and store it at high importance."""
turns_text = "\n".join(conversation_turns[-SUMMARY_TRIGGER:])
response = oai.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "system",
"content": "You are a legal case summarizer. Extract: client name, key claims, relevant dates, requested documents, and next steps. Be concise and factual."
}, {
"role": "user",
"content": f"Summarize this consultation segment:\n\n{turns_text}"
}]
)
summary = response.choices[0].message.content
client.store_memory(
agent_id=AGENT,
content=f"CASE SUMMARY [{CASE_ID}]: {summary}",
importance=0.92,
memory_type="semantic",
tags=["summary", CASE_ID, "auto-generated"]
)
# Consolidate now instead of waiting for AutoPilot's 12-hour cycle
client.consolidate_agent(AGENT)
def recall_case_context(query: str):
"""Recall summaries and key facts, filtering out raw turns."""
return client.recall(
agent_id=AGENT,
query=query,
min_importance=MIN_RECALL_IMPORTANCE,
top_k=5
)
# --- Simulation ---
store_turn("client", "I was terminated on March 4 after 8 years. No severance was offered.")
store_turn("attorney", "Was there a written employment contract?")
store_turn("client", "No written contract. I was at-will.")
# ... more turns ...
# At session end: force a final summary regardless of the trigger
summarize_and_store()
# Next session: only durable summaries come back
context = recall_case_context("Jane Doe wrongful termination case")
for mem in context.memories:
print(f"[{mem.importance:.2f}] {mem.content[:120]}")import { DakeraClient } from '@dakera-ai/dakera';
import OpenAI from 'openai';
const client = new DakeraClient({ baseUrl: 'http://localhost:3000', apiKey: 'dk-...' });
const oai = new OpenAI();
const AGENT = 'legal-assistant';
const CASE_ID = 'case-247';
const SUMMARY_TRIGGER = 15;
// Once per agent: summaries (semantic) keep their importance; raw turns still decay
const ns = `_dakera_agent_${AGENT}`;
const policy = await client.getMemoryPolicy(ns);
await client.setMemoryPolicy(ns, { ...policy, semantic_decay: 'flat' });
const conversationTurns: string[] = [];
async function storeTurn(role: 'client' | 'attorney', content: string) {
const turnText = `${role.toUpperCase()}: ${content}`;
conversationTurns.push(turnText);
await client.storeMemory(AGENT, {
content: turnText,
importance: 0.3,
memory_type: 'episodic',
tags: ['turn', CASE_ID],
});
if (conversationTurns.length % SUMMARY_TRIGGER === 0) {
await summarizeAndStore();
}
}
async function summarizeAndStore() {
const recentTurns = conversationTurns.slice(-SUMMARY_TRIGGER).join('\n');
const response = await oai.chat.completions.create({
model: 'gpt-4o',
messages: [
{
role: 'system',
content: 'You are a legal case summarizer. Extract: client name, key claims, relevant dates, documents requested, and next steps. Be concise.'
},
{ role: 'user', content: `Summarize this consultation:\n\n${recentTurns}` }
]
});
const summary = response.choices[0].message.content ?? '';
await client.storeMemory(AGENT, {
content: `CASE SUMMARY [${CASE_ID}]: ${summary}`,
importance: 0.92,
memory_type: 'semantic',
tags: ['summary', CASE_ID, 'auto-generated'],
});
// Consolidate now instead of waiting for AutoPilot's 12-hour cycle
await client.consolidateAgent(AGENT);
}
async function recallCaseContext(query: string) {
return client.recall(AGENT, query, { min_importance: 0.8, top_k: 5 });
}
// Usage
await storeTurn('client', 'I was terminated on March 4 after 8 years. No severance.');
await storeTurn('attorney', 'Was there a written contract?');
await storeTurn('client', 'No written contract. I was at-will.');
// Force a final summary at session end
await summarizeAndStore();
// Retrieve clean context next session
const context = await recallCaseContext('Jane Doe wrongful termination');
context.memories.forEach(m => console.log(`[${m.importance}] ${m.content.slice(0, 120)}`));use dakera_client::{DakeraClient, MemoryType, RecallRequest, StoreMemoryRequest};
let client = DakeraClient::builder("http://localhost:3000").api_key("dk-...").build()?;
let agent = "legal-assistant";
// Once per agent: summaries (semantic) keep their importance; raw turns still decay
let ns = format!("_dakera_agent_{}", agent);
let mut policy = client.get_memory_policy(&ns).await?;
policy.semantic_decay = Some("flat".into());
client.set_memory_policy(&ns, policy).await?;
// Store raw conversation turns at low importance
client.store_memory(
StoreMemoryRequest::new(agent, "CLIENT: Terminated March 4 after 8 years. No severance offered.")
.with_type(MemoryType::Episodic)
.with_importance(0.3)
.with_tags(vec!["turn".into(), "case-247".into()]),
).await?;
// Store the LLM-generated summary at high importance
let summary = generate_case_summary(&turns).await?; // your LLM call
client.store_memory(
StoreMemoryRequest::new(agent, format!("CASE SUMMARY [case-247]: {}", summary))
.with_type(MemoryType::Semantic)
.with_importance(0.92)
.with_tags(vec!["summary".into(), "case-247".into()]),
).await?;
// Consolidate now instead of waiting for AutoPilot's 12-hour cycle
client.consolidate_agent(agent).await?;
// Recall only high-importance summaries next session
let context = client.recall(
RecallRequest::new(agent, "Jane Doe wrongful termination case details")
.with_min_importance(0.8)
.with_top_k(5),
).await?;
for mem in &context.memories {
let preview: String = mem.content.chars().take(120).collect();
println!("[{:.2}] {}", mem.importance, preview);
}package main
import (
"context"
"fmt"
"log"
dakera "github.com/dakera-ai/dakera-go"
)
func main() {
client := dakera.NewClientWithOptions(dakera.ClientOptions{
BaseURL: "http://localhost:3000",
APIKey: "dk-...",
})
ctx := context.Background()
agent := "legal-assistant"
caseID := "case-247"
// Once per agent: summaries (semantic) keep their importance; raw turns still decay
ns := "_dakera_agent_" + agent
policy, err := client.GetMemoryPolicy(ctx, ns)
if err != nil {
log.Fatal(err)
}
flat := "flat"
policy.SemanticDecay = &flat
if _, err := client.SetMemoryPolicy(ctx, ns, *policy); err != nil {
log.Fatal(err)
}
// Store a raw turn at low importance
turnImp := float32(0.3)
client.StoreMemory(ctx, agent, dakera.StoreMemoryRequest{
Content: "CLIENT: Terminated March 4 after 8 years. No severance.",
Importance: &turnImp,
MemoryType: "episodic",
Tags: []string{"turn", caseID},
})
// After summarization, store the summary at high importance
summary := generateCaseSummary(turns) // your LLM call
summaryImp := float32(0.92)
client.StoreMemory(ctx, agent, dakera.StoreMemoryRequest{
Content: fmt.Sprintf("CASE SUMMARY [%s]: %s", caseID, summary),
Importance: &summaryImp,
MemoryType: "semantic",
Tags: []string{"summary", caseID},
})
// Consolidate now instead of waiting for AutoPilot's 12-hour cycle
client.ConsolidateAgent(ctx, agent)
// Recall only summaries at the next session
minImp := float32(0.8)
result, err := client.Recall(ctx, agent, dakera.RecallRequest{
Query: "Jane Doe wrongful termination case",
MinImportance: &minImp,
TopK: 5,
})
if err != nil {
log.Fatal(err)
}
for _, mem := range result.Memories {
fmt.Printf("[%.2f] %s\n", mem.Importance, mem.Content[:min(120, len(mem.Content))])
}
}Keep the insight, let the transcript fade
Decay and consolidation run server-side, with no infrastructure to manage.
SDK Reference
| Method | SDK | Purpose |
|---|---|---|
store_memory(agent_id, content, importance, memory_type, tags) | Python | Store turn or summary with importance score |
storeMemory(agentId, {content, importance, memory_type, tags}) | TypeScript | Store turn or summary with importance score |
client.store_memory(StoreMemoryRequest::new(agent_id, content)).await? | Rust | Store turn or summary with importance score |
client.StoreMemory(ctx, agentID, dakera.StoreMemoryRequest{...}) | Go | Store turn or summary with importance score |
recall(agent_id, query, min_importance, top_k) | Python | Retrieve summaries above importance threshold |
recall(agentId, query, {min_importance, top_k}) | TypeScript | Retrieve summaries above importance threshold |
search_memories(agent_id, query) | Python | Find recent turns before summarizing (POST /v1/memory/search) |
searchMemories(agentId, query, {top_k}) | TypeScript | Find recent turns before summarizing |
consolidate_agent(agent_id) / POST /v1/agents/{id}/consolidate | Python / REST | Consolidate the agent's memories now |
set_memory_policy(namespace, policy) | Python | Set semantic_decay to flat so summaries keep their importance |
Performance Considerations
min_importance that keeps raw turns out of recall- Summarization is the bottleneck, not storage. LLM calls for summarization take 1–4s. Run them asynchronously — store the raw turns immediately, summarize in a background job, and update the store when complete.
- min_importance is a filter, not a ranking. It keeps raw turns out of the results; among the memories that pass, recall still ranks by relevance, importance and recency.
- Consolidate in off-peak windows. Consolidation scans the agent's memories, so for agents with many memories, run on-demand consolidation during low-traffic periods (e.g., overnight).
- Chunk very long consultations. If a consultation runs long, generate intermediate summaries every 15 turns, as the code does, rather than waiting until the end. This prevents the LLM context window from being overwhelmed.
Edge Cases
If the LLM summarization call fails (timeout, rate limit), the raw turns remain at low importance. Always implement a retry with exponential backoff and a fallback: store the raw turns concatenated as a summary at importance 0.8 if the LLM fails after 3 attempts, so it still passes the recall filter. Never skip summary storage: the raw turns stay below the recall threshold.
Triggering summarization on fewer than 5 turns produces shallow summaries like "User greeted attorney." Guard against this with a minimum turn count of 8–10 before the first summarization trigger fires. Check len(conversation_turns) >= min_turns before calling the LLM.
If a session crashes after storing the summary but before clearing the turn buffer, a retry will generate and store a second summary covering the same turns. Deduplicate by tagging summaries with a deterministic ID: sha256(case_id + turn_range), then check for existing summaries with that tag before storing.
Legal and compliance workflows sometimes need the original verbatim transcript, not just summaries. Store raw turns under a separate agent_id (e.g., legal-assistant-audit, which gets its own namespace) at importance 1.0, and give that namespace a memory policy with episodic_decay set to flat. The main agent's memories can decay freely while audit history keeps its importance.
If a single consultation covers multiple unrelated topics (e.g., client discusses both a contract dispute and estate planning), generate separate summaries per topic rather than one combined summary. Use topic detection (keyword-based or LLM-based) to split the turn buffer before summarizing. Tag each summary with its topic for cleaner recall.
Advanced Configuration: Decay Strategies & Summarization Tuning
Importance Levels by Memory Type
| Memory Type | Recommended Importance | Returned at min_importance=0.8 |
|---|---|---|
| Raw greeting / small talk | 0.1–0.2 | No |
| Conversation turn (factual) | 0.3–0.4 | No |
| Working note / partial summary | 0.5–0.6 | No |
| Key fact / decision point | 0.8 | While decay keeps it at 0.8 or above |
| LLM-generated summary | 0.92 | Yes, with semantic_decay set to flat |
| Identity / case constants | 1.0 | Yes while above 0.8; store as semantic to keep it flat |
Summarization Prompt Template
SYSTEM: You are a precise case-note summarizer for legal consultations.
Extract only facts stated by the client. Include:
1. Client full name (if mentioned)
2. Key legal claim(s) and relevant dates
3. Evidence or documents discussed
4. Attorney's requested next steps
5. Any deadlines or court dates mentioned
Output as a single paragraph under 200 words. Use past tense.
Do not infer, speculate, or add information not present in the transcript.
USER: Summarize the following consultation segment:
{TURNS}
Consolidation Schedule
For high-volume systems, consolidate on a schedule rather than after every summary. AutoPilot already runs every 12 hours; for a fixed time, such as a daily job at 2 AM local time, call consolidate_agent() from your own scheduler.
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.