Ownership model
Meerkat has one canonical semantic path:- the runtime control plane (
MeerkatMachineinmeerkat-runtime) ownskeep_alive, Queue/Steer routing, comms drain lifecycle, external-event admission, and request/turn commit semantics; runtime-backed surfaces reach those semantics only by lowering into it SessionServiceis the substrate lifecycle seam used by those surfacesEphemeralSessionService/build_ephemeral_serviceremain valid for testing, embedded use, and WASM internals, but they are not the primary product path and do not own runtime semantics
- runtime-backed surfaces should call
MeerkatMachine::prepare_bindings(session_id) - those bindings flow into
SessionBuildOptions.runtime_build_mode = RuntimeBuildMode::SessionOwned(bindings) - standalone / embedded / test-only builds should opt into
RuntimeBuildMode::StandaloneEphemeralexplicitly instead of relying on silent fallback
CloseStandaloneTurn releases the epochless, unplaced session’s
lifecycle while preserving its terminal evidence and model/tool-routing state.
It does not mint a durable commit receipt or close a runtime-owned session;
SessionOwned terminal publication remains with the runtime commit owner.
SessionRuntimeBindings is the epoch-local runtime handle for a session. It carries:
session_idepoch_id- the canonical
OpsLifecycleRegistry - shared consumer cursor state used for recovery-safe completion visibility
PersistentRuntimeDriver::recover()owns input/runtime/control recoveryMeerkatMachineowns session entry recovery (ops_lifecycle,epoch_id, cursor state)
- Queue-only turns
- no runtime-owned
keep_alive - no Steer/render-metadata semantics
- no runtime ingress/admission ownership
Agent construction contract
Agent construction is centralized inAgentFactory::build_agent().
- Surfaces pass per-request build data in-band via
CreateSessionRequest.build(SessionBuildOptions). - No out-of-band staging lock is used.
FactoryAgentBuildermapsSessionBuildOptionstoAgentBuildConfig.- For runtime-backed builds,
SessionBuildOptions.runtime_build_modeshould carryRuntimeBuildMode::SessionOwned(bindings)fromprepare_bindings(). - For standalone/testing/embedded builds, prefer
RuntimeBuildMode::StandaloneEphemeralexplicitly. - Session metadata persists realm context and durable session identity:
realm_idinstance_idbackendconfig_generation- durable LLM identity
keep_alive- visible comms identity metadata such as
comms_nameandpeer_meta
Session lifecycle and turn semantics
create_session
create_session(req) builds the agent and optionally runs the first turn.
- Returns
RunResultwithsession_id. InitialTurnPolicy::RunImmediatelyexecutes the first turn inline (default).InitialTurnPolicy::Deferregisters the session without running a turn.
- pre-commit failure: side-effect free; no committed session identity
- post-commit first-turn failure: return session identity and keep the session resumable
start_turn
start_turn(id, req) executes a new turn on an existing session.
- At most one in-flight turn per session.
- Concurrent attempts return
SESSION_BUSY. - Committed success must not be rewritten to cancellation.
- Runtime-backed surfaces may hot-swap supported live settings on an existing session where the surface contract says that is allowed.
Ordered System messages
system_prompt on StartTurnRequest appends one ordinary, unkeyed System
message at that turn boundary. System messages are repeatable and may appear
anywhere in the transcript; an append never replaces row zero or rewrites an
earlier message.
Keyed system-prompt updates instead append rows carrying
SystemPromptVersionIdentity: a SystemPromptKey and SystemPromptVersion.
The active model view selects the latest version per key; unkeyed rows are
never superseded. Older versions remain historical rows reachable through
retained transcript revisions, not a promise to retain every version in the
active model view.
There is no host-configuration injection ledger. Materialization and resume
never infer a System message from current host configuration. A host that
wants to add an instruction supplies it through the admitted update path,
where it becomes a new ordered System row rather than an overwrite.
Compaction retains active keyed and unkeyed System rows verbatim in relative
order, while discarding superseded keyed versions from the compacted active
head. This does not rewrite their retained revision history.
Provider adapters lower the active instruction view without rewriting durable
history. Wires accepting ordered role=system messages normally preserve their
positions. Anthropic’s model-gated lowering can instead place turn-scoped
instructions after their governing User and before the Assistant, subject to
its legal predecessor constraints; this is a wire-only placement adjustment
(see System-message projection).
Providers with only a top-level instruction field reject later System rows
with a typed projection error; unsupported placements on other wires also fail
typed rather than silently hoisting or merging canonical rows. Provider
capability never changes, rejects, or rewrites the durable Session itself.
interrupt
interrupt(id) cancels an in-flight turn.
- If no turn is running:
SESSION_NOT_RUNNING.
Cancelling is not terminal completion.
For surfaces using SurfaceRequestExecutor, upgrading a cancellation action
during initialization is serialized with request cancellation. Cancellation
either receives the new action or the installer replays it for an already
cancelled request. Actions run outside the owner locks and may re-enter the
executor; this does not promise rollback of effects already executed.
read and list
read(id)andlist(query)are non-blocking with respect to in-flight turns.- Persistent services can include durable sessions from the realm backend.
- Presence in
list()is not the same as “live session exists”; runtime-owned seams must answer liveness.
read_history
read_history(id, query) returns the last committed transcript snapshot for a session.
- Messages are returned oldest-to-newest.
offsetandlimitapply from the start of the full transcript.- Active sessions do not expose in-flight partial output through history reads.
- Archived sessions remain readable on persistent backends; the ephemeral service rejects archived history reads with
SESSION_PERSISTENCE_DISABLED.
archive
- The canonical
SessionDocumentMachineowns thesession_lifecycle_terminalfact for ALL profiles; shells realize its verdict, they never decide it. - Realization order is fail-closed: durable document commit first, runtime retire second.
RuntimeState::Retiredis the runtime realization of the same verdict, so the resurrection window is unrepresentable. - If runtime retirement fails after the document commits
Archived, the durable terminal remains authoritative: reads, resumes, and turns stay rejected while a repeated archive command follows the generated convergence transition and retires the residual runtime. - Archived sessions are excluded from
list()and rejected for further reads/turns (SESSION_NOT_FOUNDon persistent services; the ephemeral service serves a final in-memory view). - Committed transcript history remains readable via
read_historyon persistent backends.
Keep-alive contract
keep_alive is a runtime/session concept, not an old “host mode” execution path.
Rules:
- create / run omitted => default
false - continue / resume omitted => inherit persisted session intent
- explicit
keep_aliveoverride is a session/runtime mutation once validated - invalid
keep_alive+ comms configuration is rejected before any stateful work - once validated, an explicit
keep_alivemutation may commit independently of turn success
keep_alive=true requires usable comms identity/config on the surfaces that expose it.
External events
External events are queue-only runtime-backed inputs.- they are admitted into runtime ingress
- they do not invent a second direct execution path
- “turn-boundary inbox draining” is not the primary mental model for the current runtime-backed design
Event delivery and durable projection
Live delivery and durable evidence use separate lanes:- Ordinary session subscribers read a 256-entry broadcast. If a subscriber
falls behind, it receives a synthetic
StreamTruncatedevent withreason.kind = "stream_lagged"and the exactreason.droppedcount, then continues with retained events. The marker reports a gap in that subscriber’s live view; it is not a canonical durable-log event. - A persistent session whose host installs event projection has one internal dedicated projector stream backed by an unbounded MPSC queue. Built-in realm-backed persistence installs it; custom hosts must call the projection composition seam explicitly. Event publication shares the same envelope with the live lane, but projector latency does not block live subscribers or depend on broadcast retention. The runtime emits a rate-limited degraded warning when the queue reaches 1,024 entries and records its high-water mark.
- The projector asynchronously appends each envelope to the durable event audit
log before updating derived
.rkat/materialized files. UI-ring lag does not drop projector input, but EventStore projection is still best-effort derived state. TheRuntimeStore/backend carrier remains session authority;SessionStorerows and EventStore are component/content and projection seams.
EventProjectionHalted fault, records a durable projection-halt marker when
possible, stops before appending later events across the sequence hole, and
makes replay and later resume fail closed. It does not roll back or fail the
already-committed turn. Clients that see live
stream_truncated reconcile from events/list_since, a current snapshot, or
ATIF export.
Explicit override semantics
Where the surface supports omission vs explicit override, these are distinct facts:- omit / inherit
- disable / false
- set / concrete value
SystemPromptOverride (Inherit / Set / Disable) is the canonical wire+persisted carrier for the per-request system-prompt fact, and per-turn provider parameters travel as the typed ProviderParamsOverride, not a raw JSON bag.
Realm contract
Sessions are realm-scoped.- Same
realm_idplus the same physical provider/root: shared visibility and config context. - Different
realm_id: strict isolation. - Backend is pinned per realm via
realm_manifest.json(manifest format 2 addsproviderandephemeral_domains; readers refuse formats newer than they support instead of silently ignoring fields, and realms pinned to an external storage provider refuse built-in disk opens typed). - State roots resolve realm-id-first across the project-local and
user-global candidates; a realm materialized under both is a typed
split-brain refusal (see
rkat storage doctor). - First materialization of a fresh realm reserves across the candidate set resolved by that invocation before writing the manifest. Processes using the same multi-root set converge; surfaces probing different sets do not. Use one explicit root or matching server context for cross-surface starts.
- A fresh prompt-first CLI run or
rkat helpwhose workspace-derived realm cannot open may isolate storage into a newly generated durable SQLite realm under the same resolved state root. No historical session from the failed realm is loaded into the fresh run, and the compatibility bridge is never invoked automatically. An ordinary supported initialization or migration may have completed before a later store refused. The original workspace config and auth/tool policy still govern the run. Explicit realms, isolated runs, resumes, and session commands never take this fallback. Historical session access requires the explicit fenced pre-0.8.10 bridge; there is no in-memory compatibility fallback.
Mob-member role continuity
A durable mob member normally resumes only when its stored and requested role match. Mob ID, memberAgentIdentity, bridge session_id, and transcript are
fixed identity facts; a profile edit alone cannot reinterpret the stored
member as a different role.
Trusted in-process hosts have one explicit migration seam. An exact
MemberLaunchMode::Resume may carry resume_from_role naming the stored
predecessor role for that request. Admission proves that the stored and current
comms_name and MobMemberBinding refer to the same mob and member. An absent
declaration returns MemberRoleMigrationRequired; the wrong predecessor or a
contradictory identity returns MemberRoleMigrationRejected.
Role migration is cold and one-shot. The exact session must have no live actor,
the declaration is consumed rather than persisted as standing permission, and
successful materialization restamps the current role into durable comms
metadata while preserving the same session and transcript. There is no
implicit rollback; a later role change requires a new exact cold migration.
This seam is deliberately private. Public CLI, REST, JSON-RPC, MCP, generated
SDK spawn requests, helpers, and standing profiles cannot set
resume_from_role. The private member-host materialization protocol carries it
only as part of the trusted remote-host resume path.
Compaction contract
Compaction is optional. Ordinary recoverable failures preserve uncompacted history and normally reportCompactionFailed, allowing the run to continue.
- Triggered by token thresholds and turn guards.
- A typed provider
PolicyStopfrom the summary call terminates the run. - Unresolved durable compaction-stage authority or cleanup errors, such as
SessionDurableProjectionAuthorityUnknown, also terminate fail-closed. - Preserving history does not guarantee the next request fits the provider’s capacity; an unrepaired oversized request can still fail.
- Compaction usage counts toward run budgets.
EventStore audit append failure does
not undo an already-committed turn; that guarantee does not permit ignoring
uncertain compaction-stage authority.
Durability
Ephemeral mode
No durability across process restart.Persistent mode
Durability follows the selected realm storage provider and its declarations for the seven owned domains. The built-in persistent realm backends aresqlite and jsonl; an external RealmStorageProvider may supply other
persistent stores, but it must declare every domain and may not silently
substitute memory for one declared durable. Both built-in persistent backends
mount a SQLite runtime companion for runtime authority:
sqlite realms keep the runtime_* tables inside sessions.sqlite3, and
jsonl realms carry a dedicated runtime.sqlite3 alongside the JSONL session
documents. Queued inputs, run-boundary receipts, ops snapshots, and
auth-lease/OAuth-flow authority are durable under both backends.
The memory backend is explicit ephemeral storage and does not survive process
restart.
- Completed turns are persisted.
- Crash during an in-flight turn loses at most the work the intra-turn persistence hook had not yet written; any durable tail it did write is preserved and recovered (see below), never discarded.
- SQLite-backed realms are the default persistent mode and support normal same-realm multi-process workflows.
Domain document and store authority
The current Session envelope is version 3 and contains domain state only. It does not authenticate itself and does not carry a persistence token. A deserializedSession cannot authorize a write, migration, or recovery
operation.
The selected store profile issues the physical authority:
- WholeBlob —
session_id, monotonic store revision, and SHA-256 of the exact serialized row. - HeadCanonical —
session_id, monotonic store revision, the committed boundary head, and its exact head token. The boundary head binds the message-row, rewrite, graph, component, and metadata prefixes.
rkat ... storage migrate --apply --bridge-pre-0-8-10.
JSONL and memory realms are rejected before any database is mutated. This is a
frozen, fail-closed pre-floor importer: under the realm maintenance fence it
authenticates supported historical schema before installing current
representation and authority. Ordinary store opens and v3 reads remain strict
and never enter the bridge. Catalog authentication is per domain and row
admission is per record: a row the current typed contract cannot represent
without loss is left byte-identical on disk and reported, rather than costing
the realm every other row. The maintenance transaction preserves input rows -
including their ingress payloads, whatever lifecycle state they reached -
without scheduling or replaying them. A later session activation follows normal
recovery, which applies this binary’s ordinary terminal-payload retirement to
rows it writes itself.
Durable-tail recovery
The intra-turn persistence hook writes a provisional physical successor outside the runtime boundary transaction. Every successful write returns an explicitRunCheckpointReceipt. Receipts are scoped to one session, committed
base, and run; candidate_sequence starts at one and advances contiguously.
An exact retry may return the same receipt, while the next candidate must
present and succeed the preceding receipt. A stale, divergent, or
out-of-sequence candidate fails closed.
WholeBlob encodes, hashes, and writes each candidate once. HeadCanonical
applies one sealed prepared delta and returns the exact resulting physical
revision and head token. The actor retains only the latest successful receipt;
an uncertain outcome or failed acknowledgement terminates the actor and
requires durable reload. At the final boundary the store promotes that exact
candidate. It does not construct a second document or replay the delta.
A crash or shutdown race can still leave a durable transcript tail — up to and
including a fully completed turn — whose runtime boundary commit never landed.
Durable is not the same as runtime-committed, and neither view is silently
preferred: a cold read drives a typed, machine-owned read-source decision
(serve the committed runtime snapshot, serve the committed store head,
recovery required, or quarantine) instead of a shell comparison.
The recovery rule is never-discard: every store-proven durable descendant of
the committed authority is preserved.
- A completed tail (single run,
EndTurnterminal, structurally coherent) is committed as a recovered run boundary in one atomic store transaction: recovered snapshot, boundary receipt, and input terminalization land together, so exactly-once holds and the delivery layer never re-runs a recovered turn. - An interrupted tail is closed as interrupted: content preserved, a typed recovery notice appended, the original run terminalized — never requeued.
- Anything else — including any tail carrying a dangling
tool_use— is held intact for reconciliation. A dangling call proves intent, not execution; its external side effect may already have fired, so recovery never closes it with synthetic results.
SESSION_DURABLE_TAIL_HELD_FOR_RECOVERY— the durable tail exists but machine-authorized recovery has not promoted it yet; it awaits reconciliation.SESSION_DURABLE_TAIL_RECOVERY_REFUSED— machine-authorized recovery was refused by conflicting persisted runtime facts (another live runtime, or boundary receipts that already cover or contradict the tail); retry after the conflicting runtime quiesces.SESSION_DURABLE_EVIDENCE_QUARANTINED— the durable evidence is forked or unverifiable; the session is quarantined from resume.
durable_resume_hold structured payload so callers
can distinguish the hold class without parsing messages. Ownership of the
pipeline (SessionDocumentMachine classifies, MeerkatMachine authorizes,
RuntimeStore realizes) is documented in
Machine Authority.
Storage mechanics (schema ledger, fence, durability classes)
Every SQLite file in a realm carries ameerkat_schema(domain, version)
migration ledger; stores bring their domain up to date at open under a
pinned concurrent-open protocol, and a file whose ledger is ahead of the
binary refuses typed (SchemaFromTheFuture) — a rollback candidate fails
certification cleanly instead of crash-looping. Each database also has a
sibling <file>.mfence lock file: store operations hold a shared
per-operation guard, and offline maintenance (rkat storage migrate) takes
the exclusive side after in-flight operations drain. Backup artifacts
written by maintenance use the registered *.pre-<version>-<timestamp>
naming that rkat storage doctor inventories and rkat storage prune
owns.
Every store slot a realm composes carries a machine-readable durability
class (durable / rebuildable_cache / scratch) and its resolution.
Declarations are complete by construction: a storage provider must declare
exactly one class for each of the seven domains (sessions, runtime,
schedule, workgraph, jobs, blobs, artifacts), so no slot dodges the rule
by omission. A durable slot resolving to a non-persistent store without the
realm manifest declaring that domain ephemeral is a startup error. A provider’s
typed DeclaredEphemeral resolution is already an explicit declaration and
does not need a duplicate manifest entry. Neither path permits a silent
in-memory fallback. Deployment tooling can rely on the classes (for
example, cloning only durable domains between state generations).
Session storage layout (0.7.25+): head-canonical strands
Since 0.7.25 the SQLite session store persists sessions incrementally: the transcript lives in append-onlysession_strand_messages rows, rewrite
history in session_rewrites, and a single session_heads row per session
carries the canonical metadata, message count, and CAS token. Saving a long
session appends O(delta) rows instead of rewriting a monolithic document.
The canonical-representation rule, per session: if a session_heads row
exists, the head representation is canonical. A released 0.8.10 blob, if
present, is consumed only by the one-time activation importer and is never a
fallback authority for a current head. Sessions created in HeadCanonical mode
may have no meaningful sessions blob row at all.
Range-read capability probes (load_canonical_head / load_rewrite_commits)
IncrementalSessionStore carries two additive read verbs that expose the
head-canonical representation to future O(page) history reads:
load_canonical_headreturns the persisted head row ONLY when head+rows are the session’s canonical durable representation. Unlike a full materializing load, it answersNonefor absent and non-canonical rows and never reads a WholeBlob body.Somepromises the row is the persisted head itself and thatload_messagesoverhead.strandpage-serves exactly the rows the head covers.load_rewrite_commitsreturns the ADOPTED rewrite commits (idx < head.rewrite_count), oldest first, without materializing retained revision bodies. It must always equalload_rewrites’ commits, in order — including the empty set while a recorded rewrite is not yet adopted.
None; derive from load_rewrites),
so every existing store — including delegating wrappers that do not forward
them — degrades to the whole-load path, never to a refusal. The conformance
suite pins the contract conditionally: a store answering None for every
canonical-head probe is fully conformant; a store answering Some must serve
the persisted row and page-exact strand reads, and delegating wrappers must
forward both verbs (capability_discovery chapter).
Service reads trust only the store-selected profile and its issued authority.
read_history, read_transcript_revision, and
list_transcript_revisions may materialize the required domain view from the
canonical rows, but a materialized Session never becomes write authority.
Durable runtime lifecycle vocabulary (runtime_states)
The runtime companion persists one lifecycle row per runtime. The full
runtime_state vocabulary, with polling guidance:
Busy-detection loops must treat
stopped and destroyed as not-busy:
stopped means nothing is executing and nothing will execute without an
explicit resume, so polling it as “busy” hangs until an external timeout.
Prefer the typed status surfaces over raw row reads: since 0.7.25,
session/input_status answers from durable state
without requiring a live runtime registration, so a fresh host process
reports truthful terminal outcomes (Ok/NotFound) for work finished
before a restart.
Config concurrency contract
Config runtime uses generation CAS.config/getreturns currentgeneration.config/setandconfig/patchcan specifyexpected_generation.- Mismatches fail deterministically with generation conflict.
Data governance
Request-only turn context
transient_turn_context is non-empty runtime-input text, not Session state.
Whitespace is significant and its exact text bytes remain on a pending input
so cold recovery can retry the same turn, then retire with that terminal input
payload. During the active logical turn, Meerkat projects the caller context
first and any runtime-admitted steers after it, each as a distinct temporary
user-channel injected-context message appended at the current foreground
model-request tail at the authorized request boundary. No conversational-row
anchor is required. They are never System messages, transcript appends, rewrite
records, compaction input, or extraction input. This is separate from durable
injected_context, whose rows are appended before the turn’s conversational
user message.
Deferred creation cannot carry this context because no runtime input exists
yet; attach it to the eventual turn instead. The browser/WASM standalone live
surface does not expose this durable runtime-input feature.
