NewRelease 0.12.3 is out: Engine v0.12.3, Dashboard 0.5.2, Helm 0.12.3, the four SDKs, CLI 0.8.2 and MCP 0.12.3Release 0.12.3 is outSee what's new →
Intermediate Retrieval

Episodic Replay

Reconstruct past conversations in chronological order. Let your agent review the exact sequence of a debugging session, a design discussion, or any multi-step workflow — and continue with full context intact.

⏱ ~20 min to implement 📦 Requires: Dakera v0.11+
Start Building →
Prerequisites
  • Running Dakera server (Quickstart guide)
  • Familiarity with session IDs and metadata-based filtering
  • Understanding of sequence numbers vs. timestamps for ordering

The Problem

Semantic recall finds the most relevant memories — not the most sequential ones. When a user asks a coding assistant "what did we figure out in last Tuesday's debugging session?", the assistant needs to reconstruct the exact sequence of that session: the initial error, the hypotheses explored, the failed attempts, and the final fix. Pure vector recall returns the most relevant exchanges from that session but scrambles their order, destroying the narrative arc.

Without ordered replay, the agent cannot understand why a decision was made. It might resurface an approach that was already tried and failed in the same session. It cannot tell the user "we ruled out X because Y" if it cannot reconstruct the sequence in which X was explored and rejected. Causality requires order.

Replay vs. Cross-Session Context

Cross-Session Context retrieves the highest-relevance memories across many sessions. Episodic Replay retrieves all memories from a specific session in exact sequence. Use Cross-Session Context for "what do I know about this user?" Use Episodic Replay for "replay session X so I can continue from exactly where we left off."

Architecture: Episodic Memory Buffer with Replay Selection

Each interaction in a session is stored with two pieces of metadata: a session ID (identifies which conversation it belongs to) and a sequence number (an incrementing integer preserving order within that session). The session ID also goes in the memory's tags, because batch filters match tags, not metadata. Replay is a filter-based batch recall on that tag (POST /v1/memories/recall/batch, no query and no embedding), then a client-side sort by sequence number. The sequence number is the canonical ordering key: timestamps have millisecond ambiguity; sequence numbers do not. If you store turns in a Dakera session instead, GET /v1/sessions/{id}/memories lists them the same way.

Sequence diagram of recording and replaying session sess-20260519 between the developer, the coding assistant and Dakera (agent codeassist-dev-7). On Tuesday every turn is stored as it happens: #1 the TypeError at parser.js:42 as an error at 0.80, #2 the null check hypothesis at 0.75, #3 still failing as normal at 0.72, #4 fetchData() not awaited as a decision at 0.92, and #5 fixed with tests green as a solution at 0.95, each tagged with the session ID and carrying its sequence number in metadata. On Thursday the developer asks what they found; the assistant lists the session with a batch recall filtered on the session tag (limit 200, no query), sorts the turns by their sequence number, and answers that await was the fix.Sequence diagram of recording and replaying session sess-20260519 between the developer, the coding assistant and Dakera (agent codeassist-dev-7). On Tuesday every turn is stored as it happens: #1 the TypeError at parser.js:42 as an error at 0.80, #2 the null check hypothesis at 0.75, #3 still failing as normal at 0.72, #4 fetchData() not awaited as a decision at 0.92, and #5 fixed with tests green as a solution at 0.95, each tagged with the session ID and carrying its sequence number in metadata. On Thursday the developer asks what they found; the assistant lists the session with a batch recall filtered on the session tag (limit 200, no query), sorts the turns by their sequence number, and answers that await was the fix.
Store every turn with its session and sequence number, then list and sort them to replay it in order.Full size ↗Full size ↗

