# How to Troubleshoot Handoffs Not Appearing in the Next Agent Session with ai-memory

> Troubleshoot why handoffs aren't appearing in the next agent session with akitaonrails/ai-memory. Learn to fix common workspace, project, ownership, and state issues.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: how-to-guide
- Published: 2026-08-20

---

**In ai-memory, handoffs fail to appear in subsequent agent sessions when workspace and project resolution, user ownership filters, handoff state, or current working directory constraints are not satisfied.**

The ai-memory framework persists agent handoffs as first-class SQLite records, but visibility in the next session depends on strict validation rules enforced by the admission layer. If you are troubleshooting why handoffs aren't appearing in the next agent session, you must verify that the receiving context matches the exact workspace, project, ownership, and directory constraints recorded when the handoff was created. This guide examines the source code in `akitaonrails/ai-memory` to identify the specific conditions that filter handoffs from view.

## Understanding Handoff Visibility Constraints

According to [`crates/ai-memory-core/src/handoff.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/handoff.rs), a `NewHandoff` persists as a row in the SQLite store with metadata including workspace, project, owner, state, and current working directory. The next agent session queries these records through the API routes defined 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), but the handoff only surfaces when **all** of the following conditions align:

### Workspace and Project Resolution

The receiving session must resolve the identical `{workspace}` and `{project}` identifiers stored in the handoff row. The overview endpoint at `/workspaces/{ws}/projects/{proj}/overview` filters strictly by these path parameters.

### Ownership and Authentication Filters

The admission logic in [`crates/ai-memory-wiki/src/admission.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/admission.rs) applies ownership filters based on the caller's identity. An unnamed (anonymous) caller sees only handoffs where `owner_user` is `None`. A named caller (e.g., `--user alice`) sees handoffs owned by that user plus any unowned handoffs. Handoffs owned by other users remain invisible.

### Handoff State Validation

Only rows with `state = Open` are advertised as pending handoffs. Once accepted (`state = accepted`), cancelled, or expired, the record disappears from active listings. The state machine is defined in [`crates/ai-memory-core/src/handoff.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/handoff.rs).

### Current Working Directory Matching

When created via `memory_handoff_begin`, a handoff records the `cwd` (current working directory). For automatic handoff discovery, the next session must start in the exact same directory path; otherwise, the filter excludes the record.

### Expiration and Decay

The background decay sweep in [`crates/ai-memory-consolidate/src/auto_improve.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-consolidate/src/auto_improve.rs) can mark handoffs as `expired`. Expired rows are omitted from all API responses and CLI listings.

## Step-by-Step Troubleshooting Workflow

Follow this diagnostic sequence to identify why a specific handoff is not surfacing:

1. **Verify the handoff was persisted**

   Execute the CLI list command to query the store directly:

   ```bash
   ai-memory handoff list --workspace default --project my_proj
   ```

   If the output is empty, the handoff never reached the database.

2. **Inspect the stored row directly**

   Use debug builds to examine the raw record:

   ```rust
   let handoff = store.reader.get_handoff_by_id(handoff_id).await?;
   println!("{:#?}", handoff);
   ```

   Check fields including `owner_user`, `state`, and `cwd`.

3. **Validate user identity alignment**

   Confirm that your current user flag matches the handoff's `owner_user` field. Run `ai-memory` with `--user <name>` to assume a specific identity, or omit it to view only unowned handoffs.

4. **Check workspace and project scope**

   Ensure the `--workspace` and `--project` flags (or automatic resolution from the current directory) match the values stored in the handoff exactly.

5. **Confirm directory compatibility**

   Verify that your shell's current working directory matches the `cwd` value recorded in the handoff. Automatic handoff discovery requires an exact path match.

6. **Check for expiration**

   Review the decay sweep logs or run a manual sweep in a dev build to see if the handoff was marked `expired` due to age or inactivity.

7. **Query the API directly**

   Test the REST endpoint to bypass CLI caching:

   ```bash
   curl http://127.0.0.1:49374/api/v1/workspaces/default/projects/my_proj/overview
   ```

   If the JSON response contains `"handoff": null`, one of the above filters is blocking visibility.

## Creating and Accepting Handoffs in Code

When debugging programmatically, you can manually create a handoff using the core library:

```rust
use ai_memory_core::handoff::{NewHandoff, HandoffState};
use ai_memory_store::WriterHandle;

let new = NewHandoff {
    workspace_id: ws_id,
    project_id: proj_id,
    from_session_id: Some(session_id),
    from_agent: AgentKind::ClaudeCode,
    to_agent: None,
    cwd: Some(std::env::current_dir()?.into()),
    summary: "Reached the end of the analysis".into(),
    open_questions: vec!["What is the next step?".into()],
    next_steps: vec!["Run tests".into()],
    files_touched: vec!["src/main.rs".into()],
    owner_user: Some("alice".into()),
};
writer.insert_handoff(new).await?;

```

To accept a handoff in the receiving session:

```rust
use ai_memory_core::handoff::HandoffAcceptance;

let accept = HandoffAcceptance {
    handoff_id,
    workspace_id: ws_id,
    project_id: proj_id,
    accepting_agent: AgentKind::Codex,
    accepting_session: Some(new_session_id),
    accepting_user: Some("alice".into()),
    owner_filter: OwnerFilter::UserOwned,
    receiving_cwd: Some(std::env::current_dir()?.to_string_lossy().into()),
};
writer.accept_handoff(accept).await?;

```

## Key Source Files Reference

Understanding these files helps trace visibility logic:

- **[`crates/ai-memory-core/src/handoff.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/handoff.rs)** — Defines the `NewHandoff` schema, `HandoffState` enum, and acceptance metadata.
- **[`crates/ai-memory-wiki/src/admission.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/admission.rs)** — Implements the handoff lifecycle and ownership filtering.
- **[`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 the overview and handoff list endpoints consumed by agents.
- **[`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs)** — Provides read-only queries used by both CLI and API.
- **[`crates/ai-memory-web/tests/routes.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-web/tests/routes.rs)** — Contains integration tests demonstrating expected visibility rules.

## Summary

Troubleshooting missing handoffs in ai-memory requires systematic verification of persistence and filtering constraints:

- Handoffs must persist with matching `workspace_id` and `project_id` to the next session's context.
- User identity determines visibility through `owner_user` filtering in the admission layer.
- Only handoffs with `state = Open` appear in listings; accepted or expired records are hidden.
- The current working directory must match the recorded `cwd` for automatic handoff discovery.
- Direct API inspection and CLI list commands reveal whether records exist but are filtered.

## Frequently Asked Questions

### Why does my handoff disappear after I accept it?

Once you call `accept_handoff()` or the receiving agent claims the handoff, the system updates the `state` column from `Open` to `Accepted` in [`crates/ai-memory-core/src/handoff.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/handoff.rs). The listing queries intentionally filter out non-open states to prevent duplicate processing of completed work transfers.

### Can multiple users see the same handoff?

Yes, but with restrictions. Unowned handoffs (`owner_user = None`) are visible to all authenticated and anonymous users. Owned handoffs are visible only to the specific user and to anonymous callers, but not to other named users. This isolation is enforced in [`crates/ai-memory-wiki/src/admission.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/admission.rs).

### How does the current working directory affect handoff visibility?

When `memory_handoff_begin` creates a handoff, it captures `std::env::current_dir()` into the `cwd` field. The next session must execute from the identical absolute path for automatic discovery. If you start the agent from a different directory, pass the specific handoff ID manually or change to the correct directory before launching.

### What causes handoffs to expire automatically?

The decay sweep process in [`crates/ai-memory-consolidate/src/auto_improve.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-consolidate/src/auto_improve.rs) periodically evaluates handoff age and relevance. If a handoff remains unaccepted for a configurable duration or the system determines the context is stale, it updates the `state` to `Expired`. Expired handoffs are permanently excluded from the `handoff list` command and overview API responses.