> ## Documentation Index
> Fetch the complete documentation index at: https://omi-codex-chat-first-ui-plan.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent control plane invariants

# Agent Control Plane Invariants

**Status:** Enforced contract for Phase 1 control-plane changes
**Scope:** Normative invariants and guard tests. Architecture, schema rationale,
and rollout phases remain in [agent-control-plane](/doc/developer/agent-control-plane).

Future PRs that touch `desktop/macos/agent/src/runtime`, adapter bindings,
control tools, request-scoped relay state, or startup reconciliation should name
the invariant they affect and update the matching guard test.

## Identity

### Omi IDs and adapter-native IDs are separate namespaces

`session_id`, `run_id`, `attempt_id`, `binding_id`, `artifact_id`,
`delegation_id`, and `grant_id` are Omi-owned. Adapter-native session IDs are
opaque adapter data and live only in `adapter_bindings.adapter_native_session_id`
or explicitly named compatibility fields such as `adapterSessionId` and
`legacyAdapterSessionId`.

Guard surface:

* `assertAdapterBindingContract` rejects adapters that return an Omi session ID
  as their native session ID.
* `assertAdapterAttemptResultContract` rejects terminal results that conflate or
  drift away from the binding's adapter-native ID.
* `runtime-adapter.test.ts` pins both failure modes.

### An Omi session ID does not change when bindings change

Adapter changes, stale native sessions, process restarts, and model or MCP
compatibility changes create a new binding generation or attempt under the same
Omi session/run as appropriate.

Guard surface:

* `adapter_bindings.binding_generation` is monotonic per session/adapter.
* Binding compatibility hashes include cwd, system prompt, and the
  adapter-effective MCP server set.
* `run-attempt-lifecycle.test.ts` and `adapter-binding.test.ts` cover stale
  binding retry, compatibility changes, and request-scoped MCP env stripping.

### Session execution profiles are immutable generations

Every session resolves one persisted `SessionExecutionProfile` containing its
adapter, credential scope, model profile, working directory, execution role,
and generation. Global preferences create the profile for future sessions only.
An explicit user migration must carry the expected generation, must run in one
transaction, and must reject a session with an active run. Migration creates a
new profile generation and stales prior adapter bindings; it does not restart
the shared daemon or mutate the session ID.

Guard surface:

* `session-execution-profile.ts` owns default preferences and profile migration.
* `session-execution-profile.test.ts` covers next-session defaults, stale
  generation rejection, active-run rejection, binding invalidation, and restart.

### Workstream identity is the reusable task-work boundary

For durable task work, the kernel resolves `surface_kind=workstream`,
`external_ref_kind=workstream`, and `external_ref_id=<workstream_id>`. All task
views linked to that workstream reuse one local conversation per owner/runtime.
Task IDs may remain compatibility surface mappings during migration, but they
do not own independent agent continuity.

Guard surface:

* `surface_conversations` and the existing session external-reference index are
  the only binding mechanism; there is no parallel workstream binding store.
* `workstream-continuity.test.ts` covers reuse, task-session migration, and
  shared artifact history.

## Authority

### Owner authority comes from active Omi context

The active owner for a control operation comes from the Omi request, run, or
attempt context. Tool-supplied `ownerId` values are guards: they can reject a
request, but they do not authorize it.

Guard surface:

* `control-tools.ts` rejects owner-scoped adapter-originated calls without active
  request, run, or attempt context.
* `kernel.ts` validates session, run, attempt, and artifact selectors against
  the active owner.
* `control-tools.test.ts` covers mismatched guards, cold direct control, and
  concurrent owner-scoped tool calls.

### Request-scoped state is keyed by client and request

A bare `requestId` is not unique under concurrent clients. Request-scoped
control and relay maps must use `(clientId, requestId)`.

Guard surface:

* `jsonl-transport.ts` stores adapter request correlation by
  `JSON.stringify([clientId, requestId])`; the kernel never treats that map as a
  tool grant.
* `tool-relay.test.ts`, `control-tools.test.ts`, and the external-surface
  authority tests cover concurrent clients, missing client IDs, and run-bound
  execution.

