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. Future PRs that touchdesktop/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:
assertAdapterBindingContractrejects adapters that return an Omi session ID as their native session ID.assertAdapterAttemptResultContractrejects terminal results that conflate or drift away from the binding’s adapter-native ID.runtime-adapter.test.tspins 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_generationis monotonic per session/adapter.- Binding compatibility hashes include cwd, system prompt, and the adapter-effective MCP server set.
run-attempt-lifecycle.test.tsandadapter-binding.test.tscover stale binding retry, compatibility changes, and request-scoped MCP env stripping.
Workstream identity is the reusable task-work boundary
For durable task work, the kernel resolvessurface_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_conversationsand the existing session external-reference index are the only binding mechanism; there is no parallel workstream binding store.workstream-continuity.test.tscovers 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-suppliedownerId values are guards: they can reject a
request, but they do not authorize it.
Guard surface:
control-tools.tsrejects owner-scoped adapter-originated calls without active request, run, or attempt context.kernel.tsvalidates session, run, attempt, and artifact selectors against the active owner.control-tools.test.tscovers mismatched guards, cold direct control, and concurrent owner-scoped tool calls.
Request-scoped state is keyed by client and request
A barerequestId is not unique under concurrent clients. Request-scoped
control and relay maps must use (clientId, requestId).
Guard surface:
compatibility-facade.tsstores active request state byJSON.stringify([clientId, requestId]).tool-relay.test.ts,tool-correlation.test.ts,control-tools.test.ts, andcompatibility-facade.test.tscover concurrent clients and missing client IDs.
Lifecycle
Exactly one non-terminal attempt per run has execution authority
Only one attempt with statusqueued, 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 onrun_attempts(run_id)for active statuses. - The migration that introduces the index repairs legacy duplicate active
attempts by orphaning older attempts and writing
attempt.orphanedevents in the same transaction before installing the index. sqlite-store.test.tsverifies 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 attemptsorphaned; it does
not infer success from process disappearance.
Guard surface:
reconcileStartup()updates active attempts toorphaned, marks non-resumable active bindings stale, and creates recovery dispatches for interrupted delegations.sqlite-store.test.tsandrun-attempt-lifecycle.test.tscover 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:
CancelDispatchResultcarries both fields.- Adapter capability expectations mark acknowledgement gaps as
known_limitationwith follow-up ticket references.
Binding Compatibility
Compatibility is determined by stable adapter-effective inputs
The kernel hashes cwd, system prompt, 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 implementeffectiveMcpServers so
the hash reflects what the adapter actually saw.
Guard surface:
stableMcpServerConfigstrips request-scoped MCP env keys before hashing.RuntimeAdapter.effectiveMcpServersdocuments adapter mutation behavior.run-attempt-lifecycle.test.tscovers request-scoped env changes and adapter-stripped MCP sets.
Process-local bindings are pinned or stale
Bindings withresume_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
pinnedWorkerfor 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 declarerequired, unsupported, or
known_limitation for every capability key. Known limitations need a follow-up
ticket.
Guard surface:
ADAPTER_CAPABILITY_MATRIXis the source of truth.runtime-adapter.test.tsverifies production expectations and ticketed known limitations.
Adapter credential scope is explicit and inherited
Every production adapter declaresmanaged_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.
Guard surface:
execution-policy.tsis the shared resolver used by control tools and the kernel run-acceptance path.runtime-adapter.test.ts,execution-policy.test.ts, andcontrol-tools.test.tsexhaust production declarations, managed routing, directed and signed top-level local-provider starts, and signed local-session resumption.
Execution role owns agent-management authority
Sessions persistcoordinator 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.tsverifies leaf capability projection.control-tools.test.tsverifies server denial for every nested creation and continuation entry point, plus kernel spawn authority checks.sqlite-store.test.tsverifies 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.tscovers 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 useAgentStore.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 kernelevents.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.tscovers bounded context, cross-runtime checkpoints, Candidate retry/receipt/projection, and rank-free open-loop snapshots.sqlite-store.test.tscovers interrupted-delivery startup reconciliation.