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:
UpsertPageandDeletePagefor wiki content managementBeginSessionandEnqueueSessionConsolidationfor session handlingPurgeProjectandHardDeleteDecayedPageChainfor maintenance tasksShutdownfor 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
WriteCmdis defined at line 48 incrates/ai-memory-store/src/writer.rsas 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_loopto prevent race conditions - Commands use
mpscchannels for input and optionaloneshotchannels 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →