C4 Level 3: State and Configuration¶
Status: Current code-derived model
Code baseline: release/v0.13.0 (890e1ad)
Last verified: 2026-07-25
Cognition separates authoritative LangGraph state, durable runtime lifecycle, read projections, versioned artifacts, and hot-reloadable configuration. These logical stores can share one SQLite or PostgreSQL database but have different contracts and consistency rules.
Persistence component diagram¶
C4Component
title Persistence and configuration components
Container_Boundary(server, "Cognition server") {
Component(task_service, "AgentTaskRuntime", "Application service", "Protocol-neutral task and attempt lifecycle")
Component(runtime_projection, "RuntimeProjectionService", "Projector", "Run/task/session summaries and append-only events")
Component(storage_port, "StorageBackend", "Protocol", "Sessions, messages, tasks, runs, events, checkpointer, and Store")
Component(message_projection, "Message projection", "Projection adapter", "REST-oriented messages rebuilt from checkpoints")
Component(config_store, "DefaultConfigStore", "Configuration facade", "Combines native, file, and registry definitions")
Component(config_registry, "ConfigRegistry", "Scoped configuration store", "Providers, tools, skills, Agents, MCP, profiles, defaults")
Component(dispatcher, "ConfigChangeDispatcher", "Invalidation bus", "In-process or PostgreSQL LISTEN/NOTIFY delivery")
Component(artifact_port, "ArtifactStore", "Versioned content store", "Exact-scope artifact CRUD")
Component(schema, "Schema and migration tooling", "SQLAlchemy + Alembic", "Declares Cognition tables and explicit upgrades")
}
ContainerDb(runtime_db, "Cognition tables", "SQLite/PostgreSQL", "Sessions, messages, tasks, runs, events")
ContainerDb(graph_db, "LangGraph tables", "SQLite/PostgreSQL", "Checkpoints, writes, and cross-thread Store")
ContainerDb(config_db, "Configuration tables", "SQLite/PostgreSQL", "config_entities and config_changes")
ContainerDb(artifact_db, "Artifact table", "SQLite/PostgreSQL", "Versioned scoped content")
Container(workspace, "Workspace definitions", "YAML/Markdown/Python", "Bootstrap Agents, tools, skills, middleware, and defaults")
Rel(task_service, storage_port, "Creates and queries lifecycle state")
Rel(task_service, runtime_projection, "Requests transitions")
Rel(runtime_projection, storage_port, "Updates summaries and appends events")
Rel(storage_port, runtime_db, "Reads/writes")
Rel(storage_port, graph_db, "Provides checkpointer and Store")
Rel(message_projection, graph_db, "Reads authoritative messages")
Rel(message_projection, runtime_db, "Rebuilds message rows")
Rel(config_store, config_registry, "Delegates dynamic state")
Rel(config_store, workspace, "Loads native/file definitions")
Rel(config_registry, config_db, "Reads/writes entities and changes")
Rel(config_registry, dispatcher, "Notifies on supported write paths")
Rel(dispatcher, config_store, "Invalidates cached facade entries")
Rel(artifact_port, artifact_db, "Reads/writes versions")
Rel(schema, runtime_db, "Declares/upgrades")
Rel(schema, config_db, "Declares/upgrades")
Rel(schema, artifact_db, "Declares/upgrades")
State authority¶
| Concern | Authoritative record | Derived or supporting record |
|---|---|---|
| Agent conversation and graph state | LangGraph checkpoint keyed by thread_id |
messages REST projection |
| Requested work | runtime_tasks |
Current/last run links and task metadata |
| One execution attempt | session_runs |
Session summary fields |
| Ordered runtime evidence | Append-only session_events |
SSE/A2A transport projections |
| Conversation container | sessions |
Message count, active/latest run, latest event metadata |
| Dynamic definitions | config_entities plus native/file sources |
In-process DefaultConfigStore entries |
| Agent-produced content | Versioned artifacts |
Virtual filesystem routes and task artifact descriptors |
| Cross-thread memory | LangGraph Store | Scope is available through CognitionContext; these components do not enforce a scoped Store wrapper |
The messages table is explicitly rebuildable from checkpoint messages. It is
optimized for pagination, timestamps, threading, tool metadata, token counts,
and model attribution; it is not the graph's source of truth.
Logical data model¶
erDiagram
SESSION ||--o{ MESSAGE : projects
SESSION ||--o{ RUNTIME_TASK : provides_context
SESSION ||--o{ SESSION_RUN : attempts
RUNTIME_TASK ||--o{ SESSION_RUN : correlates
SESSION_RUN ||--o{ SESSION_EVENT : emits
SESSION_RUN ||--o{ ARTIFACT : tags
SESSION ||--|| LANGGRAPH_THREAD : uses
CONFIG_ENTITY ||--o{ CONFIG_CHANGE : records
SESSION {
string id
string thread_id
string agent_name
json scopes
string status
}
RUNTIME_TASK {
string id
string session_id
string agent_name
json effective_scope
string scope_key
string status
}
SESSION_RUN {
string id
string session_id
string task_id
string status
json effective_scope
}
SESSION_EVENT {
string id
string session_id
string run_id
string task_id
int sequence
string event_type
}
CONFIG_ENTITY {
int id
string entity_type
string name
json scope
json definition
string source
}
ARTIFACT {
string id
int version
string artifact_type
json scope
string run_id
}
These are application-level relationships. The Cognition SQLAlchemy schema does not declare foreign keys between these tables. Lifecycle services must therefore maintain referential consistency and cleanup explicitly.
Storage adapters¶
Memory¶
All records, checkpoints, and Store values live in process memory. This is a test and ephemeral-development adapter, not a restart or replica boundary.
SQLite¶
Operational tables use aiosqlite; LangGraph supplies its SQLite checkpointer
and Store. The database file is local to the configured workspace. SQLite is
appropriate for a single process and local durability.
PostgreSQL¶
Operational state uses an asyncpg pool. LangGraph checkpointer and Store,
ConfigRegistry, ArtifactStore, and config notification use separate PostgreSQL
connections or pools. PostgreSQL is the supported shared durable substrate for
multiple server replicas.
Dynamic configuration¶
ConfigRegistry stores providers, tools, skills, Agents, MCP servers, sandbox
profiles, and global defaults as (entity_type, name, scope, definition,
source) rows.
Configuration resolution remains hierarchical for configuration entities:
- A stored scope matches when it is a subset of the request scope.
- The row with the greatest number of scope keys wins.
- Empty scope is the global fallback.
DefaultConfigStore exposes one facade to routes and RuntimeResolver.
Cognition creates no native Agents. Startup bootstrap treats YAML/filesystem
entries as builder definitions and preserves API-managed records where
implemented. API-created Agent CRUD is stricter than general configuration:
Agent rows resolve only at the complete trusted scope, while explicit shared
file Agents may remain read-only fallback definitions.
PostgreSQL writes add a config_changes row and issue NOTIFY. The listener
queries unprocessed rows and invokes subscribers. An in-process dispatcher is
constructed for SQLite and memory, but current registry write paths do not emit
through it. At startup the only registered subscriber is
DefaultConfigStore.on_config_change; compiled graph cache invalidation is not
separately subscribed in the composition root.
Scope semantics in the current code¶
Scope behavior is intentionally split between configuration inheritance and runtime isolation:
- Runtime tasks use a canonical hash plus exact dictionary equality and namespace idempotency by Agent and scope.
- Artifact reads and versions use exact scope.
- Config entities intentionally use subset inheritance.
- API-created Agents use exact scope, revision, and definition digests.
- Sessions, runs, messages, events, tasks, artifacts, cleanup, deletion, and checkpoint access enforce the exact trusted scope at the storage/runtime boundary. Wrong-scope identifiers return not-found.
- Runs persist a redacted manifest and manifest digest so later Agent/config updates affect future runs only.
Schema evolution¶
Alembic migrations exist and are exposed through the server administration CLI.
The FastAPI startup path does not run alembic upgrade; backend
initialize() calls metadata.create_all() and performs selected compatibility
checks. Operators must treat schema upgrade as an explicit deployment step.
Known consistency boundaries¶
- Creating a run, linking its task, updating its session, and appending its first event are separate backend calls rather than one database transaction.
- “One active run per session” is enforced through service checks, not a partial unique database constraint.
- Event sequence uniqueness is constrained by
(session_id, sequence). PostgreSQL serializes allocation with an advisory lock; SQLite does not have an equivalent retry path. - Session cleanup is scoped and deterministic for runtime-owned records, but operators should still treat durable stores and workspace/artifact storage as deployment resources that need backup/retention policy.
- Same-depth matching config scopes have no explicit precedence tie-breaker.
These boundaries belong in architectural review whenever lifecycle, multi-tenancy, deletion, retry, or replica behavior changes.
Code evidence¶
| Responsibility | Primary source |
|---|---|
| Storage protocols | server/app/storage/backend.py |
| Memory/SQLite/PostgreSQL adapters | server/app/storage/memory.py; sqlite.py; postgres.py |
| Table declarations | server/app/storage/schema.py |
| Config facade and precedence | server/app/storage/config_store.py |
| Config inheritance and adapters | server/app/storage/config_registry.py |
| Config notification | server/app/storage/config_dispatcher.py |
| Artifacts | server/app/storage/artifact_store.py; config_models.py |
| Message rebuild | server/app/storage/message_projection.py; deep_agent_service.py |
| Runtime lifecycle | server/app/agent/task_runtime.py; server/app/runtime_projection.py |
| Migrations | server/alembic/versions/; server/app/storage/migrations.py; server/app/cli.py |