Architecture

Architecture

System architecture for the CellState agent memory platform.

For core concepts (entities, memory types, coordination patterns), see MENTAL_MODEL.md. For deployment and configuration, see OPERATIONS.md.

Overview

CellState is an event-sourced agent memory framework built on:

  • PostgreSQL as the ledger — ACID transactions, RLS tenant isolation, pgvector embeddings
  • Projections derived from events — working set, graph links, vectors
  • Rust API as orchestrator — single binary, all-in-one server
  • Pack/PCP for declarative agent config — Markdown + TOML compiled to runtime rules

The architecture follows a “Redux for agents” pattern:

  • Event log = actions (immutable facts)
  • Projections = store (derived state)
  • Selectors = SQL queries
  • Middleware = mutation pipeline + receipts + idempotency
┌──────────────────────────────────────────────────────────────────────┐
│                         CellState STACK                              │
├──────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │                    HTTP/WebSocket API Layer                    │   │
│  │                 (crates/api/src/routes/*)                      │   │
│  └──────────────────────────┬───────────────────────────────────┘   │
│                             │                                        │
│  ┌──────────────────────────▼───────────────────────────────────┐   │
│  │                   Mutation Pipeline                            │   │
│  │  Stage A: Context Assembly (I/O)                               │   │
│  │  Stage B: Policy Evaluation (pure — no I/O)                    │   │
│  │  Stage C: Database Commit (atomic transaction)                 │   │
│  │  Stage D: Receipt Generation + Event Relay                     │   │
│  └──────────────────────────┬───────────────────────────────────┘   │
│                             │                                        │
│  ┌──────────────────────────▼───────────────────────────────────┐   │
│  │              DbClient (SQL-First) + CachedDbClient            │   │
│  │                    [LMDB read-through cache]                   │   │
│  └──────────────────────────┬───────────────────────────────────┘   │
│                             │                                        │
│  ┌──────────────────────────▼───────────────────────────────────┐   │
│  │             PostgreSQL 18 (with pgrx extension)               │   │
│  │  ┌────────────┬────────────┬────────────┬────────────┐       │   │
│  │  │  Entities  │  Events    │  Graph     │  Vectors   │       │   │
│  │  │ trajectory │ event      │ edge       │ pgvector   │       │   │
│  │  │ scope      │ event_dag  │ link       │ embeddings │       │   │
│  │  │ artifact   │            │            │            │       │   │
│  │  │ note, ...  │            │            │            │       │   │
│  │  └────────────┴────────────┴────────────┴────────────┘       │   │
│  └──────────────────────────────────────────────────────────────┘   │
│                                                                      │
│  PARALLEL SYSTEMS:                                                   │
│  ┌─────────────────────────┐  ┌────────────────────────────────┐   │
│  │      HybridDag           │  │   Read-Through Cache            │   │
│  │  ┌────────┬──────────┐  │  │   (LMDB + ChangeJournal)        │   │
│  │  │  LMDB  │ PG Cold  │  │  │                                  │   │
│  │  │  Hot   │ Storage  │  │  │   Sub-millisecond hot reads      │   │
│  │  └────────┴──────────┘  │  │                                  │   │
│  └─────────────────────────┘  └────────────────────────────────┘   │
│                                                                      │
│  14 SUPERVISED BACKGROUND JOBS                                       │
│  (change relay, saga cleanup, summarization, TTL, decay, ...)       │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘

Crate Structure

cellstate/
├── crates/
│   ├── core/             # Domain types, identity, event primitives
│   ├── api/              # HTTP API, mutation pipeline, routes, jobs
│   ├── storage/          # Event DAG, LMDB cache, change journal
│   ├── pcp/              # Pack Configuration Protocol — validation engine
│   ├── pipeline/         # Pack compiler — TOML/YAML/Markdown → runtime rules
│   ├── pg/               # PostgreSQL extension (pgrx) — triggers, SQL functions
│   ├── cli/              # CLI binary for admin operations
│   └── test-utils/       # Test fixtures, generators, env helpers
└── packages/
    └── rust/             # Rust SDK — typed HTTP client

See the crates README in the CellState source repository for the dependency graph.

Mutation Pipeline

All state-changing operations flow through a 4-stage compiler-like pipeline. No route handler writes directly to the database.

Stage A: Assemble     Stage B: Gates      Stage C: Commit     Stage D: Receipt
   (I/O OK)              (Pure)             (Atomic)           (Relay)
     │                     │                   │                  │
     ▼                     ▼                   ▼                  ▼
Fetch budgets      Run policy checks    Single txn with     Return proof,
+ policy config    No I/O allowed       entity + event      wake ChangeRelay
+ idempotency      Pass or Reject       + receipt insert

Stage A: Context Assembly

Fetches everything needed for policy evaluation: token budgets, PCP config, tenant limits, cloud metering quotas. All I/O happens here so Stage B is pure.

Stage B: Policy Gates

Pure functions that receive a frozen ValidationContext and return Pass or Reject(PolicyViolation). No async, no database calls. This makes policy enforcement deterministic and testable.

Gates include: PII redaction verification, token budget enforcement, schema validation, PCP policy checks, and cloud usage limits.

Stage C: Database Commit

A single PostgreSQL transaction writes the entity, semantic event, receipt, and change log entry atomically. If the server crashes after commit but before broadcast, the ChangeRelay picks it up on restart (transactional outbox pattern).

Stage D: Receipt Generation

Returns a ReceiptEnvelope with a Blake3 hash proving the mutation occurred. Wakes the ChangeRelay to broadcast the event via WebSocket.

Route Handler Pattern

Routes become minimal — HTTP validation only, pipeline owns all mutation semantics:

pub async fn create_artifact(
    State(pipeline): State<Arc<MutationPipeline>>,
    AuthExtractor(auth): AuthExtractor,
    Json(req): Json<CreateArtifactRequest>,
) -> ApiResult<impl IntoResponse> {
    let result = pipeline.create::<ArtifactResponse, _>(
        auth.tenant_id, scope_id, &req,
    ).await?;
    Ok((StatusCode::CREATED, Json(result.entity)))
}

Event DAG

CellState maintains two event rails:

Semantic Event Store (cellstate_event)

Domain events for audit and replay. Tenant-scoped with RLS. Human-readable event types like TrajectoryCreated, NoteUpdated.

Causal DAG (cellstate_event_dag)

Hash-chained event log with causal ordering. Uses a HybridDag architecture:

  • Hot tier: LMDB for sub-microsecond reads of recent events
  • Cold tier: PostgreSQL for durable storage and historical queries

Events are identified by EventKind (16-bit encoded: upper 4 bits = category, lower 12 = type) and linked via parent_event_ids for causal ordering. Each event carries a Blake3 hash chain for tamper detection.

EventKind encoding:
  0x0xxx: System      0x1xxx: Trajectory   0x2xxx: Scope
  0x3xxx: Artifact    0x4xxx: Note         0x5xxx: Turn
  0x6xxx: Agent       0x7xxx: Lock         0x8xxx: Message
  0x9xxx: Delegation  0xAxxx: Handoff      0xBxxx: Edge

Event Flow

Mutation Request

    ├─→ Pipeline Stage C: DB Commit
    │   └─→ INSERT entity → trigger → cellstate_changes
    │   └─→ INSERT cellstate_event (semantic, same txn)

    └─→ Event Emission (post-commit)
        └─→ HybridDag.append()
            ├─→ LMDB hot tier (sync, μs)
            └─→ PgEventDagStorage (async, ms)

Context Assembly

POST /context/assemble retrieves and ranks memory for an agent’s next action.

Sources

SourceTableDescription
Notescellstate_noteSemantic search or list by trajectory
Artifactscellstate_artifactSemantic search or list by scope
Turnscellstate_turnRecent conversation within scope
Working Setcellstate_agent_working_setAgent KV workspace
Graph Linkscellstate_linkRelationship traversal
Hierarchycellstate_trajectoryParent chain walk
Summariescellstate_summarization_*Scope summaries

7-Factor Scoring

Items are ranked by a deterministic composite score:

score(item) = w_v * S_vector       (embedding similarity)
            + w_w * S_warmth       (access frequency + decay)
            + w_r * S_recency      (time decay)
            + w_a * S_abstraction  (L0/L1/L2 preference)
            + w_g * S_graph        (graph distance from anchor)
            + w_k * S_keyword      (exact/partial match)
            + w_c * S_causal       (event DAG proximity)

Weights are configurable per-agent via pack config. Defaults sum to 1.0 and are normalized by the pack compiler if they don’t.

Storage Layers

Read-Through Cache (LMDB)

The CachedDbClient wraps DbClient with an LMDB-backed read-through cache. Cache invalidation uses a ChangeJournal:

  • Single instance: In-memory journal, invalidated on every pipeline write
  • Multi-instance: PG change journal poller syncs invalidations across instances (~2s staleness window)

Database Schema

The PostgreSQL schema includes ~20 entity tables, all with:

  • tenant_id column with RLS enforcement via cellstate_current_tenant_id()
  • Change triggers that write to cellstate_changes for the relay
  • UUID primary keys with typed ID wrappers in Rust

Key migrations: V1 (base schema) → V5 (RLS) → V15 (principal model, working set, graph links) → V16 (event DAG cold storage).

Background Jobs

14 supervised background jobs run in the same process. Each uses the supervisor pattern: panics trigger automatic restart with backoff. Job health is tracked and exposed via /health/ready.

See OPERATIONS.md for the full job table.

Observability

  • Prometheus metrics on /metrics (28+ counters, gauges, histograms)
  • OTLP trace export to Jaeger/Tempo/Honeycomb
  • Sentry for error tracking with release tagging
  • Structured logging via tracing with span context
  • x-cellstate-trace-id header on every HTTP response

Diagrams

Mermaid source files are in docs/diagrams/:

DiagramDescription
mutation-pipeline.mmd4-stage pipeline flow
event-dag.mmdEvent DAG structure
entity-model.mmdEntity relationships
dataflow.mmdData flow through the system
api-server.mmdServer component layout
concept-model.mmdConceptual model
real-time-flow.mmdWebSocket event flow
pack-compiler.mmdPack compilation pipeline
ecosystem.mmdSDK and tooling ecosystem

Diagrams

%% API server architecture -- middleware stack, protocols, and background jobs graph TD classDef runtime fill:#7733cc,color:#fff,stroke:#8844dd,stroke-width:2px classDef state fill:#22b37a,color:#fff,stroke:#2ed198,stroke-width:2px classDef event fill:#e85a2a,color:#000,stroke:#cc4418,stroke-width:2px classDef storage fill:#1fad8f,color:#fff,stroke:#26d9b3,stroke-width:2px classDef default fill:#1a1a2e,color:#fff,stroke:#353548,stroke-width:1px Client[Client] --> GlobalMW subgraph GlobalMW[Global Middleware] direction LR CORS[CORS] --> Observability[Observability] Observability --> Compression[Compression] Compression --> BodyLimit[Body Limit 10MB] end GlobalMW --> Router[Router] subgraph PerRoute[Per-Route Middleware] Auth[Auth
JWT / API Key / OAuth + DPoP] RateLimit[Rate Limiting] Idempotency[Idempotency] Scopes[Scope Enforcement] end Router --> REST[REST Routes
51 modules] Router --> WS[WebSocket Handler] Router --> MCP[MCP Server
Model Context Protocol] Router --> WebMCP[WebMCP
Browser Agent Tools] REST --> Pipeline[MutationPipeline] WS --> Pipeline MCP --> Pipeline WebMCP --> Pipeline Pipeline --> DB[(PostgreSQL 18
+ pgvector)] Pipeline --> DAG[Event DAG
LMDB + PG HybridDag] Pipeline --> Broadcast[Event Sink Registry] Broadcast --> WsBroadcast[WebSocket Broadcast] Broadcast --> LogSink[Log Sink] subgraph Jobs[Background Jobs -- 16 modules] ChangeRelay[Change Relay] SagaCleanup[Saga Cleanup] Summarization[Summarization Executor] TTLCleanup[TTL Cleanup] MemoryDecay[Memory Decay] HashChainAudit[Hash Chain Audit] DriftDetection[Drift Detection] AgentDeliberation[BDI Agent Deliberation] More[+ 8 more] end DB --> ChangeRelay ChangeRelay --> WsBroadcast subgraph Cache[Read-Through Cache] LMDB[LMDB Backend] Moka[Moka Context Cache] Journal[Change Journal
Cross-Instance Invalidation] end DB --> Cache Cache --> REST
Api Server
sequenceDiagram participant Client participant API as API Server participant Pipeline as Mutation Pipeline participant Gates as Gate Registry participant DB as PostgreSQL participant DAG as Event DAG participant WS as WebSocket Client->>API: HTTP/WebSocket Request API->>Pipeline: Operation + Context Pipeline->>Pipeline: Stage A: Assemble ValidationContext Pipeline->>Gates: Stage B: Run Gates Gates-->>Pipeline: Pass / Reject alt All Gates Pass Pipeline->>DB: Stage C: Commit Mutation DB-->>Pipeline: Entity + change_id Pipeline->>DAG: Append Event Pipeline-->>API: Stage D: MutationResult + ReceiptEnvelope API->>WS: Broadcast WsEvent API-->>Client: Response + Receipt else Gate Rejection Pipeline-->>API: PolicyViolation API-->>Client: 422 + violation details end
Dataflow
%% Core domain entity model %% TenantId is an isolation boundary (typed ID), not an entity struct graph TD classDef runtime fill:#7733cc,color:#fff,stroke:#8844dd,stroke-width:2px classDef state fill:#22b37a,color:#fff,stroke:#2ed198,stroke-width:2px classDef event fill:#e85a2a,color:#000,stroke:#cc4418,stroke-width:2px classDef storage fill:#1fad8f,color:#fff,stroke:#26d9b3,stroke-width:2px classDef default fill:#1a1a2e,color:#fff,stroke:#353548,stroke-width:1px TenantId[TenantId
Isolation Boundary] --> Agent[Agent] TenantId --> Trajectory[Trajectory] Trajectory --> Scope[Scope] Trajectory --> Artifact[Artifact] Trajectory --> Note[Note] Trajectory -->|parent| Trajectory Scope --> Turn[Turn] Scope --> Artifact Scope -->|parent| Scope Agent --> Trajectory Agent --> Message[Message] Lock[Lock] -->|holder_agent_id| Agent Delegation[Delegation] --> Agent Handoff[Handoff] --> Agent subgraph Typestate[Compile-Time Safe Lifecycles] Lock Delegation Handoff end subgraph EventSourcing[Event Sourced] Trajectory Scope Artifact Note end
Entity Model
%% Event DAG -- unidirectional event flow with upstream signaling graph TD classDef runtime fill:#7733cc,color:#fff,stroke:#8844dd,stroke-width:2px classDef state fill:#22b37a,color:#fff,stroke:#2ed198,stroke-width:2px classDef event fill:#e85a2a,color:#000,stroke:#cc4418,stroke-width:2px classDef storage fill:#1fad8f,color:#fff,stroke:#26d9b3,stroke-width:2px classDef default fill:#1a1a2e,color:#fff,stroke:#353548,stroke-width:1px subgraph EventDAG[Event DAG] Root[Root Event
depth=0 lane=0 seq=0] E1[Event
depth=1 lane=0 seq=1] E2[Event
depth=2 lane=0 seq=2] Fork1[Event
depth=2 lane=1 seq=0] Fork2[Event
depth=3 lane=1 seq=1] E3[Event
depth=3 lane=0 seq=3] Root --> E1 E1 --> E2 E1 --> Fork1 E2 --> E3 Fork1 --> Fork2 end subgraph Structure[DagPosition Fields] Depth[depth: distance from root] Lane[lane: parallel track] Seq[sequence: monotonic counter] end
Event Dag
%% The four-stage Mutation Pipeline -- the single throat for all state changes graph TD classDef runtime fill:#7733cc,color:#fff,stroke:#8844dd,stroke-width:2px classDef state fill:#22b37a,color:#fff,stroke:#2ed198,stroke-width:2px classDef event fill:#e85a2a,color:#000,stroke:#cc4418,stroke-width:2px classDef storage fill:#1fad8f,color:#fff,stroke:#26d9b3,stroke-width:2px classDef default fill:#1a1a2e,color:#fff,stroke:#353548,stroke-width:1px Request[API Request] --> StageA subgraph StageA[Stage A: Context Assembly] direction TB FetchBudgets[Fetch Scope Budgets] LoadPolicy[Load Policy Config] CheckIdem[Check Idempotency] Freeze[Freeze ValidationContext] FetchBudgets --> Freeze LoadPolicy --> Freeze CheckIdem --> Freeze end StageA --> StageB subgraph StageB[Stage B: Policy Evaluation] direction TB CapGate[CapabilityGate] DosGate[DosageGate] ArtGate[ArtifactLintGate] InjGate[InjectionDetectionGate] BashGate[BashBuildGate] CtxGate[ContextIntegrityGate] ConGate[ContradictionGate] IntGate[IntentAlignmentGate] StGate[StateTransitionGate] PerGate[PerceptionAlignmentGate] DriGate[DriftDetectionGate] end StageB -->|All Pass| StageC StageB -->|Any Reject| Reject[PolicyViolation] subgraph StageC[Stage C: Database Commit] direction TB Mutate[Execute Mutation] Outbox[Write Change Record] end StageC --> StageD subgraph StageD[Stage D: Receipt] direction TB Envelope[ReceiptEnvelope] Hash[blake3 Input Hash] EventLink[Event DAG Link] end StageD --> Result[MutationResult T]
Mutation Pipeline
%% Pack compiler pipeline -- TOML manifest + Markdown prompts to CompiledConfig graph TD classDef runtime fill:#7733cc,color:#fff,stroke:#8844dd,stroke-width:2px classDef state fill:#22b37a,color:#fff,stroke:#2ed198,stroke-width:2px classDef event fill:#e85a2a,color:#000,stroke:#cc4418,stroke-width:2px classDef storage fill:#1fad8f,color:#fff,stroke:#26d9b3,stroke-width:2px classDef default fill:#1a1a2e,color:#fff,stroke:#353548,stroke-width:1px Input[PackInput
cstate.toml + Markdown files] Input --> Parse[Parse TOML Manifest
PackManifest] Input --> MD[Parse Markdown Files
Fence Block Extraction] Parse --> IR[Build PackIr
Merge + Validate Cross-refs] MD --> IR IR --> AST[Build CellstateAst] AST --> Compile[PipelineCompiler::compile
CompiledConfig] Compile --> Inject[Inject Pack Metadata] Inject --> Tools[Tool Registry + Toolsets] Inject --> Agents[Agent Bindings] Inject --> Routing[Injection + Routing Config] Inject --> Flows[Compiled Flows
SHA-256 Hashed Steps] Inject --> Hashes[File Hashes
Drift Detection] Inject --> AAIF[AAIF Convergence
A2A / AGENTS.md / llms.txt] Tools --> Output[PackOutput
AST + CompiledConfig] Agents --> Output Routing --> Output Flows --> Output Hashes --> Output AAIF --> Output
Pack Compiler
%% Real-time event flow -- mutation to client notification sequenceDiagram participant Route as Route Handler participant Connector as ProtocolConnector participant Pipeline as MutationPipeline participant DB as PostgreSQL participant Outbox as cellstate_changes participant Relay as Change Relay Job participant Sinks as EventSinkRegistry participant WS as WebSocket Clients Route->>Connector: run_create/update/delete Connector->>WS: Broadcast PipelineStarted Connector->>Pipeline: Execute Mutation Pipeline->>Pipeline: Stage A: Assemble Context Pipeline->>Pipeline: Stage B: Run 11 Gates Pipeline->>DB: Stage C: Commit + Write Outbox Pipeline-->>Connector: Stage D: MutationResult + Receipt Note over Connector,WS: Gate results bundled into PipelineCompleted envelope Connector->>WS: Broadcast PipelineCompleted + Gate Results Relay->>Outbox: Poll for new changes Relay->>Sinks: Fan-out events Sinks->>WS: Tenant-filtered delivery
Real Time Flow