Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Runtime state and persistence

ZeroClaw has one install root, but not one monolithic “workspace database”. Different state surfaces have different owners, reload behavior, and durability. Use this map when a change adds state, moves state, caches config, touches reload, or changes session/memory/log/cost behavior.

The single-source-of-truth rule still applies: if a fact already lives in one surface, do not copy it into another stored field. Store new state only when this table identifies the owning surface, or resolve it from the canonical owner at use time.

Install layout

For a normal install, <install> is the resolved config directory (~/.zeroclaw/ by default, Homebrew and explicit --config-dir installs can move it). The current layout is:

<install>/
├── config.toml                 # canonical user config
├── .secret_key                 # key for encrypted secrets
├── data/                       # instance-wide runtime data
│   ├── sessions/
│   │   ├── sessions.db         # default chat/session backend
│   │   └── acp-sessions.db     # ACP protocol sessions
│   ├── cron/jobs.db            # scheduled job state
│   ├── sop/runs.db             # optional durable SOP run state
│   ├── control_plane.db        # task supervision records
│   ├── state/
│   │   ├── runtime-trace.jsonl # persisted logs
│   │   └── costs.jsonl         # cost ledger
│   ├── devices.db              # paired-device metadata
│   └── memory/                 # shared instance memory stores
├── shared/                     # shared resources, such as skill bundles
└── agents/<alias>/workspace/   # per-agent filesystem sandbox and identity

The legacy <install>/workspace/ name is still accepted during migration, but new runtime state should be described in terms of <install>/data/, <install>/shared/, and per-agent workspaces.

State map

