What Is the Data Flow in the ai-memory System? A 3-Phase Pipeline Explained
The ai-memory system uses a deterministic three-phase pipeline: ingest via lifecycle hooks, persist through a single-writer actor to SQLite and markdown, and serve through read-only query pools with optional LLM auto-improvement.
The ai-memory system, written in Rust and designed for AI agent memory management, treats the markdown wiki as the source of truth while maintaining a SQLite-derived index for fast search. Understanding the data flow within the ai-memory system is essential for developers integrating agents, debugging performance, or extending the codebase. The architecture follows a strict separation between write-heavy ingest paths and read-heavy query paths, with all mutations funneled through a single thread to prevent database contention.
Ingest Phase: From Agent Hook to Write Queue
The ingest phase transforms external agent events into sanitized, durable commands.
Lifecycle Hooks Trigger the Flow
Agents emit events through the MCP (Model Context Protocol) server endpoint. These arrive as HTTP POST requests to /hook, handled by ai_memory_mcp::server::Server in crates/ai-memory-mcp/src/server.rs.
Hooks can be emitted via CLI:
ai-memory hook --event SessionStart \
--payload '{"session_id":"123","agent_kind":"codex"}'
Or directly via HTTP from shell scripts or other agents.
Router Sanitization: The Trust Boundary
The ai_memory_hooks::router::Router in crates/ai-memory-hooks/src/router.rs performs the only trusted transformation of untrusted input. It converts raw JSON into Sanitized<NewObservation> structs, rejecting malformed payloads before they reach storage.
Write Commands Queue to Single-Writer Actor
The router constructs WriteCmd variants—InsertObservation, UpsertPage, or InsertSession—and sends them via an mpsc channel to WriterHandle in crates/ai-memory-store/src/writer.rs. This channel decouples the async HTTP handler from the synchronous writer thread, providing backpressure and preventing memory spikes under load.
Persist Phase: Single-Writer Guarantees Atomicity
The persist phase enforces sequential consistency for all durable state.
WriterHandle Owns the Sole SQLite Connection
The WriterHandle runs on a dedicated OS thread with exclusive access to the rusqlite::Connection. All mutations—pages, sessions, observations, handoffs, embeddings—execute inside this thread. This design eliminates "database is locked" errors without complex WAL tuning or connection pooling for writes.
Key operations in crates/ai-memory-store/src/writer.rs:
INSERTorUPSERTentity rows- Update FTS5 full-text index
- Maintain vector similarity tables (if embeddings enabled)
- Apply batch operations atomically
Wiki Files Update with Atomic Writes
After each successful SQLite transaction, the writer invokes ai_memory_wiki::Wiki methods in crates/ai-memory-wiki/src/wiki.rs:
Wiki::write_pagefor single-page updatesWiki::apply_batchfor bulk operations
File writes use atomic rename patterns: write to temporary file, fsync, then rename to target. This ensures the markdown wiki never contains partial content, preserving the source-of-truth invariant.
Serve Phase: Read-Only Queries and LLM Enrichment
The serve phase provides fast, concurrent access with optional background intelligence.
ReaderPool Handles Concurrent Queries
Read operations use ReaderPool from crates/ai-memory-store/src/lib.rs, which distributes lightweight read-only SQLite connections across threads. The memory_query function combines:
- FTS5 full-text search for keyword matches
- Lexical entity matching for structured references
- Vector similarity (optional) for semantic search
Results are ranked and returned as markdown page references with relevance scores.
Auto-Improvement Scheduler Runs Background Enrichment
When AI_MEMORY_LLM_PROVIDER is configured, the auto-improvement scheduler in crates/ai-memory-consolidate/src/auto_improve_schedule.rs periodically:
- Scans
SessionEndrows for recently completed sessions - Invokes the configured LLM to synthesize insights
- Writes enriched pages back through the same
WriterHandle
This creates a feedback loop: agent observations → storage → LLM analysis → improved documentation → better future retrieval.
Example scheduler usage:
use ai_memory_consolidate::auto_improve_schedule::AutoImproveScheduler;
use ai_memory_store::{ReaderPool, WriterHandle};
async fn run_scheduler(reader: ReaderPool, writer: WriterHandle) {
let mut scheduler = AutoImproveScheduler::new(reader, writer);
scheduler.run_once().await.unwrap();
}
End-to-End Data Flow Diagram
Agent Hook ──► /hook (MCP) ──► Router (sanitize) ──► WriteCmd ──► WriterHandle
│ │
│ ▼
│ SQLite + Wiki files
│ ▲
│ │
│ Auto-Improve Scheduler
│ │
▼ ▼
Query API (memory_query) ◄──── ReaderPool ◄──── SQLite index
Solid lines indicate request-response paths. Dashed lines indicate background maintenance. The core invariant holds throughout: markdown wiki remains the source of truth; SQLite remains the derived, searchable index.
Key Source Files for Data Flow Analysis
| Component | File Path | Purpose |
|---|---|---|
| MCP server entry | crates/ai-memory-mcp/src/server.rs |
HTTP /hook handler |
| Request sanitization | crates/ai-memory-hooks/src/router.rs |
Trust boundary for incoming data |
| Write serialization | crates/ai-memory-store/src/writer.rs |
Single-writer actor, SQLite + wiki persistence |
| Markdown operations | crates/ai-memory-wiki/src/wiki.rs |
Atomic file writes, index coordination |
| Read query engine | crates/ai-memory-store/src/lib.rs |
memory_query, ReaderPool |
| LLM enrichment | crates/ai-memory-consolidate/src/auto_improve_schedule.rs |
Background improvement scheduler |
| Architecture docs | docs/ARCHITECTURE.md |
High-level design and diagrams |
Summary
- Ingest: Agents send hooks → MCP server → Router sanitization → mpsc channel →
WriterHandlequeue - Persist: Single-writer thread executes SQLite transactions, then atomically updates markdown wiki files
- Serve:
ReaderPoolenables concurrent FTS5 + vector queries; auto-improvement scheduler adds LLM-driven enrichment - Invariant: Markdown wiki is source of truth; SQLite is derived index for search performance
- Safety: All mutations serialize through one thread; file writes are atomic; sanitization occurs at system boundary
Frequently Asked Questions
How does ai-memory prevent database corruption with concurrent writes?
All mutations route through WriterHandle in crates/ai-memory-store/src/writer.rs, which owns the sole rusqlite::Connection on a dedicated OS thread. This single-writer pattern eliminates write contention and "database is locked" errors without requiring WAL mode or complex locking schemes.
Can multiple agents write to ai-memory simultaneously?
Yes. The /hook endpoint in crates/ai-memory-mcp/src/server.rs is async and accepts concurrent requests. These are converted to WriteCmd messages and queued via an mpsc channel. The WriterHandle processes commands sequentially, so agents experience backpressure rather than lock failures.
What happens if the wiki files and SQLite index become out of sync?
The code in crates/ai-memory-wiki/src/wiki.rs performs atomic file updates (temp + rename + fsync) only after successful SQLite transactions. If either fails, the operation aborts. The wiki serves as the authoritative source; SQLite can be rebuilt from markdown if corruption occurs.
How do I query ai-memory programmatically?
Use the memory_query function from crates/ai-memory-store/src/lib.rs with a ReaderPool:
use ai_memory_store::{ReaderPool, memory_query};
let pool = ReaderPool::new("./memory.db")?;
let results = memory_query(&pool, "authentication token")?;
Results combine FTS5 text matches, entity references, and optional vector similarity rankings.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →