Intermediate Architecture

Session Handoff

~20 min to implement 📦 Requires: Dakera v0.11+

Transfer full conversation context between agents — bot to human, specialist to specialist, or across devices — with zero context loss. The session handoff pattern uses Dakera's session API to capture, persist, and replay exactly what the receiving agent needs to continue seamlessly from where the previous one stopped.

Implement Session Handoff →
Prerequisites
  • Running Dakera server (see Quickstart)
  • API key with session management permissions
  • A multi-agent or bot-to-human escalation setup

Problem

Conversations frequently cross agent boundaries. A tier-1 support bot hits its knowledge limit and escalates to a human agent. A general assistant identifies a specialist problem and routes to a domain expert. A user switches from mobile to desktop mid-conversation. In each case, without a proper handoff mechanism, the receiving agent starts cold:

  • The human agent asks "Can you describe your problem?" — the user has already explained it twice to the bot
  • The specialist agent has no context on what was already tried
  • The desktop session has no idea what was discussed on mobile 10 minutes ago

The session handoff pattern stores structured context at handoff time and provides it as the first recall result for the receiving agent — so they start with full awareness of the situation, the user's emotional state, and what has already been attempted.

What Makes a Good Handoff Summary

A handoff summary should contain three things: (1) the user's goal — what they are trying to accomplish, (2) the situation — what has been tried, what worked, what failed, and the current state, (3) the emotional context — frustration level, urgency, any commitments made. Facts alone are not enough — the receiving agent needs to know how to approach the user, not just what the problem is.

Architecture

The pattern has two distinct phases — the outgoing session (source agent) and the incoming session (target agent). Each phase uses different Dakera operations.

Handoff Flow Diagram

t=0 t=handoff t=end Session 1: Source Agent (Bot) store_memory() throughout conversation captures: issue, steps tried, user sentiment HANDOFF store summary end_session() Session 2: Target Agent (Human) recall() → gets handoff summary first starts with full context, continues seamlessly Dakera Memory Store Session memories stored during Session 1 Handoff summary importance: 1.0 Long-term memories user history, preferences writes publishes recalls Sessions are ephemeral; memories and handoff summary persist indefinitely

Step-by-Step Implementation

  • Store key context throughout the source session
    During the bot conversation, store memories for important events as they happen: the user's stated problem, steps already tried, any commitments made ("I'll escalate this for you"), and sentiment signals ("User is frustrated — third time calling about this"). Use importance 0.7–0.85 for these — high enough to surface during handoff recall, but below the handoff summary itself.
  • Detect the handoff trigger
    Triggers are application-specific: user requests human help, bot confidence falls below threshold, issue classification requires escalation, wait time exceeds SLA, or the user expresses high frustration. When the trigger fires, begin the handoff sequence immediately — do not continue attempting to resolve the issue after the decision to escalate.
  • Generate and store the handoff summary at importance 1.0
    The handoff summary is the single most important piece of context the receiving agent will see. Store it with importance: 1.0 so it always surfaces first. Include: (1) user's goal, (2) current situation, (3) what was tried and what happened, (4) any commitments made by the bot, (5) urgency and emotional state, (6) recommended next action. Tag it with ["handoff", "session-summary"].
  • End the source session with metadata
    Call end_session() with metadata indicating the handoff target, reason for escalation, and the handoff summary memory ID. This creates an audit trail and enables analytics on escalation rates and reasons. The session data remains queryable after ending — it is not deleted.
  • Start the target agent's session linked to the original
    Start a new session for the receiving agent with handoff_from metadata pointing to the source session ID. This allows analytics tools to reconstruct the full conversation chain across sessions and measure handoff quality (did context transfer result in faster resolution?).
  • Recall context as the first action in the new session
    Before the receiving agent says anything, call recall() with a query about the user's issue. Because the handoff summary has importance 1.0, it will be the first result. The agent can then open with: "I see you've been dealing with a billing discrepancy from January 15th — I have the full context from the bot and I'm going to resolve this for you right now." This acknowledgment of continuity dramatically improves customer satisfaction scores.

Implementation

# ===== SOURCE AGENT (Bot) =====

# 1. Start bot session
curl -X POST http://localhost:3300/v1/sessions/start \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "support-bot",
    "metadata": {"channel": "webchat", "user_id": "user-u_9k3m"}
  }'
# Response: {"id": "sess_bot_7f2a"}

# 2. Store context during conversation
curl -X POST http://localhost:3300/v1/memory/store \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "support-bot",
    "content": "User reports charge of $89.99 on Jan 15 that they did not authorize. Transaction ID: TXN-88291.",
    "importance": 0.8,
    "session_id": "sess_bot_7f2a",
    "tags": ["billing", "dispute", "unauth-charge"]
  }'

curl -X POST http://localhost:3300/v1/memory/store \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "support-bot",
    "content": "Attempted: verified account ownership (success), ran standard refund flow (failed — transaction too old), offered account credit (rejected by user).",
    "importance": 0.75,
    "session_id": "sess_bot_7f2a",
    "tags": ["attempted-resolution", "failed-paths"]
  }'

# 3. Store handoff summary at maximum importance
curl -X POST http://localhost:3300/v1/memory/store \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "support-bot",
    "content": "HANDOFF SUMMARY: User Maria Santos (user-u_9k3m) disputes $89.99 charge TXN-88291 from Jan 15. Standard refund flow failed (30-day limit). User rejected credit offer — wants full refund only. Sentiment: frustrated (3rd contact about this). Recommended: supervisor override for refund. Bot promised escalation within 5 minutes.",
    "importance": 1.0,
    "session_id": "sess_bot_7f2a",
    "tags": ["handoff", "session-summary", "priority:high"]
  }'

# 4. End bot session
curl -X POST http://localhost:3300/v1/sessions/sess_bot_7f2a/end \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "metadata": {
      "handoff_to": "human-agent",
      "escalation_reason": "refund-flow-failed",
      "priority": "high"
    }
  }'

# ===== TARGET AGENT (Human) =====

# 5. Start human agent session linked to original
curl -X POST http://localhost:3300/v1/sessions/start \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "support-bot",
    "metadata": {
      "handler": "human-sarah",
      "handoff_from": "sess_bot_7f2a",
      "channel": "webchat"
    }
  }'

# 6. Recall full context before responding — handoff summary surfaces first
curl "http://localhost:3300/v1/memory/recall?agent_id=support-bot&query=billing+dispute+user+Jan+15&top_k=8" \
  -H "Authorization: Bearer dk-..."
from dakera import DakeraClient
from datetime import datetime
import json

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

class HandoffSession:
    """
    Manages a session with proper handoff support.
    Tracks context memories and generates structured handoff summaries.
    """

    def __init__(self, agent_id: str, user_id: str, channel: str = "webchat"):
        self.agent_id = agent_id
        self.user_id = user_id
        self.channel = channel
        self.session_id = None
        self.context_memories = []
        self.attempted_resolutions = []
        self.client = client

    def start(self) -> str:
        session = self.client.start_session(
            agent_id=self.agent_id,
            metadata={"user_id": self.user_id, "channel": self.channel}
        )
        self.session_id = session["id"]
        return self.session_id

    def store_event(self, content: str, event_type: str, importance: float = 0.75):
        """Store a conversation event with session binding."""
        mem = self.client.store_memory(
            agent_id=self.agent_id,
            content=content,
            importance=importance,
            session_id=self.session_id,
            tags=["event", event_type]
        )
        self.context_memories.append({"id": mem["id"], "content": content, "type": event_type})
        return mem

    def record_attempt(self, action: str, outcome: str):
        """Track what was tried and what happened."""
        record = f"Tried: {action}. Outcome: {outcome}."
        self.attempted_resolutions.append(record)
        return self.store_event(record, "attempted-resolution", importance=0.7)

    def handoff(self, target_agent: str, reason: str, sentiment: str,
                user_goal: str, recommended_action: str, urgency: str = "normal") -> dict:
        """
        Execute handoff sequence:
        1. Store structured summary at importance 1.0
        2. End session with handoff metadata
        Returns handoff data for the target agent
        """
        attempts_text = " ".join(self.attempted_resolutions) if self.attempted_resolutions else "No prior attempts."

        summary_content = (
            f"HANDOFF SUMMARY [{urgency.upper()}]: "
            f"User goal: {user_goal}. "
            f"Situation: {attempts_text} "
            f"User sentiment: {sentiment}. "
            f"Recommended next action: {recommended_action}."
        )

        # Store at importance 1.0 — will always surface first on recall
        summary_mem = self.client.store_memory(
            agent_id=self.agent_id,
            content=summary_content,
            importance=1.0,
            session_id=self.session_id,
            tags=["handoff", "session-summary", f"priority:{urgency}", f"target:{target_agent}"]
        )

        # End the session with handoff metadata
        self.client.end_session(
            session_id=self.session_id,
            metadata={
                "handoff_to": target_agent,
                "escalation_reason": reason,
                "handoff_memory_id": summary_mem["id"],
                "urgency": urgency,
                "timestamp": datetime.utcnow().isoformat()
            }
        )

        return {
            "source_session_id": self.session_id,
            "handoff_memory_id": summary_mem["id"],
            "target_agent": target_agent,
            "urgency": urgency
        }


