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

> Discover the ai-memory handoffs table, the core SQLite structure for cross-agent continuity. Learn how it stores session snapshots for seamless collaboration.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: internals
- Published: 2026-08-26

---

**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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/V48__handoff_receiver_index.sql) for receiver tracking)

Admission hooks in [`crates/ai-memory-hooks/src/router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/handoff.rs). The following example demonstrates constructing a handoff for transfer from an OpenCode agent to Claude Code:

```rust
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`](https://github.com/akitaonrails/ai-memory/blob/main/hooks/_lib.sh) automate handoff discovery. The `ai_memory_get_handoff` function queries the MCP endpoint to fetch pending work:

```bash
#!/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`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/server.rs) updates the table:

```bash
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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-web/tests/routes.rs) verify that the handoffs table correctly surfaces open items in workspace overviews:

```rust
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** (`open` → `accepted` → `expired`) 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`](https://github.com/akitaonrails/ai-memory/blob/main/V02__handoffs.sql) and [`V48__handoff_receiver_index.sql`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/V48__handoff_receiver_index.sql) add indexes and receiver tracking columns to support the acceptance lifecycle and performance optimization for large-scale deployments.