# Understanding the Handoff Row in ai-memory Session Synthesis

> Discover the Handoff row in ai-memory session synthesis. Learn how this SQLite snapshot resumes agent work, saving time by avoiding log re-parsing and capturing key session data.

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

---

**A Handoff row is a first-class snapshot of a finished agent session stored in SQLite that captures the working directory, summary, open questions, and touched files, enabling the next agent to resume work without re-parsing the full observation log.**

The ai-memory project implements a sophisticated session synthesis layer that allows AI agents to maintain continuity across separate CLI invocations. When an agent terminates a session using `memory_handoff_end`, the system creates a **Handoff** row in the SQLite store that the next agent can claim via `memory_handoff_accept`. This mechanism eliminates the need to rebuild context from scratch or re-parse entire observation logs.

## What Constitutes a Handoff Row

According to the source code in [`crates/ai-memory-core/src/handoff.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/handoff.rs), a Handoff row encapsulates all ephemeral state required to resume work across agents. Unlike wiki entries that survive re-indexing, handoffs represent "DB-only episodic state" as noted in [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs) (lines 716-720).

### Core Schema Fields

The Handoff structure stores the following critical fields:

- **`workspace_id`** / **`project_id`** – Scoping identifiers ensuring the handoff belongs to a specific workspace and project context
- **`from_session_id`** – Tracks the session that produced the handoff (optional for manual handoffs)
- **`from_agent`** / **`to_agent`** – The producing agent and an optional target-agent hint (e.g., **claude-code**, **codex**)
- **`cwd`** – Working directory at handoff time, used to match the next session's current working directory
- **`summary`**, **`open_questions`**, **`next_steps`**, **`files_touched`** – Human-readable context visible to the next agent
- **`owner_user`** – Optional owner assignment; `None` makes the handoff public to the entire project
- **`created_at`** / **`accepted_at`** – Timestamps for lifecycle tracking

### The HandoffState Machine

The `HandoffState` enum defines three distinct lifecycle states implemented in the core library:

- **Open** – Freshly created and awaiting acceptance
- **Accepted** – Claimed by another agent via `memory_handoff_accept`
- **Expired** – Removed by the decay sweep process

## Creating and Claiming Handoffs

The ai-memory store provides explicit methods for handoff management through the writer interface in [`crates/ai-memory-store/src/writer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/writer.rs).

### Inserting a New Handoff

When ending a session, agents construct a `NewHandoff` struct and persist it using `insert_handoff`:

```rust
let new_handoff = ai_memory_core::NewHandoff {
    workspace_id,
    project_id,
    from_session_id: Some(session_id),
    from_agent: ai_memory_core::AgentKind::Codex,
    to_agent: None,
    cwd: Some(std::env::current_dir()?.into()),
    summary: "Finished data-cleaning step".into(),
    open_questions: vec!["How to handle missing values?".into()],
    next_steps: vec!["Run `memory_handoff_accept`".into()],
    files_touched: vec!["src/cleaner.rs".into()],
    owner_user: Some("alice".into()),
};
let handoff_id = store.writer.insert_handoff(new_handoff).await?;

```

The low-level SQL implementation resides in [`crates/ai-memory-store/src/ops.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/ops.rs) within the `insert_handoff_row` function.

### Accepting a Handoff

The next agent claims ownership using the `accept_handoff` method with a `HandoffAcceptance` structure:

```rust
let acceptance = ai_memory_core::HandoffAcceptance {
    handoff_id,
    workspace_id,
    project_id,
    accepting_agent: ai_memory_core::AgentKind::Codex,
    accepting_session: Some(new_session_id),
    accepting_user: Some("bob".into()),
    owner_filter: ai_memory_core::OwnerFilter::Any,
    receiving_cwd: Some(std::env::current_dir()?.to_string_lossy().into()),
};
let accepted = store.writer.accept_handoff(acceptance).await?;
assert!(accepted);

```

### Querying Pending Handoffs

Agents can list available handoffs using the reader interface, typically invoked by `memory_briefing`:

```rust
let pending = store.reader.list_handoffs(
    workspace_id,
    project_id,
    Some(ai_memory_core::HandoffState::Open),
    ai_memory_core::OwnerFilter::Any,
    50,
).await?;
println!("Open handoffs: {}", pending.len());

```

## Database Architecture and Persistence

Handoffs occupy a unique position in the ai-memory storage hierarchy. While the system rebuilds wiki content from observations during re-indexing, handoff rows remain untouched as pure database entities.

### Storage Implementation

The [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs) implementation explicitly distinguishes handoffs as ephemeral state that persists solely in SQLite. This design choice ensures that session synthesis data survives wiki rebuilds without requiring re-derivation from observation logs.

### Ownership and Access Control

The `owner_user` field implements a flexible permission model. When set to `None`, any project member can claim the handoff; when populated with a specific username, only that teammate can accept it. The test suite in [`crates/ai-memory-store/tests/handoff_ownership.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/tests/handoff_ownership.rs) validates these ownership constraints through comprehensive integration tests.

## Summary

- A **Handoff row** captures a complete snapshot of session state including working directory, touched files, and unresolved questions
- The implementation spans [`crates/ai-memory-core/src/handoff.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/handoff.rs) for definitions and [`crates/ai-memory-store/src/writer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/writer.rs) for persistence logic
- Handoffs transition through **Open**, **Accepted**, and **Expired** states managed by the `HandoffState` enum
- The `owner_user` field enables both public handoffs and private assignments to specific teammates
- Unlike wiki entries, handoffs exist as "DB-only episodic state" that survives re-indexing operations

## Frequently Asked Questions

### How does a Handoff row differ from regular session memory in ai-memory?

Regular session memory typically consists of observation logs and wiki entries that the system can rebuild from source data. A Handoff row constitutes a first-class, immutable snapshot stored directly in SQLite that preserves the exact state of a finished session without requiring re-parsing or re-indexing. This distinction allows agents to resume work immediately by reading a single row rather than reconstructing context from scattered observations.

### What triggers the Expired state in the Handoff state machine?

The **Expired** state results from a decay sweep process that removes stale handoffs from the database. Unlike the **Open** and **Accepted** states which transition based on explicit agent actions (creation and acceptance respectively), expiration occurs automatically when the system identifies handoffs that have remained unclaimed beyond their configured retention period.

### Can multiple agents claim the same Handoff simultaneously?

No. The acceptance mechanism in [`crates/ai-memory-store/src/writer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/writer.rs) implements atomic claim operations. Once an agent successfully invokes `accept_handoff` and transitions the row from **Open** to **Accepted**, subsequent attempts to claim that specific `handoff_id` will fail. The `owner_filter` parameter in `HandoffAcceptance` allows agents to specify ownership constraints, but the underlying implementation ensures single-agent ownership per handoff.

### Where is the SQL implementation for inserting Handoff rows located?

The low-level SQL operations reside in [`crates/ai-memory-store/src/ops.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/ops.rs), specifically within the `insert_handoff_row` function. This module provides the raw database interface that [`writer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/writer.rs) utilizes, while the high-level API for creating handoffs is exposed through [`crates/ai-memory-store/src/writer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/writer.rs) with the `insert_handoff` method.