What Is the Handoffs Table in ai-memory? Purpose and Implementation

The handoffs table is the core SQLite data structure that enables cross-agent continuity by storing session snapshots containing work summaries, open questions, and file states for seamless agent-to-agent collaboration.

The ai-memory repository implements a persistent memory system for LLM agents, and the handoffs table serves as its backbone for maintaining context across disparate agent sessions. Unlike inferred states from observation logs, this first-class schema in crates/ai-memory-store/migrations/V02__handoffs.sql provides deterministic lifecycle management for transferring work between agents.

Core Purpose and Architecture

The handoffs table solves the cold-start problem in multi-agent workflows. When an agent CLI finishes a session, it captures a structured snapshot of the current work-state—including summaries, open questions, next steps, and touched files—and persists it as a row in this dedicated table.

According to the module documentation in crates/ai-memory-core/src/handoff.rs, cross-agent handoffs are a headline feature that deserves explicit schema support rather than being derived from log analysis. This design ensures that agents can retrieve pending work instantly without replaying entire conversation histories.

The table enforces deterministic state transitions (Open → Accepted → Expired) ensuring exactly-once semantics: once an agent accepts a handoff, no other agent can claim it. It also supports ownership controls through the owner_user column and project-wide sharing mechanisms.

Database Schema and Rust Types

The physical schema resides in crates/ai-memory-store/migrations/V02__handoffs.sql, which creates a SQLite table tracking identity, origin metadata, serialized content, and lifecycle state.

The corresponding Rust types in crates/ai-memory-core/src/handoff.rs model this data:

  • NewHandoff – Input structure for creating handoffs containing workspace ID, project ID, agent kinds, working directory, and content
  • Handoff – The retrieved row with database-assigned ID and timestamps
  • HandoffScope – Defines visibility (user-private vs. project-wide)
  • HandoffContent – Structured body containing summary, open questions, next steps, and files touched
  • HandoffLifecycle – Enum representing open, accepted, or expired states

Lifecycle and State Management

The handoffs table implements a strict state machine to prevent race conditions in distributed agent environments.

State transitions follow this flow:

  1. open – Created when an agent calls memory_handoff_begin via the MCP tool interface
  2. accepted – Transitioned when the receiving agent invokes memory_handoff_accept, recording the receiver's identity
  3. expired – Applied by background decay sweeps for stale handoffs (see migration V48__handoff_receiver_index.sql for receiver tracking)

Admission hooks in crates/ai-memory-hooks/src/router.rs enforce permission checks before state transitions, ensuring that only authorized agents can accept specific handoffs.

Creating a New Handoff

Agents insert rows into the handoffs table using the Rust API defined in crates/ai-memory-core/src/handoff.rs. The following example demonstrates constructing a handoff for transfer from an OpenCode agent to Claude Code:

use ai_memory_core::{NewHandoff, AgentKind, WorkspaceId, ProjectId, SessionId};

let new_handoff = NewHandoff {
    workspace_id: WorkspaceId::new(),
    project_id: ProjectId::new(),
    from_session_id: Some(SessionId::new()),
    from_agent: AgentKind::OpenCode,
    to_agent: Some(AgentKind::ClaudeCode),
    cwd: Some(std::env::current_dir().unwrap()),
    summary: "Finished drafting the blog intro".into(),
    open_questions: vec!["Should we add a code example?".into()],
    next_steps: vec!["Write section on performance".into()],
    files_touched: vec!["src/main.rs".into()],
    owner_user: None,
};

store.writer.insert_handoff(new_handoff).await?;

This insertion creates a row with lifecycle state open, making it available for retrieval by the target agent.

Retrieving Pending Handoffs

Session-start hooks in hooks/_lib.sh automate handoff discovery. The ai_memory_get_handoff function queries the MCP endpoint to fetch pending work:

#!/usr/bin/env bash

# hooks/_lib.sh – ai_memory_get_handoff()

HANDOFF=$(ai_memory_get_handoff "$SERVER/handoff?agent=open-code${QS}${SESSION_QS}" 2>/dev/null || true)
if [[ -n "$HANDOFF" ]]; then
  echo "$HANDOFF"   # printed to stdout, injected into the next prompt

fi

This shell integration ensures that when a new agent session begins, any pending handoffs from previous sessions are automatically injected into the context window.

Accepting Handoffs via MCP

Once retrieved, the receiving agent must accept the handoff to prevent duplicate processing. The MCP tool memory_handoff_accept defined in crates/ai-memory-mcp/src/server.rs updates the table:

ai-memory memory_handoff_accept \
  --workspace default \
  --project my_project \
  --handoff-id 42 \
  --agent open-code \
  --session $(uuidgen) \
  --user $(whoami)

This command transitions the row from open to accepted and records the receiver's metadata in the handoffs table.

API Endpoints and Integration

The HTTP interface in crates/ai-memory-web/src/routes/api.rs exposes RESTful routes for handoff management:

  • POST /handoffs – Creates new handoffs (used by memory_handoff_begin)
  • GET /handoff – Retrieves the next pending handoff for a specific agent context
  • POST /handoff/accept – Accepts a handoff, triggering the lifecycle state change

End-to-end tests in crates/ai-memory-web/tests/routes.rs verify that the handoffs table correctly surfaces open items in workspace overviews:

let resp = client
    .get("/workspaces/default/projects/my_project/handoffs")
    .send()
    .await?;
let json = resp.json::<serde_json::Value>().await?;
assert!(json["handoffs"].as_array().unwrap().len() > 0);

Summary

  • The handoffs table in ai-memory provides persistent storage for cross-agent session snapshots, eliminating context loss between LLM interactions
  • It enforces a strict lifecycle state machine (openacceptedexpired) to ensure exactly-once handoff semantics
  • The schema supports ownership and access control through user-specific and project-wide visibility scopes
  • Session-start hooks automatically query this table to inject pending work into new agent contexts
  • All mutations occur through the MCP protocol, with SQLite migrations in V02__handoffs.sql and V48__handoff_receiver_index.sql maintaining schema evolution

Frequently Asked Questions

What columns does the handoffs table contain?

The handoffs table schema defined in crates/ai-memory-store/migrations/V02__handoffs.sql includes columns for identity (primary key), workspace and project foreign keys, originating session and agent, target agent, current working directory, serialized content (summary, questions, files), lifecycle state, owner user, and timestamps for creation and acceptance.

How does the handoffs table prevent duplicate agent assignments?

The table implements a deterministic lifecycle where handoffs transition from open to accepted upon retrieval. The memory_handoff_accept MCP tool performs an atomic update that fails if the handoff is already accepted, ensuring that only one agent can claim a pending handoff. Additional admission hooks in crates/ai-memory-hooks/src/router.rs enforce permission validation before acceptance.

Can handoffs be shared between different LLM agents?

Yes. The to_agent field optionally specifies a target agent kind (e.g., AgentKind::ClaudeCode), but the schema supports wildcard retrieval where any compatible agent can pick up open handoffs. The HandoffScope type controls whether a handoff is private to a specific user or visible project-wide, enabling flexible collaboration patterns across diverse LLM backends.

Where is the handoffs table migration defined?

The initial schema resides in crates/ai-memory-store/migrations/V02__handoffs.sql, which creates the SQLite table with all core columns. Subsequent migrations like V48__handoff_receiver_index.sql add indexes and receiver tracking columns to support the acceptance lifecycle and performance optimization for large-scale deployments.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →