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

> Discover WriteCmd and its crucial role within the SQLite actor in ai-memory. Learn how it serializes operations for sequential writes and prevents race conditions.

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

---

**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`](https://github.com/akitaonrails/ai-memory/blob/main/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.

```rust
// 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

```rust
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

```rust
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

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

```

## Integration with the Store Facade

Located in [`crates/ai-memory-store/src/store.rs`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/wiki.rs) demonstrates concrete usage when persisting wiki content, as documented in the project's [`docs/ARCHITECTURE.md`](https://github.com/akitaonrails/ai-memory/blob/main/docs/ARCHITECTURE.md).

## Summary

- **`WriteCmd`** is defined at line 48 in [`crates/ai-memory-store/src/writer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/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.