---
title: "Session contracts"
description: "Canonical session, runtime, keep-alive, and commit-boundary contracts."
icon: "file-lines"
---

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
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)
