WriteCmd and the SQLite Actor: Single-Writer Command Pattern in ai-memory

WriteCmd is the private enum that serializes all mutable SQLite operations through a dedicated Tokio actor, enforcing sequential writes and preventing race conditions in the ai-memory store.

In the akitaonrails/ai-memory repository, all database mutations flow through a strict single-writer architecture. The WriteCmd enum serves as the exclusive message protocol for this SQLite actor, ensuring that only one thread ever writes to the database while readers operate concurrently from a separate pool.

What Is WriteCmd?

Definition and Location

The WriteCmd enum is declared at line 48 of crates/ai-memory-store/src/writer.rs. It is intentionally kept private to the ai-memory-store crate, which prevents external modules from constructing arbitrary SQL commands. Instead, callers must use the type-safe API exposed by the Store facade, preserving critical invariants around atomicity and transaction boundaries.

Command Variants and Operations

Each variant of WriteCmd encodes a specific mutation the store can perform. These range from low-level CRUD operations to high-level orchestration commands:

  • UpsertPage and DeletePage for wiki content management
  • BeginSession and EnqueueSessionConsolidation for session handling
  • PurgeProject and HardDeleteDecayedPageChain for maintenance tasks
  • Shutdown for graceful actor termination

Because the enum is exhaustive and typed, the system guarantees that every mutation is handled within a single transaction context.

The Single-Writer Actor Architecture

The Writer Event Loop

The writer actor spawns a background worker_loop that owns a rusqlite::Connection. This loop listens on an mpsc::Receiver<WriteCmd> and processes commands sequentially, implementing workspace invariant #1: there is only ever one writer.

// Conceptual structure from crates/ai-memory-store/src/writer.rs
loop {
    match receiver.recv().await {
        Some(cmd) => match cmd {
            WriteCmd::UpsertPage { page, reply } => { /* ... */ }
            WriteCmd::Shutdown => break,
            // ... other variants
        }
    }
}

Channel-Based Communication

Callers do not interact with the SQLite connection directly. They invoke store.send(WriteCmd::...) which pushes the command onto the writer's mpsc channel (self.tx). When a result is required, commands carry a oneshot::Sender that the writer uses to return values asynchronously.

Safety Guarantees and Transaction Boundaries

By funneling all writes through WriteCmd, the system avoids SQLite race conditions entirely. The design ensures every mutation is part of a single transaction, making it possible to reason about durability, ordering, and error handling in one centralized location.

The actor also implements a "try-send" path for background tasks that must never block the main process, and uses WriteCmd::Shutdown to implement graceful termination without data loss.

Practical Code Examples

Upserting a Wiki Page

let page = Page {
    path: PagePath::new("docs/FAQ.md")?,
    content: "## FAQ\n…".into(),

    // …
};
store
    .send(WriteCmd::UpsertPage { page, reply: tx })
    .await?;

Beginning a New Session

let session = Session::new(user_id, client_id);
let (reply_tx, reply_rx) = oneshot::channel();
store
    .send(WriteCmd::BeginSession { session, reply: reply_tx })
    .await?;
let session_id = reply_rx.await?;

Deleting Stale Data

store
    .send(WriteCmd::HardDeleteDecayedPageChain {
        page_id,
        reply: reply_tx,
    })
    .await?;

Integration with the Store Facade

Located in crates/ai-memory-store/src/store.rs, the Store type provides the public send method that wraps the private WriteCmd variants. This abstraction prevents external code from bypassing the actor's queue. Similarly, crates/ai-memory-wiki/src/wiki.rs demonstrates concrete usage when persisting wiki content, as documented in the project's docs/ARCHITECTURE.md.

Summary

  • WriteCmd is defined at line 48 in crates/ai-memory-store/src/writer.rs as a private enum
  • It serves as the exclusive protocol for the single-writer SQLite actor, enforcing workspace invariant #1
  • All variants are processed sequentially in worker_loop to prevent race conditions
  • Commands use mpsc channels for input and optional oneshot channels for replies
  • The design guarantees transaction atomicity and enables graceful shutdown via WriteCmd::Shutdown

Frequently Asked Questions

What is the WriteCmd enum in ai-memory?

WriteCmd is the central command message used by the single-writer SQLite actor to encapsulate every mutable database operation. It is a private enum defined in the store crate that ensures external code cannot construct arbitrary SQL, enforcing type-safe database access.

How does WriteCmd prevent race conditions in SQLite?

By forcing all write operations through a single Tokio task that owns the rusqlite::Connection, WriteCmd guarantees sequential processing. This eliminates concurrent write attempts and ensures every mutation occurs within proper transaction boundaries.

Where is the WriteCmd enum defined in the source code?

The enum is defined at line 48 of crates/ai-memory-store/src/writer.rs, alongside the writer actor implementation and the worker_loop that processes the commands.

How do applications send commands to the SQLite writer actor?

Applications use the Store facade's send method, which internally constructs the appropriate WriteCmd variant and pushes it to the writer's mpsc channel. This abstraction allows concurrent reads from a separate pool while maintaining strict single-writer safety for mutations.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →