# How ai-memory Provides Persistent Cross-Session Memory for AI Coding Agents

> Discover how ai-memory creates persistent cross-session memory for AI coding agents using a local markdown wiki and SQLite index. Keep your AI context across sessions.

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

---

**ai-memory enables persistent, cross-session memory by treating a directory-local markdown wiki as the single source of truth and building a derived SQLite index that agents query at runtime.**

AI coding agents traditionally lose all context when a terminal session ends. The [akitaonrails/ai-memory](https://github.com/akitaonrails/ai-memory) project solves this with a Git-backed wiki architecture where every observation gets written atomically to markdown and indexed for fast retrieval. This article explains the seven-stage workflow—from lifecycle hooks to retention policies—that makes cross-session memory possible.

## The Core Architecture: Wiki-First, Index-Second

Unlike systems that store everything in an opaque database, ai-memory uses a **human-readable markdown wiki** as the authoritative source. The SQLite database exists only as a performance-optimized derivative.

This design delivers three advantages for AI coding agents:

- **Durability** – The wiki is a standard Git repository with full version history
- **Portability** – Any tool can read the markdown without special drivers
- **Inspectability** – Developers can browse, search, and edit memory with standard tools

The trade-off is a single-writer bottleneck that serializes all mutations through a dedicated actor.

## Stage 1: Lifecycle Hook Emission

Every agent action triggers a JSON hook to the `/hook` endpoint. In [`crates/ai-memory-hooks/src/lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/lib.rs), the router sanitizes payloads and assigns an `ObservationKind`:

| Kind | Trigger |
|------|---------|
| `session-start` | New CLI invocation or IDE window opened |
| `user-prompt` | Agent receives a coding request |
| `tool-execution` | Agent runs a command or edits a file |
| `session-end` | Process termination or explicit cleanup |

Each observation becomes a `WriteCmd` enqueued to the single-writer SQLite actor defined in [`crates/ai-memory-store/src/writer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/writer.rs).

```bash

# Example: Start a managed workstream that emits session-start

ai-memory run

```

## Stage 2: Atomic Markdown Persistence

The writer actor persists observations by appending to markdown pages under `<data_dir>/wiki/`. In [`crates/ai-memory-wiki/src/atomic.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/atomic.rs), writes use a **temp-file + rename + fsync** sequence that guarantees atomicity even on power loss:

```rust
// From ai-memory-wiki/src/atomic.rs
pub fn atomic_write(path: &Path, content: &[u8]) -> io::Result<()> {
    let temp = NamedTempFile::new_in(path.parent().unwrap())?;
    temp.as_file().write_all(content)?;
    temp.as_file().sync_all()?;      // fsync before rename
    temp.persist(path)?;             // atomic rename
    Ok(())
}

```

This ensures the wiki never contains partially-written pages. Common page paths include:

- [`log.md`](https://github.com/akitaonrails/ai-memory/blob/main/log.md) – Chronological event stream
- `notes/*.md` – Developer-captured insights
- `sessions/<id>.md` – Auto-generated session summaries

## Stage 3: Derived SQLite Indexing

While the wiki remains the source of truth, [`crates/ai-memory-store/src/lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/lib.rs) maintains a derived database at `<data_dir>/db/memory.sqlite` with tables for:

- **pages** – Markdown file metadata with access counters
- **observations** – Individual hook payloads with timestamps
- **links** – Cross-references between pages
- **page_embeddings** – Optional vector representations for semantic search

The schema enables **FTS5 full-text search** and **vector RRF retrieval** when embeddings are configured. Updates happen synchronously with markdown writes to maintain consistency.

## Stage 4: Session Synthesis and Handoffs

When a `session-end` hook arrives, the server performs two critical operations:

1. **Generate session summary** – Creates `sessions/<id>.md` with condensed context
2. **Create handoff row** – Inserts `memory_handoff_begin` pointing to the next expected agent

This **memory handoff** eliminates manual context copying. A developer running Claude Code at 9 AM can resume with Codex at 2 PM with zero friction.

```bash

# End session; creates summary and handoff automatically

ai-memory finalize-session

```

## Stage 5: Runtime Query Surface

Agents retrieve memory through MCP tools implemented in [`crates/ai-memory-store/src/fts_query.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/fts_query.rs). The three primary tools are:

- **`memory_query`** – FTS5 + entity-match + optional vector RRF
- **`memory_read_page`** – Direct markdown retrieval by path
- **`memory_briefing`** – Aggregated context for session startup

Each query bumps `pages.access_count`, enabling the retention system to identify hot versus cold knowledge.

```bash

# Search across all remembered pages with relevance scoring

ai-memory query "how to configure auto-improve"

```

For programmatic access from custom agents:

```rust
use ai_memory_client::MemoryClient;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let client = MemoryClient::new("http://127.0.0.1:49374")?;
    
    let resp = client.memory_query()
        .query("embedding provider")
        .limit(5)
        .send()
        .await?;
        
    for hit in resp.hits {
        println!("▶ {} ({:.2})", hit.path, hit.score);
    }
    Ok(())
}

```

## Stage 6: Optional LLM Consolidation

When `AI_MEMORY_LLM_PROVIDER` is set, the `memory_consolidate` tool in [`crates/ai-memory-consolidate/src/lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-consolidate/src/lib.rs) **rewrites episodic logs into semantic knowledge**. This transforms raw event streams into organized documentation that survives long-term.

The consolidation pipeline:

1. Identifies related observations and pages
2. Generates LLM summaries with source attribution
3. Proposes structural improvements (merging, splitting, linking)
4. Applies changes atomically through the standard writer path

Without consolidation, memory remains searchable but fragmented. With it, the wiki becomes a curated knowledge base.

## Stage 7: Retention and Decay

A periodic **forget-sweep** prevents unbounded growth:

- Evicts pages below an access threshold
- Respects explicit TTLs on observations
- Purges tombstoned records after grace period
- Preserves pinned knowledge regardless of temperature

This keeps query latency bounded while protecting critical context. The sweep runs as a background task in the writer actor, never blocking agent operations.

## Key Source Files

| Component | Path | Purpose |
|-----------|------|---------|
| Hook router | [`crates/ai-memory-hooks/src/lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/lib.rs) | Validates JSON, assigns `ObservationKind`, enqueues writes |
| Wiki API | [`crates/ai-memory-wiki/src/lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/lib.rs) | High-level markdown page operations |
| Atomic writes | [`crates/ai-memory-wiki/src/atomic.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/atomic.rs) | Crash-safe file persistence |
| Writer actor | [`crates/ai-memory-store/src/writer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/writer.rs) | Serialized DB mutations and FTS5 updates |
| Query engine | [`crates/ai-memory-store/src/fts_query.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/fts_query.rs) | FTS5 + entity + vector RRF search |
| CLI entry | [`crates/ai-memory-cli/src/main.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/main.rs) | Subcommand parsing and execution |
| Consolidation | [`crates/ai-memory-consolidate/src/lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-consolidate/src/lib.rs) | LLM-driven page rewriting |

## Summary

- **Wiki-first design** makes memory human-readable and Git-versioned
- **Atomic writes** via temp-file + rename + fsync guarantee durability
- **Single-writer actor** serializes mutations while allowing concurrent reads
- **Session handoffs** enable seamless context transfer between agents and sessions
- **FTS5 + optional vector search** delivers fast, relevant retrieval
- **LLM consolidation** transforms raw logs into structured knowledge
- **Forget-sweep retention** bounds storage without losing important context

## Frequently Asked Questions

### What makes ai-memory different from a simple log file?

Traditional logs are write-only streams that grow indefinitely. ai-memory maintains a **bidirectional relationship** between markdown content and a queryable SQLite index. Every write updates FTS5 and optional embedding tables, while every query feeds back into access statistics that drive retention. The atomic write protocol in [`crates/ai-memory-wiki/src/atomic.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/atomic.rs) also ensures crash safety that append-only logs lack.

### How does cross-session persistence actually work?

When an agent starts, it loads the `memory_handoff_begin` row created by the previous session's `finalize-session` command. This points to the summary page and relevant context pages. Because the wiki is **directory-local and Git-backed**, any subsequent agent invocation in the same repository sees identical state. No network sync or explicit export is required.

### Can I use ai-memory without an LLM provider?

Yes. The core functionality—hook ingestion, atomic markdown writes, FTS5 search, and session handoffs—works entirely offline. LLM consolidation via `memory_consolidate` is **optional** and only activates when `AI_MEMORY_LLM_PROVIDER` is configured. Without it, you'll have searchable but unconsolidated episodic memory.

### What happens if the SQLite database is corrupted?

The database is strictly a **performance derivative**. Delete `<data_dir>/db/memory.sqlite` and restart the server; it will rebuild the entire index from the markdown wiki in [`crates/ai-memory-store/src/lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/lib.rs). This reconstruction preserves all content because the wiki is the authoritative source.