# Understanding Cross-Cutting Invariants in ai-memory's Architecture

> Discover the fifteen cross-cutting invariants in ai-memory's architecture. Learn how SQLite channels and typed sanitization ensure data integrity, thread safety, and predictable behavior.

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

---

**The ai-memory system enforces fifteen cross-cutting invariants—from single-writer SQLite channels to typed sanitization boundaries—that guarantee data integrity, thread safety, and predictable behavior across every Rust crate.**

The `akitaonrails/ai-memory` repository implements these architectural rules as non-negotiable constraints documented in [`docs/ARCHITECTURE.md`](https://github.com/akitaonrails/ai-memory/blob/main/docs/ARCHITECTURE.md). Each invariant addresses a specific failure mode discovered in prior prototyping, ensuring that configuration, storage, and concurrency behave identically across all runtime paths.

## What Are Cross-Cutting Invariants?

Cross-cutting invariants are architectural constraints that span multiple modules or layers of an application. Unlike local business logic, these rules apply universally—regardless of which crate, function, or thread is executing—to prevent systemic failures.

## The 15 Cross-Cutting Invariants in ai-memory

### Configuration and Initialization

- **One config-read path**: `Config::load()` executes exactly once at startup. No module reads environment variables directly, ensuring a single source of truth and preventing hidden-state bugs.
- **Absolute canonical data directory**: The data directory resolves to an absolute path once at startup and is logged prominently. All components reference this same physical location to avoid accidental duplication.
- **Zero-LLM default**: The server operates fully without any LLM provider configured, enabling headless deployment and simplifying operations for users who do not need AI features.
- **Provider auth resolves before construction**: Each LLM provider receives a typed `ProviderAuth` object during initialization; implementations never read environment variables directly. This centralizes secret handling and makes credential rotation safe.
- **No global singletons**: The codebase explicitly rejects `lazy_static` or hidden globals. All dependencies are injected, making the system testable and preventing state leakage across threads.

### Storage and Data Integrity

- **Single-writer SQLite actor**: All mutations route through a single `mpsc` channel to one dedicated OS thread. This prevents race conditions while preserving SQLite's ACID guarantees.
- **Indexes commit in the same transaction as data**: Schema changes and associated FTS5 or entity indexes write atomically with payload rows. This prevents stale search indexes after a crash.
- **Typed three-tuple identity**: Every domain row stores `(workspace_id, project_id, path)` from day zero. This enables proper multi-workspace isolation and safe cross-project linking.
- **Embedding metadata denormalised**: Each `page_embeddings` row stores the provider, model, and dimension alongside the vector. Stale vectors are automatically ignored when embedding configurations change.
- **Atomic file writes**: Wiki writes use a temporary file, `rename`, then `fsync`. The file-watcher ignores its own writes by filename prefix, guaranteeing durability of the markdown wiki.

### Security and Validation

- **Privacy strip is a typed boundary**: Only the `sanitize()` function can create a `Sanitized<NewObservation>` value. Untrusted text must pass through this sanitizer before entering the store.
- **JSON-schema-only structured outputs**: LLM providers must emit JSON matching a predefined schema. The system rejects XML or free-form text to keep downstream parsers stable and prevent injection attacks.

### Concurrency and Runtime Safety

- **Hooks are fire-and-forget**: Hook scripts enforce a timeout of ≤200 ms, and the server replies with HTTP 202 (or 429 when saturated). This guarantees user-visible CLI commands never block on external network latency.
- **Live-process check before direct-disk lifecycle ops**: Destructive commands like `reset`, `restore`, `reindex`, and `uninstall --purge-data` first query the running process via `sysinfo`. This prevents file corruption while the server actively uses the data directory.
- **Tracing subscribers filter their own module**: No tracing subscriber creates a feedback loop that would cause unbounded logging. This keeps observability overhead bounded and prevents recursive tracing crashes.

## How Invariants Are Enforced in Code

### Centralized Configuration Loading

The system guarantees a single configuration load at startup through `Config::load()` in [`crates/ai-memory-core/src/config.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/config.rs). This function is called exactly once in the CLI entry point:

```rust
let cfg = ai_memory_core::config::Config::load()?;   // called at program start only

```

### Single-Writer Channel Pattern

All write operations serialize through the writer actor defined in [`crates/ai-memory-store/src/lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/lib.rs). Mutations become messages rather than direct database calls:

```rust
let write = ai_memory_store::WriteCmd::WritePage { 
    path: "notes/todo.md".into(), 
    body: "…".into() 
};
ai_memory_store::writer::WRITER.send(write)?;   // all mutations go through this channel

```

### Sanitization Boundary

The privacy invariant enforces that only `sanitize()` can produce sanitized observations. This type-level guarantee prevents accidental storage of untrusted data:

```rust
let raw = HookPayload { … };
let sanitized = ai_memory_hooks::sanitizer::sanitize(raw)?;   // only way to get a Sanitized<…>

```

### Atomic Wiki Operations

The markdown wiki—the system's source of truth—uses atomic file operations implemented in [`crates/ai-memory-wiki/src/wiki.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/wiki.rs):

```rust
let mut wiki = ai_memory_wiki::Wiki::open(&cfg.data_dir)?;
wiki.write_page("ideas/feature.md", "# New Feature\nDetails…")?;

```

### Process-Aware Lifecycle Commands

Destructive CLI commands check for running server processes before executing. The `reset` command in [`crates/ai-memory-cli/src/commands/reset.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/commands/reset.rs) implements this safety check:

```rust
ai_memory_cli::commands::reset::run()?;   // internally looks at sysinfo before touching files

```

### Identity-Based Queries

The three-tuple identity invariant enables precise data isolation. Queries in [`crates/ai-memory-store/src/queries.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/queries.rs) require all three identifiers:

```rust
let rows = ai_memory_store::queries::pages_by_path(
    &reader_pool,
    workspace_id,
    project_id,
    "docs/architecture.md"
)?;

```

## Key Files Implementing Cross-Cutting Invariants

| File | Role |
|------|------|
| [`docs/ARCHITECTURE.md`](https://github.com/akitaonrails/ai-memory/blob/main/docs/ARCHITECTURE.md) | The single-source design document that lists and explains all fifteen invariants. |
| [`crates/ai-memory-core/src/config.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/config.rs) | Implements the immutable configuration struct and single-load policy. |
| [`crates/ai-memory-store/src/lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/lib.rs) | Contains the writer actor, `WriteCmd` enum, and read-only connection pool. |
| [`crates/ai-memory-wiki/src/wiki.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/wiki.rs) | Provides atomic markdown writes and the Git-backed file watcher. |
| [`crates/ai-memory-hooks/src/sanitizer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/sanitizer.rs) | Central sanitization boundary for all hook payloads. |
| [`crates/ai-memory-cli/src/main.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/main.rs) | CLI entry point that initializes config and spawns the writer actor. |

## Summary

- **Cross-cutting invariants** in `akitaonrails/ai-memory` are architectural rules enforced across all crates to prevent data corruption and race conditions.
- **Configuration safety** is guaranteed by loading `Config` exactly once and resolving all credentials before provider construction.
- **Storage integrity** relies on a single-writer SQLite actor, atomic file operations, and transaction-bound index updates.
- **Security boundaries** use type-system enforcement via `Sanitized<NewObservation>` and mandatory JSON schema validation for LLM outputs.
- **Operational safety** includes live-process checks before destructive commands and fire-and-forget hook timeouts to prevent CLI blocking.

## Frequently Asked Questions

### What is a cross-cutting invariant in software architecture?

A cross-cutting invariant is a constraint that applies universally across multiple modules or layers of an application, rather than being localized to specific business logic. In the ai-memory codebase, these invariants ensure that critical behaviors—such as configuration loading and database access—remain consistent regardless of which crate or function is executing.

### Why does ai-memory use a single-writer pattern for SQLite?

The single-writer pattern funnels all mutations through one dedicated OS thread via an `mpsc` channel. This design eliminates race conditions without requiring complex locking schemes, while preserving SQLite's native ACID guarantees. As implemented in [`crates/ai-memory-store/src/lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/lib.rs), this pattern allows concurrent reads while serializing writes safely.

### How does the system prevent configuration drift and hidden state bugs?

The **one config-read path** invariant mandates that `Config::load()` executes exactly once at startup, and the **no global singletons** rule prohibits `lazy_static` or hidden globals. Combined with **provider auth resolving before construction**, these constraints ensure that all configuration state is explicitly injected and immutable after initialization, preventing scattered environment variable access.

### Why is the zero-LLM default important for deployment?

The **zero-LLM default** invariant ensures the server operates fully without any LLM provider configured. This enables headless deployment scenarios and simplifies operations for users who only need the memory store's document and search capabilities without artificial intelligence features, reducing both attack surface and operational complexity.