Storage
The storage namespace configures three independent backends — a database, a
cache, and an object store — plus a retention policy. These back the
observability dashboard, the model metadata catalog, and reflection memory. The
database is created automatically when admin auth is configured, when
llm.model_catalog uses the SQLite backend, when reflection memory is
enabled, or when the database configuration source is enabled
(config_sources.database.enabled: true).
storage: database: kind: sqlite sqlite: path: /app/data/aicr.sqlite cache: kind: memory # memory | redis | none object: kind: filesystem # filesystem | s3 filesystem: root: /app/data/objects retention: deleted_project_grace_days: 30 recent_runs: max_count: 2000 max_age_months: 6 events: max_count: 2000 max_age_months: 6 queue: max_count: 1000 max_age_months: 6storage.database
Section titled “storage.database”| Field | Type | Default | Description |
|---|---|---|---|
kind |
enum | sqlite |
sqlite or postgres. |
sqlite.path |
string | /app/data/aicr.sqlite |
SQLite database file path. |
postgres.url_env |
string | – | Name of the env var holding the Postgres connection URL. |
migrate |
enum | auto |
Startup schema mode: auto applies pending steps; verify refuses a missing, pending, drifted or newer schema without applying migrations. |
The SQLite database is created automatically when needed. It stores the
observability stats, the keyed model_catalog table (when the catalog uses
the SQLite backend), reflection memory entries, deferred reviews, received
webhook events, and the dynamic-config revision/session tables. The Postgres
backend serves the same contracts through a dedicated schema. Schema upgrades
use schema_migrations for config and PostgreSQL business tables; SQLite
business tables retain their historical name-only _migrations ledger.
aicr migrate --status|--check|--apply inspects or upgrades both config and
store namespaces on SQLite and PostgreSQL without starting the server (see
CLI Reference).
Keep SQLite WAL databases on a local filesystem shared by processes on the same host. WAL does not support network filesystems; use the Linux filesystem for WSL database tests rather than a Windows-drive mount. See the SQLite WAL requirements.
Upgrade compatibility
Section titled “Upgrade compatibility”Before the first upgrade, stop ingress and claims on every old instance, drain
accepted work, confirm the old processes have exited, and back up the database.
A migration lock does not detect an idle old process. The tested historical
baseline is commit c5d221c: SQLite/PostgreSQL require that stop-and-upgrade
sequence, and the old program refuses to reopen schema 2. Redis keeps its
existing keys and JSON shape; both versions can read/write v1/v2 documents with
CAS conflict protection. Other version pairs need their own compatibility test.
The SQL ledger records minimum reader/writer protocols and transaction mode.
Both protocols are currently 1; historical rows without those fields mean 1.
Only atomic SQL migrations are supported. PostgreSQL migration lock waits are
bounded to 5 seconds, and migration statements to 30 seconds. migrate --status
reports compatibility; --check/--apply reject incompatible requirements.
Database document/effective-config readers and writers accept formats 1–2;
raw configuration files continue to use format 1.
storage.cache
Section titled “storage.cache”| Field | Type | Default | Description |
|---|---|---|---|
kind |
enum | memory |
memory, redis, or none. |
redis.url_env |
string | – | Name of the env var holding the Redis URL. Required when kind: redis. |
ttl_seconds |
int > 0 | – | Optional default cache TTL. |
Redis is shared with the model catalog Redis backend
(llm.model_catalog.cache.backend: redis). When the catalog uses Redis,
storage.cache.kind must be redis and redis.url_env must resolve,
otherwise the config is rejected at load time. See
LLM Providers and Models for the catalog side.
The configuration source’s Redis backend (config_sources.database.backend: redis) also reuses this connection declaration: it requires
storage.cache.kind: redis and a resolvable redis.url_env, otherwise the
config is rejected at load time.
storage.object
Section titled “storage.object”| Field | Type | Default | Description |
|---|---|---|---|
kind |
enum | filesystem |
filesystem (available) or s3 (reserved). |
filesystem.root |
string | /app/data/objects |
Local directory root. |
s3.endpoint_url_env |
string | – | Name of the env var holding the S3 endpoint URL. |
s3.bucket |
string | – | Bucket name. |
s3.region_env |
string | – | Name of the env var holding the region. |
s3.access_key_id_env |
string | – | Name of the env var holding the access key id. |
s3.secret_access_key_env |
string | – | Name of the env var holding the secret access key. |
s3.force_path_style |
bool | – | Use path-style addressing (MinIO / RustFS / many S3-compatible endpoints). |
S3 fields are reserved — they validate but are not yet used by runtime features.
storage.retention
Section titled “storage.retention”| Field | Type | Default | Description |
|---|---|---|---|
deleted_project_grace_days |
int ≥ 0 | 30 |
Hard-delete soft-deleted projects after this many days. |
recent_runs.max_count |
int 1–1000000 | 2000 |
Most recent run details to retain. |
recent_runs.max_age_months |
int 1–1200 | 6 |
Maximum age of run details, measured from start time. |
events.max_count |
int 1–1000000 | 2000 |
Most recent received events to retain. |
events.max_age_months |
int 1–1200 | 6 |
Maximum age of events, measured from receipt time. |
queue.max_count |
int 1–1000000 | 1000 |
Most recent terminal automatic-commit batches to retain. |
queue.max_age_months |
int 1–1200 | 6 |
Maximum age of terminal batch history, measured from creation time. |
History expires when either limit is exceeded. Months are calendar months in UTC, with the day clamped to the last day of the target month. Counts apply to the whole store across workspaces. Pending, running and retrying batches, and batches still referenced by an active stream, are protected from history cleanup.
These six fields support YAML and database configuration. Database values take precedence, including when YAML explicitly sets a value. In Admin → Config → Advanced, edit the Storage fields or use Reset database overrides to fall back to the YAML value, then the default when YAML omits it. Published changes apply to subsequent history reads and cleanup; increasing a limit cannot restore details that have already been removed. Storage connection settings remain file-only bootstrap settings.
Recent Runs cleanup erases per-run display details while retaining accounting facts, numeric usage and run IDs. Overview, Projects, Providers, daily rollups and checkpoint deduplication continue to include older runs. Events are deleted; Queue cleanup removes terminal batch details and outbox records but preserves receipt and member identities needed to prevent duplicate analysis. This policy does not remove run-directory artifacts or all accounting rows from the database.
Startup, a 60-second maintenance timer and history reads perform bounded cleanup (up to 500 records per category per sweep). Reads enforce the configured limits even while an older cleanup backlog remains. The three panels fetch 20 rows per page from server-side indexed queries, so retaining the default 2000/2000/1000 records does not load the full history into the browser. SQL uses time/ID indexes; Redis uses status sorted sets, backfilled on first startup after upgrade.
Backends at a glance
Section titled “Backends at a glance”| Backend | Wired in | Reserved | Notes |
|---|---|---|---|
| database | sqlite, postgres |
— | SQLite at /app/data/aicr.sqlite by default; Postgres via postgres.url_env. |
| cache | memory, redis, none |
— | Redis reused by the model catalog Redis backend. |
| object | filesystem |
s3 |
Filesystem at /app/data/objects by default. |