SurfaceCanonical sourceDurable pathIn-memory ownerReload / concurrency boundaryNotes
Config valueszeroclaw-config::Config loaded from config.toml<install>/config.tomldaemon Arc<RwLock<Config>> plus per-subsystem resolved views/admin/reload re-reads config and re-instantiates daemon subsystems; direct config writes use schema validation and dirty-path checks. RPC-side config mutations additionally serialize their whole read-mutate-flush section on RpcContext::config_write_lock (tokio mutex first, parking_lot RwLock second, and never a parking_lot guard held across an .await or lock().await); gateway HTTP config mutations likewise serialize their whole read-mutate-swap section on AppState::config_write_lock (same lock order)Do not cache config-derived facts in long-lived structs unless the cache is explicitly rebuilt on reload.
Config save durabilitysave() / save_dirty() atomic-write path in zeroclaw-config<install>/config.toml plus retained config.toml.baksame as config valuesWrites go through a temp file, pre-replacement directory sync, atomic rename, then a post-replacement directory syncOk(()) means the replacement is visible, not that rename durability is proven: a pre-replacement failure aborts with disk and live config unchanged, but a post-rename directory-sync failure still returns Ok(()) with a warning logged and config.toml.bak retained. After a crash immediately following such a save, the directory entry may replay to the prior file; callers must not treat Ok as a stronger durability guarantee, and recovery can consult the retained .bak.
Encrypted secretsConfig secret fields plus .secret_key<install>/config.toml, <install>/.secret_keysecret-store helpers in zeroclaw-configReload observes changed config; losing .secret_key makes encrypted config secrets unrecoverableNever copy decrypted values into logs, docs, PR bodies, or runtime metadata.
Agent filesystem identityPer-agent workspace files<install>/agents/<alias>/workspace/effective SecurityPolicy and agent prompt constructionCreated lazily when the agent starts; workspace access is evaluated from configThis is the filesystem sandbox, not the config source of truth for providers/channels/tools.
Shared skill bundlesConfigured skill bundle entries and resolved bundle dirs<install>/shared/skills/<bundle>/ by defaultskill loading / prompt enrichmentReload and new agent starts observe config and filesystem changesBundle aliases and directory resolution come from config; the files are the bundle content.
Conversation memoryzeroclaw-memory backend selected per agentSQLite/Postgres/Lucid/Qdrant/Markdown backend locations; SQLite shared store lives under data/memory/Arc<dyn Memory> wrapped in agent-scoping adaptersBackend choice is locked once an agent has written data; same-backend cross-agent recall is opt-inMemory rows are agent-scoped. Do not replace memory ownership with copied prompt/session caches.
Chat and channel sessions[channels].session_backend plus SessionBackendDefault data/sessions/sessions.db; legacy/explicit JSONL uses data/sessions/*.jsonlzeroclaw-infra backend handles are currently constructed independently by channels, gateway, RPC, and session toolsSQLite backend uses WAL; SessionActorQueue serializes active turns per session; JSONL mutations share a process-local sessions-directory lockChat/Code sessions use the unified backend contract. ACP protocol sessions use a separate store. Process-level backend ownership is not yet single-sourced.
ACP sessionsACP protocol session storedata/sessions/acp-sessions.dbAcpSessionStore opened at daemon boot and in RPC contextWAL-backed SQLite store, separate from chat sessionsACP session/load and session/resume operate on this protocol store, not the chat session backend.
Live RPC/TUI sessionsRPC SessionStorenone by itselfcrates/zeroclaw-runtime/src/rpc/session.rs in-memory mapProcess-local; session history persists only through the chat or ACP backendLive session handles, uploads, cancel tokens, owners, and overrides are runtime state.
Cron jobsDeclarative config membership plus cron SQLite storedata/cron/jobs.dbzeroclaw-runtime::cron scheduler/storeRead paths do not create jobs.db; scheduler owns due/lock stateDeclarative jobs are reconciled from config, while run metadata and locks live in the cron DB.
SOP runsSopEngine plus SopRunStoreNone by default; data/sop/runs.db when durable SQLite initialization succeedsSOP engine active/finished run cachesThe durable store owns admission claims and persisted revisions; the engine restores active and terminal state on startupStore initialization failure logs a warning and falls back to memory. Memory-backed audit records are not the run-lifecycle source of truth.
Background task supervisionDurable task control planedata/control_plane.dbcontrol-plane handle, task producers, and reaperOwner PID/boot ID identifies prior-boot orphans; heartbeat timeout applies only to producers that emit heartbeatsCurrent delegate/subagent producers register best-effort rows but leave heartbeat, parent, route, and principal fields absent. Goal APIs exist, but end-to-end goal execution is not yet wired.
Background delegate resultsDelegate result record<workspace>/delegate_results/<task-id>.jsondelegate tool cancellation registry and running futureResult files survive restart; live cancellation handles do notReads are file-first and overlay only lost or timed_out supervision when the file still says running; result and control-plane writes are independent and can diverge.
Runtime logszeroclaw-log event schema and subscriber layerdata/state/runtime-trace.jsonl when persistence is enabledbroadcast hook, JSONL writer, /api/logs reader, Observer bridgeRolling/full/none persistence is config-controlled; dashboard SSE receives events even when JSONL is disabledLogs are evidence and observability, not the source of user config or session state.
Cost ledgerCostTracker plus rate configdata/state/costs.jsonlprocess-global CostTrackerReload hot-swaps CostConfig; the tracker is constructed on demand if cost tracking becomes enabledExisting records keep their recorded price; rate edits affect future requests after reload.
Gateway pairing tokensPairingGuard from gateway.paired_tokenstoken hashes in configpairing guardReload reconstructs the guard from configValid bearer tokens are config state, not devices.db rows.
Paired device metadataDevice registry rows keyed by token hashdata/devices.dbDeviceRegistry cache plus SQLiteRegistry reconciles metadata against the canonical paired-token setThis DB makes paired devices visible/manageable; it does not invent valid tokens.
Health and component statusrunning subsystems report component statenonegateway health/status stateProcess-local; reset/rebuilt on daemon restart or reload/health, /api/health, and /api/status are current observations, not durable configuration.
Queues, debouncers, watchdogszeroclaw-infra process utilitiesnone unless a caller stores results elsewherein-memory queues/debouncers/watchdogsProcess-local; used to serialize, coalesce, or detect stallsTreat these as coordination state. Persist only the domain data they protect, not the queue itself.

Reload and restart

POST /admin/reload sends an in-process reload signal to the daemon. The outer daemon loop re-reads config from disk and re-runs the daemon, creating fresh gateway, channel, heartbeat, scheduler, MQTT, session, memory, and cost wiring from the new config. The PID stays the same, but listeners briefly rebind.

A full process restart also rotates process-local state such as live RPC sessions, health snapshots, actor queues, and any ephemeral tool-receipt key. Durable stores survive restart according to the table above.

Session backend migration

Selecting the SQLite session backend imports legacy data/sessions/*.jsonl files when a backend handle is constructed. The importer moves each source to a private .jsonl.importing generation while holding the process-local JSONL mutation lock, writes the messages, metadata, and a source-bound import receipt in one SQLite transaction, then retains the source as .jsonl.migrated for rollback.

The receipt binds the source filename, session key, SHA-256 digest, and byte length. Before the receipt transaction begins, the importer syncs the staged source file and, on Unix, the live-to-staged directory rename. Migration transactions use full SQLite synchronization for the import commit, then restore the normal runtime setting. Archive handoff likewise syncs directory metadata on Unix before removing the staged source. Backend construction restores the process-local inactive state from durable receipts before scanning source files. As soon as an import receipt is committed, JSONL mutations for that sessions directory remain inactive even if archive handoff or a later file fails. The next construction can verify the staged source against that receipt and finish the handoff without inserting duplicate messages. Empty and whitespace-only JSONL files are retained as zero-message SQLite sessions; a non-empty source with no valid messages still fails closed.

Constructing the SQLite backend without importing a source does not deactivate JSONL mutations. An in-process reload can therefore switch back to JSONL when no durable import receipt exists.

A receipt-less source is not merged over existing SQLite messages or metadata for the same session key. If that pre-commit check fails, the staged source is restored to its live JSONL path. An incompatible receipt, staged source, or archive causes backend construction to return an error. Each process entry point currently decides independently whether that error stops the subsystem or disables persistence; process-level ownership and startup policy are separate from the migration contract.

Backup and restore

For a normal single-instance install, back up the whole <install> directory. At minimum, include:

  • config.toml
  • .secret_key if encrypted secrets are used
  • data/memory/
  • data/sessions/
  • data/cron/jobs.db if cron jobs are configured through runtime surfaces
  • data/sop/runs.db if durable SOP runs are enabled
  • data/control_plane.db if supervised task history matters
  • data/state/costs.jsonl if cost history matters
  • data/state/runtime-trace.jsonl if logs are needed for incident review
  • data/devices.db for paired-device metadata

Do not run two daemons against the same install root. Several stores use SQLite with a single-writer model, and the process-local caches assume one daemon owns the instance.

Source pointers

  • Config, install-root, and data-dir resolution: crates/zeroclaw-config/src/schema.rs
  • Session backends: crates/zeroclaw-infra/src/session_sqlite.rs, crates/zeroclaw-infra/src/session_store.rs
  • ACP session store: crates/zeroclaw-infra/src/acp_session_store.rs
  • RPC live sessions: crates/zeroclaw-runtime/src/rpc/session.rs
  • Cron persistence: crates/zeroclaw-runtime/src/cron/store.rs
  • SOP persistence: crates/zeroclaw-runtime/src/sop/store/
  • Background task and goal supervision: crates/zeroclaw-runtime/src/control_plane/
  • Background delegation results: crates/zeroclaw-runtime/src/tools/delegate.rs
  • Logs: crates/zeroclaw-log/
  • Cost ledger: crates/zeroclaw-config/src/cost/tracker.rs
  • Pairing guard: crates/zeroclaw-config/src/pairing.rs
  • Device registry: crates/zeroclaw-gateway/src/api_pairing.rs
  • Reload endpoint: crates/zeroclaw-gateway/src/lib.rs