> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rkat.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Session contracts

> Canonical session, runtime, keep-alive, and commit-boundary contracts.

This page is the canonical settled-state contract for session and runtime behavior.

## Ownership model

Meerkat has one canonical semantic path:

* **runtime-backed surfaces** own `keep_alive`, Queue/Steer routing, comms drain lifecycle, external-event admission, and request/turn commit semantics
* **`SessionService`** is the substrate lifecycle seam used by those surfaces
* **`EphemeralSessionService` / `build_ephemeral_service`** remain valid for testing, embedded use, and WASM internals, but they are not the primary product path and do not own runtime semantics

The runtime-backed path is:

```text theme={null}
Surface -> MeerkatMachine -> SessionService -> AgentFactory::build_agent()
```

The runtime-backed build contract has an explicit binding seam:

* 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::StandaloneEphemeral` explicitly instead of relying on silent fallback

`SessionRuntimeBindings` is the epoch-local runtime handle for a session. It carries:

* `session_id`
* `epoch_id`
* the canonical `OpsLifecycleRegistry`
* shared consumer cursor state used for recovery-safe completion visibility

This keeps one owner for runtime semantics:

* `PersistentRuntimeDriver::recover()` owns input/runtime/control recovery
* `MeerkatMachine` owns session entry recovery (`ops_lifecycle`, `epoch_id`, cursor state)

Direct substrate usage is intentionally narrower:

* 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 in `AgentFactory::build_agent()`.

* Surfaces pass per-request build data in-band via `CreateSessionRequest.build` (`SessionBuildOptions`).
* No out-of-band staging lock is used.
* `FactoryAgentBuilder` maps `SessionBuildOptions` to `AgentBuildConfig`.
* For runtime-backed builds, `SessionBuildOptions.runtime_build_mode` should carry `RuntimeBuildMode::SessionOwned(bindings)` from `prepare_bindings()`.
* For standalone/testing/embedded builds, prefer `RuntimeBuildMode::StandaloneEphemeral` explicitly.
* Session metadata persists realm context and durable session identity:
  * `realm_id`
  * `instance_id`
  * `backend`
  * `config_generation`
  * durable LLM identity
  * `keep_alive`
  * visible comms identity metadata such as `comms_name` and `peer_meta`

## Session lifecycle and turn semantics

### create\_session

`create_session(req)` builds the agent and optionally runs the first turn.

* Returns `RunResult` with `session_id`.
* `InitialTurnPolicy::RunImmediately` executes the first turn inline (default).
* `InitialTurnPolicy::Defer` registers the session without running a turn.

Create has a commit boundary:

* **pre-commit failure**: side-effect free; no committed session identity
* **post-commit first-turn failure**: return session identity and keep the session resumable

Committed create failure must not be silently rewritten to “cancelled” or cleaned up as unpublished work.

### 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.

### Deferred first-turn system prompt override

Rust supports `system_prompt` on `StartTurnRequest` only for a deferred session's first turn.

* deferred + first turn: allowed
* existing history/messages: rejected

This keeps Rust aligned with the current runtime contract instead of implying arbitrary mid-session prompt replacement.

### interrupt

`interrupt(id)` cancels an in-flight turn.

* If no turn is running: `SESSION_NOT_RUNNING`.

### read and list

* `read(id)` and `list(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.
* `offset` and `limit` apply from the start of the full transcript.
* Active sessions do not expose in-flight partial output through history reads.
* Archived sessions remain readable when the underlying service/backend supports archived snapshots.

### archive

* Removes session from live runtime.
* Persistent behavior depends on service/store implementation.

## 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_alive` override is a session/runtime mutation once validated
* invalid `keep_alive` + comms configuration is rejected before any stateful work
* once validated, an explicit `keep_alive` mutation 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

## Explicit override semantics

Where the surface supports omission vs explicit override, these are distinct facts:

* omit / inherit
* disable / false
* set / concrete value

When all three meanings matter, the wire/API must preserve that distinction. Typed optional fields and override masks are preferred over default-value folklore.

## Realm contract

Sessions are realm-scoped.

* Same `realm_id`: shared visibility and config context.
* Different `realm_id`: strict isolation.
* Backend is pinned per realm via `realm_manifest.json`.

## Compaction contract

Compaction is optional and non-fatal.

* Triggered by token thresholds and turn guards.
* On failure, emits `CompactionFailed` and continues with uncompacted history.
* Compaction usage counts toward run budgets.

## Durability

### Ephemeral mode

No durability across process restart.

### Persistent mode

Durability follows the active realm backend (`sqlite` or `jsonl`).

* Completed turns are persisted.
* Crash during in-flight turn may lose only that turn.
* SQLite-backed realms are the default persistent mode and support normal same-realm multi-process workflows.

Persistent session metadata is the source of truth for resumed durable session behavior unless an interactive surface supplies an explicit typed override.

## Config concurrency contract

Config runtime uses generation CAS.

* `config/get` returns current `generation`.
* `config/set` and `config/patch` can specify `expected_generation`.
* Mismatches fail deterministically with generation conflict.

## Data governance

<Warning>
  No automatic sensitive-data redaction is applied to session content or tool payloads.
  Encrypt storage at rest externally if required by your environment.
</Warning>

## See also

* [Architecture](/reference/architecture)
* [Design philosophy](/reference/design-philosophy)
* [Realms](/concepts/realms)
* [Sessions](/concepts/sessions)
* [Capability matrix](/reference/capability-matrix)
