Skip to main content
Meerkat is realm-first. A realm_id is the only identity key for sharing or isolating state across surfaces. For task-oriented CLI behavior, filesystem layout, and troubleshooting, see the realm guide.

Core rules

  1. Same realm_id means shared sessions, config, and runtime metadata.
  2. Different realm_id means strict isolation, even from the same folder.
  3. Persistent backends (sqlite or jsonl) are pinned once per realm via realm_manifest.json; memory is an explicit ephemeral backend for single-process runs.
  4. Realm IDs are user-chosen stable identifiers with syntax constraints. Reuse the same value to collaborate across surfaces.
Realm IDs must be 1-64 characters, start with an ASCII alphanumeric character, and may contain only ASCII alphanumerics, _, and -. They cannot contain whitespace or :, and UUID-like opaque values are rejected for explicit user-facing realm IDs.

Surface defaults

This means CLI is workspace-friendly by default, while non-CLI surfaces are isolated by default. The CLI workspace default uses the current directory as context_root, derives identity from that path, and stores realm runtime state under <context-root>/.rkat/realms/ unless --context-root or --state-root is provided.

Realm-id-first root resolution

Every surface resolves the realm identity first, then probes for that specific realm under both candidate state roots — the project-local <context-root>/.rkat/realms and the user-global data-dir root:
  • an explicit --state-root always wins (no probing);
  • a realm that already exists under exactly one candidate root is used where it lies (so an explicit --realm finds its data regardless of which surface materialized it);
  • a realm materialized under both roots is a typed split-brain refusal pointing at rkat storage doctor — no surface will silently pick one and deepen the split;
  • a realm that exists nowhere is created under the surface’s documented default root (CLI: project-local; servers: user-global). First materialization reserves a marker in every candidate root before writing the manifest (the cross-candidate first-start reservation), so two surfaces with different default roots racing the same fresh realm cannot each mint a copy: exactly one manifest materializes, and a racer whose realm appeared under a different candidate refuses typed instead of manufacturing a twin — its next resolution finds the winner’s copy and uses it where it lies.
Server surfaces probe the project-local candidate only when started with an explicit --context-root, which keeps their no-flags behavior unchanged. An existing split heals offline: rkat storage doctor reports the realm’s twin locations, rkat storage migrate (dry-run) computes their per-domain divergence under held fences, and rkat storage migrate --apply --adopt-root <path> adopts one root while archiving the other copy read-only under the registered backup naming — nothing is merged or discarded. See CLI commands.

Explicit sharing

Use the same realm everywhere:
SDKs pass the same value when connecting:

Backend pinning

On first use, a realm writes a manifest with backend choice. Later opens must honor it. After pinning, all surfaces use the manifest backend for that realm. Realms opened through an external RealmStorageProvider pin the provider the same way: the manifest records the provider name (manifest format 2, which also adds ephemeral_domains) and the backend as external:<name>. A realm pinned to an external provider refuses opens through the built-in disk composition — and vice versa — with a typed provider mismatch, and readers refuse manifest formats newer than they support instead of silently ignoring fields they do not understand.

Default backend and same-realm sharing

When SQLite support is compiled in, new persistent realms default to sqlite.
  • sqlite is the recommended backend for the normal Meerkat operating mode where multiple processes share one realm.
  • jsonl remains explicitly selectable for inspectable file-based persistence. Session documents live as JSONL files; runtime authority (queued inputs, run-boundary receipts, ops snapshots) is durable in a runtime.sqlite3 companion under the realm root.
  • memory uses in-process session/blob/artifact stores and is useful for throwaway runs that should not persist conversation state.
Existing realms stay pinned to the backend recorded in their manifest. Changing the default does not migrate old realms.

Config and CAS

Config is realm-scoped and generation-based.
  • config/get returns the core envelope fields config, generation, realm_id, instance_id, and backend.
  • Some surfaces may also include diagnostic fields such as resolved_paths, but those should be treated as optional.
  • config/set and config/patch accept optional expected_generation.
  • Stale writes fail deterministically with generation conflict.
resolved_paths should be treated as a diagnostic/admin field rather than a guaranteed universal field on every public config envelope.

Why this solves multi-surface concurrency

  1. 100 RPC servers from the same folder without realm_id do not collide.
  2. 100 agents across RPC/REST/MCP/CLI with the same realm_id see the same state.
  3. CLI workspace ergonomics remain simple without forcing filesystem identity onto non-CLI surfaces.

See also