# How SessionEnd Events Are Synthesized and Drive Agent Handoffs in ai-memory

> Learn how SessionEnd events synthesize summaries and drive agent handoffs in ai-memory with atomic SQLite transactions for zero-duplication context transfer. Read now!

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

---

**When an agent session terminates, the ai-memory server synthesizes a markdown summary page and issues an automatic handoff in a single atomic SQLite transaction, ensuring zero-duplication context transfer to subsequent agents.**

The **ai-memory** project (akitaonrails/ai-memory) provides durable memory and context management for AI agents through a git-backed wiki and SQLite store. When a client sends a `session-end` hook event, the system transitions from active conversation to archived context by generating documentation and routing control to the next agent via an automatic handoff mechanism.

## Detecting Substantive vs. Ephemeral Sessions

The hook handler in [`crates/ai-memory-hooks/src/router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/router.rs) first classifies the session to avoid polluting the wiki with empty interactions.

- **Ephemeral sessions**—those containing only “turn-boundary” events such as `stop` with no substantive work—are handled by calling `end_admitted_lifecycle_only_session`. This path commits any pending logs but **skips summary generation and handoff creation** ([`router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/router.rs#L2802-L2832).
- **Substantive sessions** proceed to synthesis. The router checks for real observations or user prompts that warrant preservation before invoking the synthesis pipeline.

## Synthesizing the Session Summary Page

For substantive sessions, the router delegates narrative construction to `synthesize_session_page` defined in [`crates/ai-memory-hooks/src/synth.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/synth.rs). This function assembles the final user prompt, collected observations, and a “what-was-done” narrative into a markdown page stored at `sessions/<id>.md`.

The page is persisted via `wiki.write_page` ([`router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/router.rs#L2847-L2868). According to the source code, this write occurs atomically with the session-end stamp, guaranteeing that a server crash cannot leave a half-finished session record without its corresponding wiki page.

## Creating the Automatic Handoff

Once the summary page is persisted, the router constructs a `NewHandoff` object through `build_auto_handoff` ([`router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/router.rs#L2776-L2786). This structure:

- Points to the newly created summary page ID
- Carries the session’s owner as the baton’s recipient
- Tracks the current working directory and agent context

The handoff enters an admission chain where registered webhooks can approve or refuse the transfer. If admission is refused, the session ends cleanly without a handoff, and the refusal is logged ([`router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/router.rs#L2889-L2896). Upon successful admission, the router commits the handoff durably.

## Atomic Persistence and Durability

The critical durability logic resides in [`crates/ai-memory-store/src/writer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/writer.rs) within the `end_admitted_session_with_handoff` method (L1018-L1038). This function executes a single SQLite transaction that:

1. Stamps the session as officially ended
2. Inserts the handoff row into the database
3. Links the handoff record to the summary page ID

This atomicity ensures that replaying events or recovering from crashes will not duplicate handoffs or orphan summary pages.

## Post-Processing and LLM Consolidation

After the database transaction commits, the wiki auto-commits to its git backend, making the summary page and handoff immediately visible to downstream agents ([`router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/router.rs#L3100-L3110).

If an LLM provider is configured, the server enqueues a **SessionEnd consolidation job** via `enqueue_session_end_consolidation` ([`router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/router.rs#L3269-L3271). This background process allows rule-based summaries to be later enriched with LLM-generated insights without blocking the handoff flow.

## Code Examples

### Manually Triggering SessionEnd from CLI

```rust
// Trigger the same internal path as the hook for testing or manual finalization
ai_memory_cli::commands::finalize_session::run(
    "--agent", "codex",
).await?;

```

### Internal Server Flow

```rust
async fn handle_session_end(state: &State, session_id: SessionId) -> Result<()> {
    // Load raw observations
    let mut obs = state.reader.observations_for_session(session_id).await?;

    // Synthesize markdown summary
    let page = synthesize_session_page(ws, proj, session_id, agent_kind, &obs);
    let page_id = state.wiki.write_page(page.into_request()).await?;

    // Construct automatic handoff
    let handoff = build_auto_handoff(ws, proj, agent, session_id, cwd, &obs, owner);

    // Atomically end session and persist handoff
    let handoff_id = state.writer
        .end_admitted_session_with_handoff(admitted, Some(page_id), handoff)
        .await?;
    Ok(())
}

```

### Querying Handoffs from Another Agent

```rust
let handoffs = ai_memory_client::list_handoffs(project_id, /* only open */ true).await?;
for h in handoffs {
    println!("Handoff {} → {}", h.id, h.target);
}

```

## Summary

- **Ephemeral detection** prevents empty sessions from generating noise in the wiki by checking for turn-boundary-only content in [`router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/router.rs).
- **Page synthesis** converts observations into durable markdown via `synthesize_session_page` in [`synth.rs`](https://github.com/akitaonrails/ai-memory/blob/main/synth.rs).
- **Automatic handoffs** are built with `build_auto_handoff` and validated through an admission chain before persistence.
- **Atomic transactions** in `end_admitted_session_with_handoff` guarantee that sessions, handoffs, and wiki pages remain consistent even during crashes.
- **Git-backed durability** and optional LLM consolidation ensure downstream agents receive complete, enriched context.

## Frequently Asked Questions

### What distinguishes a substantive session from an ephemeral one in ai-memory?

A substantive session contains actual work—user prompts, tool calls, or observations—while an ephemeral session consists solely of turn-boundary markers like `stop` events. The router in [`crates/ai-memory-hooks/src/router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/router.rs) filters ephemeral sessions to avoid creating unnecessary summary pages or handoffs, instead calling `end_admitted_lifecycle_only_session` to simply flush pending logs.

### How does ai-memory prevent duplicate handoffs during server crashes?

The system relies on the atomic SQLite transaction within `end_admitted_session_with_handoff` (defined in [`crates/ai-memory-store/src/writer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/writer.rs)). This function stamps the session as ended, inserts the handoff row, and links the summary page ID in a single commit. If the server crashes mid-operation, replaying the event will encounter the already-completed session record and skip reprocessing.

### Can developers trigger SessionEnd synthesis manually outside of automated hooks?

Yes. The codebase exposes the same internal pathways through CLI commands such as `ai_memory_cli::commands::finalize_session::run`, allowing developers to manually finalize sessions for testing or administrative purposes. This invokes the identical logic used by the automated `session-end` hook handler.

### What happens during the LLM consolidation phase after a session ends?

When an LLM provider is configured, the server enqueues a background job via `enqueue_session_end_consolidation` after the atomic commit succeeds. This job retrieves the rule-based summary generated during synthesis and enhances it with LLM-generated insights, updating the wiki page asynchronously without blocking the handoff to the next agent.