Deploy

SQLite layout on disk

Edit source

SQLite layout on disk

The gateway's state is files. Each app has its own SQLite pair (data.sqlite for handler-owned data, runtime.sqlite for chat + agent run history). Two small gateway-level SQLites sit next to the apps directory: centraid-gateway.sqlite (users + prefs) and centraid-analytics.sqlite (cross-app run rollup). The rest of the gateway-level metadata is plain JSON.

Top-level layout

Code
<gatewayRoot>/├── centraid-gateway.sqlite         ← users, user_prefs├── centraid-analytics.sqlite       ← cross-app run_summary rollup└── <appsDir>/    ├── _registry.json    └── <id>/                       ← one per registered app        ├── data.sqlite             ← app-owned data (persistent across versions)        ├── runtime.sqlite          ← chat sessions, agent run ledger, automation state (persistent across versions)        ├── current.json            ← { activeVersion, history }        ├── versions/        │   ├── v_&lt;UTC ts&gt;_<sha[:6]>/        │   └── v_…/        └── _chat/            ├── w<windowId>.jsonl   ← per-window chat transcript            └── index.json          ← chat mode + adapter session ids

Where <gatewayRoot> is path.dirname(appsDir), and <appsDir> is:

  • Local desktop: ~/.centraid/apps/ (default) — gateway-level dbs at ~/.centraid/.
  • Remote (OpenClaw plugin): $OPENCLAW_STATE_DIR/centraid/apps/ — typically ~/.openclaw/centraid/apps/; gateway-level dbs at ~/.openclaw/centraid/.

TODO(#120) — confirm the local desktop's default. README of openclaw-plugin documents the remote default; the local desktop's is in apps/desktop/src/main/.

_registry.json

The registry of currently-registered app ids. Schema:

json
{  "apps": [    { "id": "hydrate", "registeredAt": "2026-05-08T14:30:00.000Z" },    { "id": "todos", "registeredAt": "2026-05-09T09:12:44.000Z" }  ]}

TODO(#120) — confirm the exact schema. The README documents the existence of _registry.json but not its full shape.

The gateway writes this atomically on register/deregister.

<id>/data.sqlite

The app's handler-owned database. Lives at the app root — not inside a version directory. Code is versioned; data isn't.

  • Persists across activate flips.
  • Persists across version pruning.
  • The only way to "reset" an app's data is to delete this file (and let migrations re-run on next boot).

The reserved-name guards mean data.sqlite is never served as a static file, never uploadable in a tarball, never readable from any HTTP path except through query/action handlers.

<id>/runtime.sqlite

The app's history-of-use database. Also lives at the app root, also persists across version flips. Separate from data.sqlite so handler-owned data and runtime-owned history can evolve independently. Holds:

  • chat_sessions — conversation containers (user, title, mode, adapter session id, turn count, timestamps).
  • runs / run_nodes — unified agent run ledger across kinds (chat turns, automation fires, sub-runs). parent_run_id links sub-runs across apps.
  • automation_state — per-automation KV ((automation_id, key) → JSON).

Centraid never serves this file; only the runtime reads/writes it.

Gateway-level SQLites (<gatewayRoot>/centraid-*.sqlite)

Two small files sit beside <appsDir>/, owned by the runtime, never per-app:

  • centraid-gateway.sqliteusers and user_prefs tables. Schema is multi-user-ready; on the desktop it's single-user today. On the OpenClaw plugin, identity comes from OpenClaw — see below.
  • centraid-analytics.sqlite — a denormalized run_summary table with one row per run (every kind, every app). Each insert into a per-app runtime.sqlite#runs mirrors a summary row here so cross-app audit / activity queries don't have to fan out across every app's runtime db.

<id>/current.json

The atomic pointer to the active version.

json
{  "activeVersion": "v_2026-05-09T09-12-44-000Z_d4e5f6",  "history": [    { "versionId": "v_2026-05-08T14-30-00-000Z_a1b2c3", "activatedAt": "2026-05-08T14:30:01.000Z" },    { "versionId": "v_2026-05-09T09-12-44-000Z_d4e5f6", "activatedAt": "2026-05-09T09:12:45.000Z" }  ]}

TODO(#120) — confirm whether history[] is a flat list of activations (an audit log) or a different shape. The README mentions { activeVersion, history }; the inner shape needs verification.

A POST /centraid/_apps/<id>/activate call rewrites this file. The gateway uses temp-file + rename for atomicity.

<id>/versions/v_<ts>_<sha>/

Each upload extracts to a new directory under versions/. The directory name is v_ + UTC ISO timestamp + first 6 chars of the content sha256.

  • Immutable. The gateway never modifies a version directory after extraction.
  • Code-only. No data.sqlite, no runtime.sqlite, no _chat, nothing app-state. Just what was in the uploaded tarball.
  • Pruned. Old versions above versionRetention (default 5; min 2) are deleted. The active version is always retained.

Identical re-uploads collapse: if the sha matches an existing version, no new directory is created — history[] records the latest timestamp.

<id>/_chat/

Per-window chat state.

Code
_chat/├── w<windowId>.jsonl├── w<otherWindow>.jsonl└── index.json
  • w<windowId>.jsonl — append-only transcript, one event per line.
  • index.json — per-window metadata: chat mode (openclaw / codex / claude), adapter session id, so the next turn can resume the conversation.

TODO(#120) — document the .jsonl event shape. From the README: "SSE stream of normalized events" describes the wire format; the .jsonl transcript almost certainly stores the same events. Confirm against runtime-core/src/chat/.

What's NOT here

  • No per-app user table. Identity lives in the gateway-level centraid-gateway.sqlite#users. On the OpenClaw plugin, the gateway can also import identity from OpenClaw.
  • No cross-app handler database. Each app's data.sqlite and runtime.sqlite are isolated; no handler ever opens another app's files. The gateway-level centraid-analytics.sqlite is a rollup (one summary row per run) — handlers can't write to it.
  • No log files. Handler logs go through the gateway's logger; persistence is the gateway host's concern.

Backing up

The whole <gatewayRoot>/ directory is the backup surface — both gateway-level SQLites plus the apps directory. versions/ can be re-uploaded and _chat/ can be cleared, but the SQLite files and current.json are the only state you can't reconstruct.

sh
tar czf centraid-backup.tar.gz ~/.centraid

TODO(#120) — capture official backup guidance once the runtime stabilizes. For now this is the obvious move.

Resetting an app

sh
# Stop the gateway (close desktop, or stop the OpenClaw process)rm <appsDir>/<id>/data.sqlite     # clear handler-owned datarm <appsDir>/<id>/runtime.sqlite  # clear chat history + run ledger# Start the gateway — migrations re-run on next boot

Where to go next

Was this useful?