Step-by-Step Implementation

  1. Generate a stable session ID at conversation start
    Create a unique session ID (UUID or timestamp-based) at the start of each conversation. This ID ties all memories in the session together and is the key used for replay. Store it in your application's session state. Example: sess-20260519-u8a2f.
  2. Store each interaction with session ID and sequence number
    Every turn — both user messages and agent responses — gets stored immediately after it occurs. Include session_id and sequence in metadata, and the session ID as a tag. Use an integer counter starting at 1, not timestamps, for sequence. Two messages at the exact same millisecond would sort ambiguously with timestamps; integers never have this problem.
  3. Tag messages by role and significance
    Tag each memory with role:user or role:agent, and optionally mark significant exchanges: decision, breakthrough, error, solution. These tags enable selective replay — replaying only decision points rather than every turn, which saves context window space.
  4. List the full session by its tag
    To replay a session, run a batch recall filtered on the session tag with a generous limit (200). It takes no query, so it returns every turn of the session rather than the most relevant ones, and only that session's. Sort the result by metadata.sequence ascending to restore the original order. For selective replay, keep the turns whose stored significance is decision, solution or breakthrough.
  5. Compress completed sessions for long-term storage
    After a session ends and its debugging task is complete, the full turn-by-turn replay becomes less valuable. Keep key decision points and final resolutions at high importance; reduce importance of exploratory back-and-forth. This keeps the memory store lean without losing the critical causal chain.

Implementation

# Store interaction 1: user reports error
curl -X POST http://localhost:3000/v1/memory/store \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "codeassist-dev-7",
    "content": "[user] TypeError: cannot read property length of undefined in parser.js:42",
    "memory_type": "episodic",
    "importance": 0.80,
    "tags": ["episode", "sess-20260519", "role:user", "error"],
    "metadata": {"session_id": "sess-20260519", "sequence": 1, "role": "user", "significance": "error"}
  }'

# Store interaction 2: agent hypothesis
curl -X POST http://localhost:3000/v1/memory/store \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "codeassist-dev-7",
    "content": "[agent] Likely null check missing. Suggested: if (arr && arr.length) before accessing.",
    "memory_type": "episodic",
    "importance": 0.75,
    "tags": ["episode", "sess-20260519", "role:agent", "hypothesis"],
    "metadata": {"session_id": "sess-20260519", "sequence": 2, "role": "agent", "significance": "hypothesis"}
  }'

# (Interaction 3 is stored the same way: "normal", importance 0.72, sequence 3)

# Store interaction 4: root cause found
curl -X POST http://localhost:3000/v1/memory/store \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "codeassist-dev-7",
    "content": "[agent] Root cause: fetchData() not awaited at line 38 causing async race. Fix: add await.",
    "memory_type": "episodic",
    "importance": 0.92,
    "tags": ["episode", "sess-20260519", "role:agent", "decision"],
    "metadata": {"session_id": "sess-20260519", "sequence": 4, "role": "agent", "significance": "decision"}
  }'

# Store interaction 5: confirmed fix
curl -X POST http://localhost:3000/v1/memory/store \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "codeassist-dev-7",
    "content": "[user] Fixed! Adding await before fetchData() resolved the TypeError. Tests green.",
    "memory_type": "episodic",
    "importance": 0.95,
    "tags": ["episode", "sess-20260519", "role:user", "solution"],
    "metadata": {"session_id": "sess-20260519", "sequence": 5, "role": "user", "significance": "solution"}
  }'

# Replay the session: list every memory with its tag (no query, no embedding),
# then sort by metadata.sequence client-side. Selective replay keeps only
# decision, solution and breakthrough turns from the same list.
curl -X POST http://localhost:3000/v1/memories/recall/batch \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "codeassist-dev-7", "filter": {"tags": ["sess-20260519"]}, "limit": 200}'
from dakera import BatchMemoryFilter, BatchRecallRequest, DakeraClient
import uuid
from typing import Literal

client = DakeraClient(base_url="http://localhost:3000", api_key="dk-...")

Role = Literal["user", "agent"]
Significance = Literal["normal", "hypothesis", "error", "decision", "solution", "breakthrough"]

IMPORTANCE_BY_SIGNIFICANCE = {
    "normal": 0.72,
    "hypothesis": 0.75,
    "error": 0.80,
    "decision": 0.92,
    "solution": 0.95,
    "breakthrough": 0.97,
}
KEY_TURNS = {"decision", "solution", "breakthrough"}