class IncomingSession:
    """Target agent session — loads handoff context immediately."""

    def __init__(self, agent_id: str, handler_id: str, handoff_data: dict):
        self.agent_id = agent_id
        self.handler_id = handler_id
        self.handoff_data = handoff_data
        self.session_id = None
        self.client = client

    def start_and_load_context(self, query: str = "current issue and situation", top_k: int = 8):
        """Start session and immediately recall handoff context."""
        session = self.client.start_session(
            agent_id=self.agent_id,
            metadata={
                "handler": self.handler_id,
                "handoff_from": self.handoff_data["source_session_id"],
                "channel": "webchat"
            }
        )
        self.session_id = session["id"]

        # Recall context — handoff summary (importance 1.0) surfaces first
        context = self.client.recall(
            agent_id=self.agent_id,
            query=query,
            top_k=top_k
        )

        return context

    def get_opening_message(self, context: list) -> str:
        """Generate an opening message that acknowledges context continuity."""
        if not context:
            return "How can I help you today?"

        handoff_summary = context[0]  # Highest importance memory = handoff summary
        content = handoff_summary.get("content", "")

        # Extract key facts for the opening message
        if "HANDOFF SUMMARY" in content:
            return (
                "I've just been briefed on your situation — I have the full context "
                "from your previous conversation. Let me take it from here and get this resolved for you."
            )
        return "I've reviewed your case history. Let me pick up where we left off."


# ===== USAGE: Customer Support Escalation =====

# BOT HANDLES INITIAL CONVERSATION
bot_session = HandoffSession(
    agent_id="support-bot",
    user_id="user-u_9k3m",
    channel="webchat"
)
bot_session.start()

# Store events as conversation unfolds
bot_session.store_event(
    "User: Maria Santos reports unauthorized charge $89.99 on Jan 15. Transaction: TXN-88291.",
    event_type="issue-report",
    importance=0.85
)

bot_session.store_event(
    "User confirmed account ownership. Identity verified.",
    event_type="verification",
    importance=0.6
)

# Record resolution attempts
bot_session.record_attempt(
    action="Standard refund flow for TXN-88291",
    outcome="FAILED — transaction exceeds 30-day refund window"
)
bot_session.record_attempt(
    action="Offered $90 account credit as alternative",
    outcome="REJECTED — user insists on full refund only"
)

bot_session.store_event(
    "User expressed frustration: 'This is the third time I am calling about this.'",
    event_type="sentiment",
    importance=0.9
)

# BOT TRIGGERS ESCALATION
handoff_data = bot_session.handoff(
    target_agent="human-agent",
    reason="refund-window-exceeded-supervisor-required",
    sentiment="High frustration — 3rd contact, previous resolution attempts failed",
    user_goal="Full refund of $89.99 unauthorized charge TXN-88291",
    recommended_action="Supervisor override refund. Mention 5-min ETA already committed.",
    urgency="high"
)

print(f"Handoff initiated. Session: {handoff_data['source_session_id']}")

# HUMAN AGENT RECEIVES HANDOFF
human_session = IncomingSession(
    agent_id="support-bot",
    handler_id="agent-sarah",
    handoff_data=handoff_data
)

context = human_session.start_and_load_context(
    query="billing dispute refund situation and what was tried",
    top_k=8
)

print(f"
Context loaded ({len(context)} memories):")
for mem in context:
    print(f"  [{mem['importance']:.2f}] {mem['content'][:100]}")

opening = human_session.get_opening_message(context)
print(f"
Agent opening: "{opening}"")
import { DakeraClient } from '@dakera-ai/dakera';

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

interface HandoffData {
  sourceSessionId: string;
  handoffMemoryId: string;
  targetAgent: string;
  urgency: string;
}

// ===== SOURCE AGENT (Bot) =====

// 1. Start bot session
const botSession = await client.listSessions(); // or start via startSession API
const sessionId = 'sess_bot_7f2a'; // from start session response

// 2. Store context events during conversation
await client.storeMemory('support-bot', {
  content: 'User Maria Santos disputes $89.99 charge TXN-88291 from Jan 15. Not authorized.',
  importance: 0.85,
  tags: ['billing', 'dispute', 'unauth-charge']
});

await client.storeMemory('support-bot', {
  content: 'Standard refund flow failed: transaction exceeds 30-day window.',
  importance: 0.75,
  tags: ['attempted-resolution', 'failed']
});

await client.storeMemory('support-bot', {
  content: 'Credit offer ($90) rejected. User insists on refund only. Third contact about this issue.',
  importance: 0.85,
  tags: ['attempted-resolution', 'sentiment:frustrated']
});

// 3. Generate and store handoff summary at maximum importance
const summaryMem = await client.storeMemory('support-bot', {
  content: [
    'HANDOFF SUMMARY [HIGH]:',
    'User: Maria Santos (user-u_9k3m).',
    'Goal: Full refund of $89.99 unauthorized charge TXN-88291 (Jan 15).',
    'Tried: standard refund (30-day limit exceeded), credit offer (rejected).',
    'Sentiment: high frustration, 3rd contact.',
    'Committed: escalation within 5 minutes.',
    'Action: supervisor override refund required.'
  ].join(' '),
  importance: 1.0,
  tags: ['handoff', 'session-summary', 'priority:high']
});

// 4. End bot session with handoff metadata
await client.sessionMemories(sessionId); // confirm memories stored
// end_session called via API

const handoffData: HandoffData = {
  sourceSessionId: sessionId,
  handoffMemoryId: summaryMem.id,
  targetAgent: 'human-sarah',
  urgency: 'high'
};

// ===== TARGET AGENT (Human) =====

// 5. Start new session linked to original
const humanSession = await client.listSessions(); // start human session

// 6. Recall context — handoff summary surfaces first due to importance: 1.0
const context = await client.recall(
  'support-bot',
  'billing dispute refund situation attempted resolutions',
  { top_k: 8 }
);

console.log(`Context loaded (${context.length} memories):`);
context.forEach(mem => {
  console.log(`  [${mem.importance.toFixed(2)}] ${mem.content.substring(0, 100)}`);
});

// First memory is always the handoff summary
const handoffSummary = context[0];
console.log(`
Handoff summary: ${handoffSummary.content}`);

// 7. Human agent acknowledges continuity
const opening = handoffSummary.content.includes('HANDOFF SUMMARY')
  ? "I have the full context from your previous conversation. I'm going to resolve this for you now."
  : "Let me pull up your case.";

console.log(`Agent opening: "${opening}"`);
use dakera_rs::{Client, StoreMemoryRequest, RecallRequest};

let client = Client::new("http://localhost:3300", "dk-...");

// ===== SOURCE AGENT =====

// Store conversation context
client.store_memory("support-bot", StoreMemoryRequest {
    content: "User reports $89.99 unauthorized charge TXN-88291 on Jan 15.".into(),
    importance: Some(0.85),
    tags: vec!["billing".into(), "dispute".into()],
    ..Default::default()
}).await?;

client.store_memory("support-bot", StoreMemoryRequest {
    content: "Standard refund failed: 30-day limit exceeded. Credit offer rejected.".into(),
    importance: Some(0.75),
    tags: vec!["attempted-resolution".into(), "failed".into()],
    ..Default::default()
}).await?;

client.store_memory("support-bot", StoreMemoryRequest {
    content: "User: frustrated, 3rd contact about this charge.".into(),
    importance: Some(0.9),
    tags: vec!["sentiment".into(), "high-priority".into()],
    ..Default::default()
}).await?;

// Store handoff summary at importance 1.0
let summary = client.store_memory("support-bot", StoreMemoryRequest {
    content: concat!(
        "HANDOFF SUMMARY [HIGH]: User Maria Santos disputes $89.99 TXN-88291 Jan 15. ",
        "Standard refund failed (30d limit). Credit rejected. Third contact. ",
        "Sentiment: high frustration. Action: supervisor override refund required. ",
        "Bot committed: escalation within 5 minutes."
    ).into(),
    importance: Some(1.0),
    tags: vec!["handoff".into(), "session-summary".into(), "priority:high".into()],
    ..Default::default()
}).await?;

println!("Handoff summary stored: {}", summary.id);

// ===== TARGET AGENT =====

// Recall — handoff summary surfaces first (importance 1.0)
let context = client.recall("support-bot", RecallRequest {
    query: "billing dispute refund situation and resolution attempts".into(),
    top_k: Some(8),
    ..Default::default()
}).await?;

println!("Context memories: {}", context.len());
for mem in &context {
    println!("  [{:.2}] {}", mem.importance, &mem.content[..mem.content.len().min(100)]);
}

if let Some(summary) = context.first() {
    println!("
Handoff summary: {}", summary.content);
}
client := dakera.NewClient("http://localhost:3300", "dk-...")
ctx := context.Background()

// ===== SOURCE AGENT =====

// Store context events during bot conversation
client.StoreMemory(ctx, "support-bot", dakera.StoreMemoryRequest{
    Content:    "User Maria Santos disputes $89.99 charge TXN-88291 from Jan 15. Not authorized.",
    Importance: 0.85,
    Tags:       []string{"billing", "dispute"},
})

client.StoreMemory(ctx, "support-bot", dakera.StoreMemoryRequest{
    Content:    "Standard refund failed: 30-day window exceeded. Credit offer rejected by user.",
    Importance: 0.75,
    Tags:       []string{"attempted-resolution", "failed"},
})

client.StoreMemory(ctx, "support-bot", dakera.StoreMemoryRequest{
    Content:    "User frustrated: third contact about this charge. High priority.",
    Importance: 0.9,
    Tags:       []string{"sentiment", "high-priority"},
})

// Handoff summary at maximum importance
summaryContent := strings.Join([]string{
    "HANDOFF SUMMARY [HIGH]:",
    "User: Maria Santos. Goal: refund $89.99 TXN-88291 Jan 15.",
    "Tried: standard refund (30d limit failed), credit ($90 rejected).",
    "3rd contact. High frustration.",
    "Action: supervisor override required. Bot committed 5-min ETA.",
}, " ")

summary, _ := client.StoreMemory(ctx, "support-bot", dakera.StoreMemoryRequest{
    Content:    summaryContent,
    Importance: 1.0,
    Tags:       []string{"handoff", "session-summary", "priority:high"},
})
fmt.Printf("Handoff summary ID: %s
", summary.ID)

// ===== TARGET AGENT =====

// Recall context — handoff summary (importance 1.0) always first
memories, _ := client.Recall(ctx, "support-bot", dakera.RecallRequest{
    Query: "billing dispute refund situation attempts",
    TopK:  8,
})

fmt.Printf("Context loaded: %d memories
", len(memories))
for _, mem := range memories {
    content := mem.Content
    if len(content) > 100 {
        content = content[:100]
    }
    fmt.Printf("  [%.2f] %s
", mem.Importance, content)
}

if len(memories) > 0 {
    fmt.Printf("
Handoff summary: %s
", memories[0].Content)
}

Stop making users repeat themselves at every escalation.

Dakera's session handoff gives receiving agents full context in under 200ms from the first recall call.

Implement Session Handoff →

Before / After Memory State

Before — no handoff pattern
-- Session 1 ends --
[memories exist but no structured summary]
[no session linkage to Session 2]

-- Human agent starts Session 2 --
context = recall("billing issue")

[0.5] User called about billing
[0.5] Charge on Jan 15
[0.5] Tried to refund
[0.5] User was frustrated

Agent: "Can you describe your issue?"
User: "I ALREADY EXPLAINED THIS THREE TIMES"

Result: CSAT score: 2/5
Resolution time: +18 minutes
After — session handoff pattern
-- Handoff summary stored at importance 1.0 --

-- Human agent recalls, gets summary first --
[1.0][handoff] HANDOFF SUMMARY: Maria Santos,
  $89.99 TXN-88291 Jan 15, standard refund
  failed (30d limit), credit rejected, 3rd
  contact, HIGH frustration. Action: supervisor
  override. Bot promised 5-min ETA.

[0.85] Unauthorized charge confirmed
[0.75] Refund flow: 30-day limit exceeded
[0.90] User frustration escalating

Agent: "I have the full context — I'm applying
  a supervisor override for your refund now."
User: "Finally, thank you."

Result: CSAT score: 5/5. Time saved: 12 min

SDK Reference

OperationPythonTypeScriptPurpose
Start sessionstart_session(agent_id, metadata)startSession(agentId, metadata)Begin a tracked conversation session
Store eventstore_memory(..., session_id=id)storeMemory(..., {sessionId: id})Bind memories to current session
Store handoff summarystore_memory(..., importance=1.0, tags=["handoff"])storeMemory(..., {importance: 1.0, tags: ["handoff"]})Max-importance context for receiver
End sessionend_session(session_id, metadata)endSession(sessionId, metadata)Close session with handoff metadata
List sessionslist_sessions()listSessions()Audit session history
Session memoriessession_memories(session_id)sessionMemories(sessionId)Retrieve all memories from a session
Recall contextrecall(agent_id, query, top_k=8)recall(agentId, query, {top_k: 8})Load handoff context in new session

Real-World Example: 24/7 Support with Shift Handoffs

A SaaS company runs customer support 24/7 with 8-hour agent shifts. At shift change, dozens of open cases need to transfer to incoming agents. Here is how session handoff works at scale:

08:00 — Day shift starts
Night shift agent Sarah runs the handoff sequence for 12 open cases. Each case gets a structured handoff summary stored at importance 1.0 via a batch operation. Session metadata includes handoff_to: "day-shift" and the estimated resolution complexity.
08:01 — Day agent loads queue
Day agent Marcus runs recall() for each open case using the customer's agent_id. For every case, the handoff summary surfaces first in the results. Marcus can triage all 12 cases in under 3 minutes by reading only the summaries — no need to re-read conversation history.
Throughout the day — context accumulates
As Marcus resolves each case, he stores resolution notes with importance 0.7. For complex cases requiring further escalation, he runs the handoff pattern again — creating a chain of handoff summaries that form a complete audit trail of every step taken.
Analytics — handoff quality metrics
The ops team queries session_memories() weekly to analyze handoff efficiency: average context recall latency, resolution time before vs. after handoffs, cases that required re-explanation (indicating poor handoff quality). These metrics drive continuous improvement of the summary generation process.

Handoff Package: Anatomy of a Context Transfer

This diagram breaks down exactly what gets captured in a handoff package and how the receiving agent reconstructs the full context from stored memories alone.

Source Agent Session ending... intent: account_lock status: 2FA reset done context: VIP enterprise next: verify email sentiment: frustrated Dakera Memory Store 5 memories tagged: handoff imp: 0.7-0.9 persists until explicit delete recall(tags=handoff) Target Agent New session, full context ✓ knows: intent ✓ knows: status ✓ knows: tier ✓ knows: next step ✓ knows: mood Zero re-explanation needed Complete context transfer with no data loss — 5 memories, <50ms recall

Performance Considerations

<50ms
Handoff summary recall latency
-40%
Average handle time with context handoff
+1.8pt
CSAT improvement vs. cold transfer

Handoff context recall takes under 50ms because the summary is stored at importance 1.0 — it's always the first result with no scoring competition from lower-importance memories. The operation is a single recall() call, not a session replay or history re-fetch.

Operational improvements are significant in production deployments: teams using structured session handoffs report 35–45% reduction in average handle time after escalation (receiving agents don't need to gather context) and CSAT improvements of 1.5–2 points from users not having to repeat themselves.

Edge Cases

1. Handoff summary too long for effective recall

A bot stuffs 2,000 words into the handoff summary, filling the receiving agent's context window with handoff boilerplate and leaving no room for the agent's response. Fix: enforce a strict length limit on handoff summaries — 200–300 words maximum. Use a bulleted structure: Goal, Situation, Tried, Sentiment, Action. The full conversation history is available via additional recall queries if needed.

2. Chain handoffs losing earlier context

Case escalates: Bot → Tier 1 Agent → Tier 2 Specialist → Manager. By the fourth handoff, only the Tier 2 → Manager handoff summary is at importance 1.0. Earlier context gets buried. Fix: each handoff summary should include a handoff_chain field summarizing all prior handoffs. The current summary absorbs the previous one: "This is the 3rd escalation. Previous agents confirmed X, tried Y, Z."

3. Target agent starts responding before recall completes

In async/streaming architectures, the human agent's UI loads before the recall API call returns. The agent responds without context. Fix: block the agent UI from showing the user as "connected" until the context recall completes. Show a "Loading context..." indicator. The 50ms latency is imperceptible to users and eliminates cold-start responses entirely.

4. Source session ends before handoff summary is stored

The bot crashes or times out before completing the handoff sequence. No handoff summary exists. Fix: implement a two-phase handoff: (1) store the handoff summary memory first, (2) then end the session. If the session ends without a handoff memory, the target agent falls back to querying session_memories() directly for the source session ID.

5. Multiple simultaneous escalations of the same user

A user contacts support on two channels simultaneously (chat and phone). Two bot sessions both escalate to human agents. Both human agents pull the same user's memories and see each other's in-progress context. Fix: include a channel tag on all memories and the handoff summary. Agents filter recall by channel to avoid seeing the other channel's state. Merge conflicts are resolved by the supervisor if both cases need to be collapsed.

Include Emotional State in Every Handoff

Factual context alone is not enough. "User disputes $89.99 charge" tells the human agent what to do but not how to approach the user. "User is highly frustrated after 3 contacts — open with an apology and direct commitment to resolve before asking any verification questions" changes the entire interaction quality. Include explicit sentiment guidance and tone recommendations in every handoff summary.

Advanced Configuration — Batch Handoffs & Session Analytics

Batch Handoff at Shift Change

from dakera import DakeraClient, BatchRecallRequest

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

def batch_handoff_shift_change(open_cases: list[dict]) -> list[dict]:
    """
    Generate handoff summaries for all open cases at shift change.
    Returns list of handoff packages for incoming shift.
    """
    handoff_packages = []
    for case in open_cases:
        # Recall current state for each case
        context = client.recall(
            agent_id=case["agent_id"],
            query=f"{case['issue_type']} situation and resolution status",
            top_k=5
        )

        # Store handoff summary
        summary_text = generate_handoff_summary(case, context)  # your LLM call
        mem = client.store_memory(
            agent_id=case["agent_id"],
            content=summary_text,
            importance=1.0,
            tags=["handoff", "shift-change", f"case:{case['id']}"]
        )

        handoff_packages.append({
            "case_id": case["id"],
            "agent_id": case["agent_id"],
            "handoff_memory_id": mem["id"],
            "priority": case.get("priority", "normal")
        })

    return sorted(handoff_packages, key=lambda x: x["priority"] == "high", reverse=True)

Session Analytics — Measuring Handoff Quality

def analyze_handoff_quality(agent_id: str, days: int = 7) -> dict:
    """Measure handoff effectiveness via session chain analysis."""
    sessions = client.list_sessions()

    # Find sessions that were handoffs
    handoff_sessions = [
        s for s in sessions
        if s.get("metadata", {}).get("handoff_from")
    ]

    metrics = {
        "total_handoffs": len(handoff_sessions),
        "avg_resolution_after_handoff": 0,
        "context_recall_rate": 0  # sessions where handoff summary recalled
    }

    for session in handoff_sessions:
        memories = client.session_memories(session["id"])
        has_handoff_summary = any(
            "HANDOFF SUMMARY" in m.get("content", "")
            for m in memories
        )
        if has_handoff_summary:
            metrics["context_recall_rate"] += 1

    metrics["context_recall_rate"] /= max(len(handoff_sessions), 1)
    return metrics

Cross-Channel Handoff (Mobile to Desktop)

# Mobile session context stored normally
client.store_memory(
    agent_id="assistant",
    content="User was reviewing Q3 financial report on mobile — viewing page 12 (EBITDA breakdown).",
    importance=0.8,
    tags=["cross-channel", "session-state", "mobile"]
)

# Mobile handoff summary
client.store_memory(
    agent_id="assistant",
    content="CHANNEL HANDOFF: User switching mobile->desktop. Was on Q3 report page 12 (EBITDA). Had questions about depreciation line items. Resume from that context.",
    importance=1.0,
    tags=["handoff", "cross-channel", "mobile-to-desktop"]
)

# Desktop session immediately recalls channel state
context = client.recall(
    agent_id="assistant",
    query="current session state and what user was working on",
    top_k=5
)
# First result: the channel handoff summary — desktop resumes from exact context

Zero-Loss Session Handoffs in Your Product

Implement structured session handoffs in under 20 minutes. Users stop repeating themselves. Agents start every escalation with full context.

Implement Session Handoff →