Request/client IDs are correlation only. Cancellation resolves the durable run
and active owner in the kernel; it cannot derive authority from a matching
request map or a caller-supplied owner.

### Intent routing and control application are one kernel boundary

Surfaces submit a typed proposal and syntax facts to `route_intent`. The kernel
returns a typed decision and atomically applies any required control action.
Swift does not classify the same text, infer dispatch from keywords, or repair a
decision with a second resolver.

Guard surface:

* `desktop-intent-router.test.ts` covers all surfaces, typed validation,
  deterministic syntax facts, and route-and-apply atomicity.
* `convergence-authority-ratchet.test.ts` forbids the retired Swift classifiers.

### Physical tool authority is run/attempt scoped and single use

The kernel's capability reference is process-local input to authorization, not
a bearer exported to Swift. Authorization re-reads durable owner, session, run,
attempt, profile generation, role, and manifest policy. It then persists the
invocation claim before emitting `authorized_tool_execution` to Swift.

The ledger transition is `prepared → dispatched → succeeded|failed`. On daemon
restart, `prepared → failed` and `dispatched → outcome_unknown`. A
non-idempotent write has `never_auto_retry`; no result may be accepted unless it
echoes the complete immutable claim tuple, input hash, and generated manifest
digest. Ordinary adapter `tool_use` events are display-only in Swift.

Guard surface:

* `run-tool-capability.test.ts` covers revocation, owner/attempt/profile checks,
  replay denial, manifest policy, restart reconciliation, and exact result claims.
* `AuthorizedToolExecutionTests.swift` covers the physical executor boundary and
  cross-language canonical input hash.

### Conversation durability has one sequence owner

The kernel journal is the only canonical conversation writer. A record/update,
its monotonic sequence revision, and any backend outbox row commit together.
Swift may stage presentation immediately and perform owner-bound HTTP, but it
cannot acknowledge, reorder, or durably mutate a turn.

Backend chat is a rollback-readable mirror in the existing message format. It
is also an event-triggered import feed for genuinely remote client turns. The
kernel reads owner-scoped bounded pages, deduplicates stable remote and
canonical turn IDs, and advances a durable ID frontier. It never uses a
timestamp or offset cursor. Swift uses the additive desktop reconciliation
endpoint, whose Firestore document-ID cursor is validated inside the
authenticated owner and requested app/session filter; the rollback message
shape retains `client_message_id`, structured metadata, and resources. The idle
outbox timer never polls backend history.

Guard surface:

* `conversation-journal.test.ts` covers producer idempotency, ordered replay,
  owner-scoped delivery/deletion, paged remote import, restart, and exact claims.
* `KernelTurnRecordedProjectionTests.swift` covers sequence projection and the
  single Swift physical sync boundary.
* `KernelJournalBackendReconcileTests.swift` and backend desktop-migration tests
  cover exact cursor wire shape, insertion between pages, reported-row scans,
  canonical identity, and structured metadata.
* `convergence-authority-ratchet.test.ts` forbids direct writers and timestamp
  reconciliation cursors.

### Context snapshots are kernel-selected and pinned per run

Swift can update typed source data with a revision and one of `available`,
`empty`, `unavailable`, or `redacted`. It cannot send a system prompt, model,
cwd, surface prompt JSON, or capability policy in a query. The kernel builds a
session-scoped `ContextSnapshot` with content version, monotonic generation,
renderer-policy version/fingerprint, recent journal turns, explicit source
outcomes, active runs, and a non-bearer capability projection.

Admission pins the snapshot used by the logical run. Queue delay and retries do
not rebuild it from newer sources. An A→B→A content sequence may reuse content
version A but must have a higher generation. Voice socket refresh identity uses
content version plus renderer/capability fingerprints, not the monotonic audit
generation, so unchanged material does not churn a warm provider session.

Guard surface:

* `session-execution-profile.test.ts` covers source outcomes, renderer policy,
  A→B→A generation, irrelevant-source selection, and admission/dispatch races.
* `KernelContractWireTests.swift` forbids request-supplied runtime authority.