class EpisodicSession:
    """Records a conversation episode with full replay capability."""

    def __init__(self, agent_id: str, session_id: str = None):
        self.agent_id = agent_id
        self.session_id = session_id or f"sess-{uuid.uuid4().hex[:8]}"
        self.sequence = 0

    def record(self, role: Role, content: str, significance: Significance = "normal") -> dict:
        """Store one interaction turn."""
        self.sequence += 1
        return client.store_memory(
            agent_id=self.agent_id,
            content=f"[{role}] {content}",
            memory_type="episodic",
            importance=IMPORTANCE_BY_SIGNIFICANCE[significance],
            # The session ID is a tag because batch filters match tags, not metadata
            tags=["episode", self.session_id, f"role:{role}", significance],
            metadata={"session_id": self.session_id, "sequence": self.sequence,
                      "role": role, "significance": significance}
        )

    def _turns(self) -> list:
        """Every turn of this session, in sequence order (filter-based, no query)."""
        resp = client.batch_recall(BatchRecallRequest(
            agent_id=self.agent_id,
            filter=BatchMemoryFilter(tags=[self.session_id]),
            limit=200
        ))
        return sorted(resp.memories, key=lambda m: m.metadata["sequence"])

    def replay_full(self) -> list[str]:
        """Replay all turns in this session, in order."""
        return [m.content for m in self._turns()]

    def replay_decisions_only(self) -> list[str]:
        """Replay only decisions, solutions and breakthroughs, in order."""
        return [m.content for m in self._turns() if m.metadata["significance"] in KEY_TURNS]

# --- Record the debugging session ---
session = EpisodicSession("codeassist-dev-7", "sess-20260519")

session.record("user",
    "TypeError: cannot read property 'length' of undefined in parser.js:42",
    significance="error"
)
session.record("agent",
    "Likely a null check missing. Try: if (arr && arr.length) before accessing.",
    significance="hypothesis"
)
session.record("user",
    "Still failing on the same line. I already had that null check.",
    significance="normal"
)
session.record("agent",
    "Root cause: fetchData() at line 38 is not awaited. Async race condition. Add 'await' before the call.",
    significance="decision"
)
session.record("user",
    "That was it! Adding await before fetchData() fixed the TypeError. Tests all green.",
    significance="solution"
)

# --- Replay later (new process, resuming work) ---
replay_session = EpisodicSession("codeassist-dev-7", "sess-20260519")

print("=== FULL REPLAY ===")
for turn in replay_session.replay_full():
    print(turn)

print("\n=== DECISIONS ONLY ===")
for turn in replay_session.replay_decisions_only():
    print(turn)

# Output (decisions only):
# [agent] Root cause: fetchData() at line 38 is not awaited. Async race condition. Add 'await' before the call.
# [user] That was it! Adding await before fetchData() fixed the TypeError. Tests all green.
import { DakeraClient } from '@dakera-ai/dakera';
import { randomUUID } from 'crypto';

const client = new DakeraClient({ baseUrl: 'http://localhost:3000', apiKey: 'dk-...' });

type Role = 'user' | 'agent';
type Significance = 'normal' | 'hypothesis' | 'error' | 'decision' | 'solution' | 'breakthrough';

const IMPORTANCE: Record<Significance, number> = {
  normal: 0.72,
  hypothesis: 0.75,
  error: 0.80,
  decision: 0.92,
  solution: 0.95,
  breakthrough: 0.97,
};
const KEY_TURNS = new Set<string>(['decision', 'solution', 'breakthrough']);

class EpisodicSession {
  private agentId: string;
  private sessionId: string;
  private seq = 0;

  constructor(agentId: string, sessionId?: string) {
    this.agentId = agentId;
    this.sessionId = sessionId ?? `sess-${randomUUID().slice(0, 8)}`;
  }

  get id(): string { return this.sessionId; }

  async record(role: Role, content: string, sig: Significance = 'normal'): Promise<void> {
    this.seq += 1;
    await client.storeMemory(this.agentId, {
      content: `[${role}] ${content}`,
      memory_type: 'episodic',
      importance: IMPORTANCE[sig],
      // The session ID is a tag because batch filters match tags, not metadata
      tags: ['episode', this.sessionId, `role:${role}`, sig],
      metadata: { session_id: this.sessionId, sequence: this.seq, role, significance: sig },
    });
  }

  // Every turn of this session in sequence order (filter-based, no query)
  private async turns() {
    const resp = await client.batchRecall({
      agent_id: this.agentId,
      filter: { tags: [this.sessionId] },
      limit: 200,
    });
    if (resp.truncated) console.warn('Session has more than 200 turns; raise the limit');
    return [...resp.memories].sort(
      (a, b) => Number(a.metadata?.sequence ?? 0) - Number(b.metadata?.sequence ?? 0)
    );
  }

  async replayFull(): Promise<string[]> {
    return (await this.turns()).map(m => m.content);
  }

  async replayDecisionsOnly(): Promise<string[]> {
    return (await this.turns())
      .filter(m => KEY_TURNS.has(String(m.metadata?.significance)))
      .map(m => m.content);
  }
}

// Record
const session = new EpisodicSession('codeassist-dev-7', 'sess-20260519');
await session.record('user', "TypeError: cannot read property 'length' of undefined in parser.js:42", 'error');
await session.record('agent', 'Likely a null check missing. Try: if (arr && arr.length) before accessing.', 'hypothesis');
await session.record('user', 'Still failing on the same line. I already had that null check.');
await session.record('agent', "Root cause: fetchData() at line 38 is not awaited. Add 'await' before the call.", 'decision');
await session.record('user', 'That was it! Adding await fixed the TypeError. Tests all green.', 'solution');

// Replay later
const replay = new EpisodicSession('codeassist-dev-7', 'sess-20260519');
console.log('=== FULL REPLAY ===');
(await replay.replayFull()).forEach(t => console.log(t));
console.log('\n=== DECISIONS ONLY ===');
(await replay.replayDecisionsOnly()).forEach(t => console.log(t));
use dakera_client::{
    BatchMemoryFilter, BatchRecallRequest, DakeraClient, MemoryType, StoreMemoryRequest,
};
use serde_json::json;

let client = DakeraClient::builder("http://localhost:3000").api_key("dk-...").build()?;
let agent_id = "codeassist-dev-7";
let session_id = "sess-20260519";

// (role, content, significance, importance)
let turns = vec![
    ("user", "TypeError: cannot read property length of undefined in parser.js:42", "error", 0.80f32),
    ("agent", "Likely null check missing. Try: if (arr && arr.length) before accessing.", "hypothesis", 0.75),
    ("user", "Still failing. Already tried the null check.", "normal", 0.72),
    ("agent", "Root cause: fetchData() not awaited at line 38. Async race. Add await.", "decision", 0.92),
    ("user", "Fixed! Tests all green. Shipping to staging.", "solution", 0.95),
];

// Store all turns: session ID as a tag, sequence number in metadata
for (seq, (role, content, significance, importance)) in turns.iter().enumerate() {
    client.store_memory(
        StoreMemoryRequest::new(agent_id, format!("[{}] {}", role, content))
            .with_type(MemoryType::Episodic)
            .with_importance(*importance)
            .with_tags(vec!["episode".into(), session_id.into(), format!("role:{}", role), significance.to_string()])
            .with_metadata(json!({ "session_id": session_id, "sequence": seq + 1, "significance": significance })),
    ).await?;
}

// Replay: list the session's turns by tag (no query), then sort by sequence
let listed = client.batch_recall(
    BatchRecallRequest::new(agent_id)
        .with_filter(BatchMemoryFilter::default().with_tags(vec![session_id.into()]))
        .with_limit(200),
).await?;

let sequence = |m: &dakera_client::RecalledMemory| {
    m.metadata.as_ref().and_then(|v| v["sequence"].as_u64()).unwrap_or(0)
};
let mut memories = listed.memories;
memories.sort_by_key(|m| sequence(m));

for m in &memories {
    println!("{}", m.content);
}
package main

import (
    "context"
    "fmt"
    "log"
    "sort"

    dakera "github.com/dakera-ai/dakera-go"
)

type Turn struct {
    Role         string
    Content      string
    Significance string
    Importance   float32
}

// sequence reads the turn number stored in metadata (JSON numbers decode as float64)
func sequence(m dakera.RecalledMemory) float64 {
    n, _ := m.Metadata["sequence"].(float64)
    return n
}

func main() {
    client := dakera.NewClientWithOptions(dakera.ClientOptions{
        BaseURL: "http://localhost:3000",
        APIKey:  "dk-...",
    })
    ctx := context.Background()
    agentID := "codeassist-dev-7"
    sessionID := "sess-20260519"

    turns := []Turn{
        {"user", "TypeError: cannot read property length of undefined in parser.js:42", "error", 0.80},
        {"agent", "Likely null check missing. Try: if (arr && arr.length) before accessing.", "hypothesis", 0.75},
        {"user", "Still failing. Already tried the null check.", "normal", 0.72},
        {"agent", "Root cause: fetchData() at line 38 not awaited. Async race. Fix: add await.", "decision", 0.92},
        {"user", "Fixed! Tests green. Shipping to staging.", "solution", 0.95},
    }

    // Record all turns: session ID as a tag, sequence number in metadata
    for i, t := range turns {
        importance := t.Importance
        if _, err := client.StoreMemory(ctx, agentID, dakera.StoreMemoryRequest{
            Content:    fmt.Sprintf("[%s] %s", t.Role, t.Content),
            MemoryType: "episodic",
            Importance: &importance,
            Tags:       []string{"episode", sessionID, "role:" + t.Role, t.Significance},
            Metadata: map[string]interface{}{
                "session_id":   sessionID,
                "sequence":     i + 1,
                "significance": t.Significance,
            },
        }); err != nil {
            log.Fatal(err)
        }
    }

    // Replay: list the session's turns by tag (no query), then sort by sequence
    listed, err := client.BatchRecall(ctx, dakera.BatchRecallRequest{
        AgentID: agentID,
        Filter:  dakera.BatchMemoryFilter{Tags: []string{sessionID}},
        Limit:   200,
    })
    if err != nil {
        log.Fatal(err)
    }
    sort.Slice(listed.Memories, func(i, j int) bool {
        return sequence(listed.Memories[i]) < sequence(listed.Memories[j])
    })
    for _, m := range listed.Memories {
        fmt.Println(m.Content)
    }
}

Give your coding assistant perfect memory of every debug session.

Episodic replay preserves the causal chain your agent needs to continue intelligently.

Get Started →

Before & After: Replay State

The left shows what a semantic recall returns for a debugging session — relevant but unordered. The right shows episodic replay with sequence metadata — ordered and causally coherent.

Before: Semantic Recall Only
{
  // Recall returns by relevance,
  // not by order. Causality is lost.

  query: "debugging session",
  results: [
    "#5: Fixed! Tests green.",       // relevant
    "#4: Root cause: async race.",   // relevant
    "#1: TypeError at parser.js",    // relevant
    "#2: Try null check fix",        // relevant
    // #3 may not appear at all
    // (user said "still failing" —
    //  low semantic relevance)
  ]
  // Agent cannot tell #3 happened
  // BEFORE #4 — cannot know that
  // null check was already tried.
}
After: Episodic Replay with Sequence
{
  // List by session tag + sort by sequence.
  // Full causal chain restored.

  session_id: "sess-20260519",
  replay: [
    "#1 [user] TypeError in parser.js",
    "#2 [agent] Try null check fix",
    "#3 [user] Still failing (tried it)",
    "#4 [agent] Root: async race, await!",
    "#5 [user] Fixed! Tests green.",
  ],
  // Agent now KNOWS: null check was
  // tried and failed (#3), so it will
  // never suggest it again. The fix
  // was adding await (#4).
}

Real-World Example: Coding Assistant Reviewing Past Debug Sessions

Scenario: Orbit Code is an AI pair programmer deployed to engineering teams. Developers spend hours in complex debugging sessions and frequently return to the same codebase days later. Without episodic replay, the assistant starts fresh each time. With it, the assistant can reference specific past sessions by name and continue with full context.

A Typical Developer Workflow Across 3 Days

Day 1 (Tuesday), Session sess-20260519: Developer Priya spends 90 minutes debugging an async race condition in the parser module. Five turns are recorded. The breakthrough (adding await) is tagged as a decision at importance 0.92. The session ends with the fix shipped to staging.

Day 3 (Thursday): Priya opens a new session. She says "I need to refactor the parser module we fixed Tuesday, can you remind me what we found?" The assistant replays sess-20260519 in order, identifies the root cause (#4) and resolution (#5), and responds: "In Tuesday's session, we found that fetchData() wasn't awaited at line 38, causing a race condition. We added await and it resolved the TypeError. The refactoring should ensure all async calls in that module are properly awaited." Priya says it felt like talking to a teammate who was there.

Day 5 (Saturday): A new bug appears in the same module. The agent proactively replays the related sessions and warns: "This looks similar to the async race we fixed in session sess-20260519. Check if the new processChunks() function is missing an await." It was. Fix takes 3 minutes instead of 90.

Pro Tip: Selective Replay Saves Context Window

Full episodic replay of a 50-turn session can consume 3,000–5,000 tokens in your LLM context window. For resumption scenarios, use selective replay: replay only turns whose stored significance is decision, breakthrough, or solution. Select them by that label, not by an importance cut: importance decays over time (half-life 168 hours by default), so a 0.92 decision can be under 0.90 the next day. Selective replay keeps the causally important turns at a fraction of the tokens. Reserve full replay for explicit user requests ("show me the full session").

Replay Selection Algorithm: From Archive to Active Context

This diagram shows how the session tag filter and the significance selector work together to extract a minimal, causally relevant replay set from an episodic archive.

Workflow of full and selective replay. A replay request names one session ID. Every replay lists the session with a Dakera batch recall filtered on the session tag (no query, limit 200) and sorts the turns by their sequence number. A full replay, made on request, keeps every turn; a selective replay, used to resume work, keeps only the turns whose stored significance is decision, solution or breakthrough. Either way the turns go into the LLM prompt in causal order.Workflow of full and selective replay. A replay request names one session ID. Every replay lists the session with a Dakera batch recall filtered on the session tag (no query, limit 200) and sorts the turns by their sequence number. A full replay, made on request, keeps every turn; a selective replay, used to resume work, keeps only the turns whose stored significance is decision, solution or breakthrough. Either way the turns go into the LLM prompt in causal order.
Selective replay keeps the decisions and solutions of one session, back in order.Full size ↗Full size ↗

Performance Characteristics

200
limit of the session listing
200
Max recommended turns per session before compression
3
Significance labels selective replay keeps

A replay is one filter-based batch recall, with no embedding, plus a client-side sort by sequence number. For sessions exceeding 200 turns, consider compressing intermediate exchanges into summaries using the Summarization & Decay pattern, keeping only decision points and the final resolution at full importance.

Edge Cases & Developer Gotchas

Gotcha 1: Sequence Numbers Must Be Monotonic

If you use floating point timestamps instead of integer sequence counters, two events that occur within the same millisecond (fast async code, batch operations) will have identical or reversed ordering. Solution: Use an integer counter that increments atomically with each stored turn. Keep this counter in your application state for the duration of the session. Never rely on timestamps alone for sequence ordering.

Gotcha 2: A Small limit Drops Turns

If a session has 80 turns and you list it with limit=50, 30 turns are left out. The replay is incomplete and the sequence has gaps. Solution: Always set limit to at least 2x the expected session length, and check the response's truncated flag. Use a generous default of 200 for replay listings. Storage cost per turn is negligible; do not optimize aggressively here.

Gotcha 3: Cross-Session Session ID Collisions

If your session ID generation is weak (e.g., sequential integers) and two different users happen to have the same session ID, a replay query will return mixed memories from different users. Solution: Use UUIDs or prefix session IDs with the user ID: user-alice-sess-a8f2b1. Never rely on simple numeric session IDs across a multi-tenant system.

Gotcha 4: Storing Both Sides Doubles Your Memory Count

Every session turn requires two stores (user message + agent response). A 40-turn conversation produces 80 memory entries. For long-lived users with hundreds of sessions, this accumulates quickly. Solution: Only store agent responses that contain decisions, hypotheses, or significant information. Skip agent responses that are pure clarifying questions ("Could you share the error message?") as they add noise without signal.

Gotcha 5: A Semantic Recall Is Not a Session Filter

A recall with "sess-abc123 debugging turns" as its query can also return memories from other sessions that contain similar debugging content: text in the query is not a filter. Solution: Replay with a filter, as above: the batch recall's tags filter on the session tag, or a recall's session_id when you store turns in a Dakera session.

SDK Reference

Operation Python TypeScript Purpose
Store episode turn client.store_memory(agent_id, content, importance, memory_type, tags, metadata) client.storeMemory(agentId, {content, importance, memory_type, tags, metadata}) Persist one conversation turn
List a session for replay client.batch_recall(BatchRecallRequest(agent_id, filter=BatchMemoryFilter(tags=[session_id]), limit=200)) client.batchRecall({agent_id, filter: {tags: [sessionId]}, limit: 200}) Every turn of one session, no query (sort by metadata.sequence after)
Selective replay The same listing, filtered on metadata["significance"] The same listing, filtered on metadata.significance Only decisions, solutions and breakthroughs
List sessions client.list_sessions(agent_id) client.listSessions({agent_id}) Browse past Dakera sessions to select for replay
Get session memories client.session_memories(session_id) client.sessionMemories(sessionId) Memories stored with a Dakera session_id
Compress old episodes client.update_importance(agent_id, memory_ids, importance) client.updateImportance(agentId, {memory_ids, importance}) Archive old exploratory turns
Delete episode client.forget(agent_id, memory_id) client.forget(agentId, memoryId) Remove a single turn from history
Bulk delete session client.batch_forget(BatchForgetRequest(agent_id, filter=BatchMemoryFilter(tags=[session_id]))) client.batchForget({agent_id, filter: {tags: [sessionId]}}) Delete all turns of a completed session
Advanced Configuration

Session Compression After Completion

Once a debugging session is resolved, compress exploratory turns and keep only the causal chain:

def compress_completed_session(session_id: str, agent_id: str):
    """
    After session ends: keep decisions + solutions as they are,
    archive exploratory turns to low importance.
    """
    turns = client.batch_recall(BatchRecallRequest(
        agent_id=agent_id,
        filter=BatchMemoryFilter(tags=[session_id]),
        limit=200
    )).memories
    exploratory = [m.id for m in turns
                   if m.metadata["significance"] not in ("decision", "solution", "breakthrough")]
    if exploratory:
        client.update_importance(agent_id=agent_id, memory_ids=exploratory, importance=0.30)

Cross-Session Pattern Detection

Search across sessions to find recurring patterns (e.g., the same error appearing in multiple sessions). These are semantic recalls across the agent's whole history; the session each hit came from is in its metadata:

queries = [
    ("TypeError async race condition", 20),
    ("null reference undefined errors root cause", 20),
    ("solutions that worked parser module fixes", 10),
]
hits = [m for q, k in queries
        for m in client.recall(agent_id="codeassist-dev-7", query=q, top_k=k).memories]
sessions = {m.metadata["session_id"] for m in hits if m.metadata}
# Detect that async race appeared in 3 separate sessions
# Recommend: add async linting rule to prevent recurrence

Training Data Extraction from Successful Sessions

Sessions with confirmed solutions make excellent fine-tuning examples. Extract decision + resolution pairs:

training_pairs = []
for session_id in my_session_ids:  # the session IDs your application issued
    turns = client.batch_recall(BatchRecallRequest(
        agent_id="codeassist-dev-7",
        filter=BatchMemoryFilter(tags=[session_id]),
        limit=200
    )).memories
    turns.sort(key=lambda m: m.metadata["sequence"])
    decisions = [m for m in turns if m.metadata["significance"] == "decision"]
    solutions = [m for m in turns if m.metadata["significance"] == "solution"]
    if decisions and solutions:
        training_pairs.append({
            "problem": decisions[0].content,
            "resolution": solutions[-1].content
        })

When to Use This Pattern

Try it live

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.