## Lifecycle

### Exactly one non-terminal attempt per run has execution authority

Only one attempt with status `queued`, `starting`, `running`, `waiting_input`,
`waiting_approval`, or `cancelling` may exist for a run.

Guard surface:

* The kernel refuses to create a second active attempt.
* SQLite enforces `run_attempts_one_active_per_run_uq`, a partial unique index
  on `run_attempts(run_id)` for active statuses.
* The migration that introduces the index repairs legacy duplicate active
  attempts by orphaning older attempts and writing `attempt.orphaned` events in
  the same transaction before installing the index.
* `sqlite-store.test.ts` verifies the storage-level invariant.

### Silence, disappearance, and UI dismissal are not success

Run completion is determined only by a terminal kernel state persisted by the
control plane. Startup reconciliation marks active attempts `orphaned`; it does
not infer success from process disappearance.

Guard surface:

* `reconcileStartup()` updates active attempts to `orphaned`, marks
  non-resumable active bindings stale, and creates recovery dispatches for
  interrupted delegations. It also repairs rows written during a one-release
  downgrade window (missing immutable profiles or journal sequence metadata)
  and terminalizes orphaned pending/streaming journal turns with a bounded
  observable repair code. Every repair is idempotent across repeated opens.
* `sqlite-store.test.ts` and `run-attempt-lifecycle.test.ts` cover startup
  reconciliation and idempotent recovery dispatch behavior.

### Cancellation acknowledgement is truthful

`dispatchAttempted` and `adapterAcknowledged` are separate. A cancelled run is
terminally `cancelled` even if partial text exists. `adapterAcknowledged` remains
false unless the adapter independently confirms cancellation or the worker is
known terminated.

Guard surface:

* `CancelDispatchResult` carries both fields.
* Adapter capability expectations mark acknowledgement gaps as
  `known_limitation` with follow-up ticket references.

## Binding Compatibility

### Compatibility is determined by stable kernel-effective inputs

The kernel hashes the pinned profile generation, kernel policy, working
directory, and stable MCP server configuration to decide whether a binding is
reusable. If an adapter mutates MCP servers before
passing them to its native runtime, it must implement `effectiveMcpServers` so
the hash reflects what the adapter actually saw.

Guard surface:

* `stableMcpServerConfig` strips request-scoped MCP env keys before hashing.
* `RuntimeAdapter.effectiveMcpServers` documents adapter mutation behavior.
* `run-attempt-lifecycle.test.ts` covers request-scoped env changes and
  adapter-stripped MCP sets.

### Process-local bindings are pinned or stale

Bindings with `resume_fidelity = 'none'` must be pinned to their worker while
active. On restart they become stale and cannot be treated as native-resumable.

Guard surface:

* The capability matrix requires `pinnedWorker` for process-local production
  adapters.
* Worker-pool tests enforce one active attempt per worker and idle pinned-worker
  retention.
* Startup reconciliation marks active non-resumable bindings stale.

### Adapter capabilities are explicit

Every production adapter must declare `required`, `unsupported`, or
`known_limitation` for every capability key. Known limitations need a follow-up
ticket.

Guard surface:

* `ADAPTER_CAPABILITY_MATRIX` is the source of truth.
* `runtime-adapter.test.ts` verifies production expectations and ticketed known
  limitations.

### Adapter credential scope is explicit and inherited

Every production adapter declares `managed_cloud` or `local_user` in
`ADAPTER_CAPABILITY_MATRIX`. Sessions persist a provider boundary when the
kernel creates them. Managed sessions may select only managed-cloud adapters;
local-user sessions are pinned to the exact selected local adapter. Missing
adapter arguments inherit that boundary, while unknown adapters fail closed.
Signed desktop control, and the canonical top-level `spawn_agent` tool's
explicit `provider` selector for Hermes or OpenClaw, may establish a fresh
local-provider session or resume an existing session against its persisted
boundary, even when the control bridge itself uses managed-cloud routing. An
adapter ID alone may not use that exception, and neither may cross a parent
run's boundary or override an existing session's adapter.

For model-driven external surfaces, that selector is valid only when the
current user explicitly names the exact provider. The external-surface policy
normalizes a matching provider, strips an invented/stale/mismatched provider,
and therefore defaults generic delegation to the managed Omi adapter before the
kernel mints a child session. A signed desktop UI selection remains explicit
user control and follows the same provider-boundary rules.

Guard surface:

* `execution-policy.ts` is the shared resolver used by control tools and the
  kernel run-acceptance path.
* `runtime-adapter.test.ts`, `execution-policy.test.ts`, and
  `control-tools.test.ts` exhaust production declarations, managed routing,
  directed and signed top-level local-provider starts, and signed local-session
  resumption.
* `external-surface-authority.test.ts` verifies generic PTT delegation cannot
  select a local provider while an explicitly named provider is preserved.

### Execution role owns agent-management authority

Sessions persist `coordinator` or `leaf`. Background, delegated, and floating
pill sessions are created as leaves. Leaf tool projections omit agent creation
and continuation tools, while the control relay and kernel independently reject
stale or forged calls for every leaf-restricted tool (including
`send_agent_message`). The relay loads the caller's persisted policy by session
id with an owner check and fails closed when that lookup cannot complete;
surface-derived role exists only in the creation/legacy migration boundary.
Persisted kernel state is authoritative afterward. `spawnBackgroundAgent`
requires a coordinator caller session or an explicit trusted-user spawn.

Guard surface:

* `omi-tool-manifest.test.ts` verifies leaf capability projection.
* `control-tools.test.ts` verifies server denial for every nested creation and
  continuation entry point, plus kernel spawn authority checks.
* `sqlite-store.test.ts` verifies role and provider-boundary persistence.

### Required control failures cannot become successful runs

Required spawn, delegate, and continuation operations remain unresolved after
failure until the same logical operation succeeds. Transport correlation and
adapter-selection fields do not define logical identity, so a corrected retry
can resolve the obligation; an unrelated successful operation cannot.

Guard surface:

* `pi-mono-adapter.test.ts` covers terminal failure, matching retry success,
  and unrelated-success denial.
* The kernel remains the only component that persists run and attempt terminal
  state; adapter outcomes are inputs to that transition, not an alternate store.

## Persistence

Lifecycle state transitions and their durable events should commit in the same
SQLite transaction. New store paths must use `AgentStore.withTransaction` when
they persist both a state transition and an event row.

Startup reconciliation must be deterministic and idempotent. Re-running it
should either no-op or strictly refine stale interrupted state without producing
duplicate recovery dispatches.

### Product continuity never imports execution authority

Workstream context packets and continuation checkpoints contain bounded product
summaries, selected events, task state, artifact heads, and provenance. They do
not copy adapter transcripts or import another runtime's run/attempt status.
Each runtime establishes its own binding and remains authoritative for its local
execution state.

Checkpoint freshness is the backend workstream journal's canonical event
sequence, never the local kernel `events.event_seq`. Upserts are monotonic and
import persists an expiring product-context packet for restart. Provenance uses
the canonical typed `EvidenceRef` contract, including device-local scope rules;
device-local or sensitive evidence cannot be exported without a resolved,
export-specific dispatch. Export replaces sensitive source content with its
redacted representation and removes source-runtime dispatch state before a
target runtime can import it.

Local task Candidates are outbox records until backend delivery. Delivery uses
a stable idempotency key and stores a bounded receipt; after receipt, local state
is `forwarded` or a projection of canonical resolution and must not remain in a
local review queue.

Pre-contract Candidate rows migrate as `blocked`. Reconciliation must supply
the current account generation before an eligible pending row can enter the
outbox; historical accepted, rejected, or expired rows remain non-deliverable.
Creation and resolution receipts are bounded and retained separately.

Guard surface:

* `workstream-continuity.test.ts` covers bounded context, cross-runtime
  checkpoints, Candidate retry/receipt/projection, and rank-free open-loop
  snapshots.
* `sqlite-store.test.ts` covers interrupted-delivery startup reconciliation.
