# Agent Runtime Delivery Reliability

> Canonical path: `specs/message/delivery-runtime.md`
> Related specs: [../collaboration/collaboration.md](../collaboration/collaboration.md), [../daemon/daemon.md](../daemon/daemon.md), [../daemon/runtime-state-compatibility.md](../daemon/runtime-state-compatibility.md)

Status: current.
Scope: server fanout handoff, daemon-local durable delivery ownership, runtime lifecycle failure reporting, and busy-turn continuation.
Non-goals: server-side agent `/receive`, server-side/per-recipient message delivery ledgers, scope runtime ledgers, generic unread backlog auto-start, and compatibility paths for old targeted delivery tables.

This spec defines how AgentTeam gets a newly-routed message or non-message signal to an agent runtime without turning readable history into an offline inbox. The server owns durable message facts, readability, routing decisions, and fanout retry state. The daemon owns the local runtime queue and decides how an accepted delivery is presented to a live, idle, or newly-started runtime.

## 1. Runtime lifecycle ownership

`agentteam daemon` owns local runtime lifecycle handling. It knows whether a runtime is active, busy, able to accept stdin, or needs a fresh turn. The server owns durable message/activity facts and the outbox job that asks the daemon to handle the current event; the daemon must not create an alternate server-side message truth.

The existing `agent_runtime_events` table is limited to daemon / runtime lifecycle facts that current shipped behavior consumes, such as:

- runtime start failed;
- runtime exited unexpectedly;
- runtime reported an error, even if the local runtime process later exits with status code 0;
- wake handling failed after the daemon accepted work;
- visible reply sent;
- completed without visible reply;
- busy-turn deferral / replay state when the daemon needs it to continue the same active runtime turn.

When the agent successfully adds a reaction to the exact message that triggered
the current wake, that reaction is valid lightweight response evidence for the
wake lifecycle. The daemon should close that wake with the successful reply
status rather than later reporting `completed_without_visible_reply`; because a
reaction is not a `scope_messages` row, the reply message id remains empty.

When a runtime reports an error while handling a concrete wake, the daemon treats that wake as failed. The failure detail must be sent through the runtime lifecycle and wake-status surfaces so diagnostics can explain what happened, instead of being reclassified as a successful clean exit or a completed turn with no visible reply. Server wake-status projection must attach the Agent's public technical and display names from the same bounded machine-ownership lookup that accepts the event; Browser rendering must not expose a raw Agent ID as user-facing identity or issue a management-only Agent detail read to recover that identity. The daemon must not write this failure detail back as an ordinary channel or DM message because that pollutes conversation history and makes operational status look like an agent-authored reply. Browser conversation surfaces present active wake state, or the latest relevant terminal state when nothing is active, in one non-message status area at the end of the current conversation. They must not repeat terminal runtime status beneath historical messages. Detailed failure diagnostics remain available on explicit expansion.

The same boundary applies when a runtime produces local assistant text without confirming a AgentTeam-visible reply. Local runtime text is not an ordinary message source and must not be auto-relayed into AgentTeam. The daemon-side wake status, prompt-compiler guidance, and agent-level rollout switch for this behavior are defined by [唤醒可见回复契约](../daemon/wake-visible-reply-contract.md).

These events are not message visibility, read-state, fanout target, or server-side receive facts. They must not be used as a replacement for `scope_messages`, `scope_activity_events`, membership/read authority, or outbox state.

### 1.1 Explicit runtime lifecycle

Runtime output and runtime lifecycle are separate contracts. A runtime that declares an explicit turn lifecycle must use its authoritative start and terminal protocol events to move the daemon between busy and idle. Text, thinking, tool activity, tool output, and late item completion are trajectory content; they do not start or reopen a root turn by themselves.

For Codex app-server, `turn/started` is the authoritative busy boundary and `turn/completed` is the ordinary terminal boundary. A non-retrying terminal `error` is also a terminal boundary: the error remains visible as failure evidence, while the driver synthesizes the matching turn end instead of waiting indefinitely for a later `turn/completed`. Once a primary turn is terminal, root activity for that completed turn or for a different stale turn must be dropped. Correlated delivery acknowledgements and thread-scoped usage/settings metadata remain consumable because they are ownership or observability facts, not root-turn activity.

Daemon-restart safety follows the same explicit lifecycle. Starting a Codex turn, handing new work to it, or starting compaction marks the saved session unsafe. A terminal boundary can mark it safe only after the current delivery boundary is resolved. The daemon persists an unsafe baseline before constructing `Process`, calling driver `Spawn`, or allowing any initial turn mutation—not merely before callback installation—and callback installation replays the latest versioned safety snapshot so a fast start/end sequence cannot be overwritten by callback-registration order. Every live-owner upsert sets unsafe in the same transaction; a safe=true transaction rechecks the journal and refuses promotion while any `queued` or `inflight` owner exists. Durable safety updates are compare-and-set by a monotonically increasing Process-launch epoch plus an epoch-local transition version, so a delayed callback from an older Process cannot overwrite a replacement launch's unsafe baseline. A no-prompt idle `SessionInit` is itself safe only after callback delivery and ownership restore/drain leave no inbox, pending runtime input, or reserved handoff; restoring any live owner supersedes it back to unsafe. `SessionResumeSafe` is a delivery ownership/reconciliation fact, not a provider-session guard: restart may still hand the saved thread to runtime authority when the current runtime policy and semantic base/developer instruction fingerprint match. An active or uncertain owner is reconciled against that thread and is never blind-replayed.

### 1.2 Framing and generation-scoped translated output

The newline-delimited Codex app-server reader and Process translated-event parser enforce their limit while reading, not after an unbounded token allocation. They use `ReadSlice('\n')`, accumulate fragments across `ErrBufferFull`, and apply one hard `32 MiB` cap to the complete raw line including its terminating newline. A fragment boundary, decoded field size, or approximate character count is not the limit. An oversized or malformed frame, read failure, or unexpected EOF is transport-fatal; the runtime must not silently truncate or continue on a stream whose request/turn ownership is no longer provable.

Codex translated output is generation-scoped and lossless. Each event is charged by the final JSON-encoded wire bytes, including escaping and the terminating newline, against both the Process line cap and a queue capped at 128 entries / `32 MiB` of reserved final wire bytes. Saturation applies cancellable backpressure to producers; transcript, tool, and terminal events may not be silently dropped. Replacement aborts old producers before waiting for the generation write fence, then cancels the retired flusher and closes its writer only after acquiring that fence; explicit cleanup or translated-output failure may cancel/close immediately to release a missing consumer. A source EOF or malformed frame instead seals the queue and drains every admitted line; Process exit is not exposed until the generation reader, translated flusher, and Process-facing final-EOF consumption fence have completed. Explicit cleanup may abandon a missing consumer through its shutdown signal. Encoding failure, an oversized final line, a short write, or an unexpected writer failure is transport-fatal; an intentional abort is idempotent and cannot poison the replacement generation.

Compaction has an explicit two-event terminal latch because Codex app-server may emit `thread/status/changed=idle` before the matching `turn/completed`. The early idle is recorded only for the current generation and exact compaction turn; it does not clear activity or emit runtime idle by itself. The matching completion consumes the latch atomically and emits exactly one runtime-idle boundary. A wrong-turn, duplicate, or old-generation completion cannot consume it, and ordinary turns continue to use their normal TurnEnd boundary.

## 2. Delivery handoff contract

Server-side agent delivery has one durable execution boundary: `scope_fanout_outbox`.

1. The message writer computes the routing plan at write time.
2. The writer normalizes durable recipients and wake recipients, with `WakeRecipients ⊆ DurableRecipients`.
3. The writer stores the delivery payload/reasons in `scope_fanout_outbox`.
4. After commit, the fanout worker leases the outbox job and sends `agent:start` or `agent:deliver` to the target machine daemon.
5. The daemon ACKs whether it accepted the handoff.
6. Once accepted, the payload is daemon-local runtime input. During an active turn, the daemon either hands it to an ack-aware runtime pending queue or retains it in the current live process's internal continuation inbox for automatic delivery at the next safe input boundary or idle transition.

`agent:deliver` is strictly a handoff to the daemon's current Process generation. It may admit the message into that Process's direct runtime transport or durable continuation queue—including the queue owned by a live runtime that does not support stdin—but it must not create, resume, replace, or restart a Process. If there is no current Process accepting delivery, the generation is retired, or a start/replacement generation is already in flight, the daemon rejects the ACK promptly so the server can use `agent:start`, which owns cold-start and replacement lifecycle. The current Process is authoritative even when the mutable agent profile, runtime catalog, stored start config, or managed-skill readiness cache is absent or stale; none of those cold-start prerequisites may gate a live handoff. The wire handler therefore does not synchronize profiles, mutate sleeping/wake state, or move delivery into a separate runtime-config-selected deferred queue before Process admission. The short-lived delivery-ACK context bounds only that handoff and is never a parent of Process lifetime. Explicit urgent delivery follows the same boundary: lack or failure of the current Process's native interrupt-and-deliver capability is a rejected handoff, not a daemon-local restart.

The server does **not** expose an agent `/receive` endpoint or a server-side pending-message pull queue. The daemon-local continuation inbox is internal delivery state, not an agent-facing MCP/CLI surface and not a DB scan over unread history.

A sleeping or offline agent must not auto-start solely because old readable messages exist. Auto-wake is decided only for the current routed event. If a machine was offline for a long time, reconnecting does not replay every missed readable message; the agent can still use `read_history` / search later when it needs context.

### 2.1 Durable daemon ownership journal

An accepted handoff must always have exactly one daemon-side owner, including across Process replacement and daemon restart. Before returning an accepted handoff acknowledgement, the daemon atomically persists the complete replay body and stable delivery identity in its local session state. Failure to persist means the daemon has not accepted ownership. The same rule applies even when the server did not request an acknowledgement: every concrete `MessageID` delivered through default dispatch, `agent:start`, execution completion/timeout/interruption, or a hidden workflow uses durable `queued` followed by a post-publication error-returning handoff. Ephemeral/hidden UI behavior must not weaken delivery reliability; hidden workflows use an isolated session workdir instead of sharing the normal agent journal. This journal is not a second server inbox: it contains only concrete payloads already routed through `scope_fanout_outbox` or durable daemon system signals, is bounded, and is private to runtime delivery.

The ownership states are:

- `queued`: the daemon owns the body and the runtime has not accepted it. Exact replay is safe.
- `inflight(dispatching | outcome_unknown)`: the state is durable before a mutating runtime request. The runtime may own the input, so blind replay is forbidden. The reason is diagnostic; restart treats every inflight owner as outcome unknown.
- `terminal(surfaced | failed)`: `surfaced` is correlated evidence that the runtime accepted responsibility for the input at its declared acknowledgement boundary; it is stronger than transport acceptance but is not proof that a particular model request consumed the body or completed inference. `failed` is explicit terminal evidence that duplicate-safe progress ended.

`queued` and `inflight` are live ownership. `terminal` is a tombstone retained as bounded evidence for same-ID deduplication and diagnostics. `surfaced` outranks `failed`: later correlated evidence may upgrade failed to surfaced, but no transition may downgrade surfaced or return terminal ownership to live. A stale rollback/delete may remove only a live owner. The only synchronous durability boundaries are live admission before daemon ACK, inflight before runtime mutation, and terminal before releasing ownership; retry counters, backoff, wake UI progress, and equivalent inflight-reason updates need not make the disk snapshot continuously current.

After an unexpected Process exit, the daemon may continue historical live ownership without waiting for another message. Restore first performs a no-prompt launch under the ordinary per-session start reservation. A restored `queued` record uses the correlated `DeliverForHandoff` path; a restored `inflight` record reconciles its stored runtime identity before any replay. A concrete current wake is always the foreground launch input and is never replaced by an empty historical-reconcile launch. Historical restore runs only as background work or when no current wake exists. Explicit Stop or Sleep does not auto-respawn and rejects new handoffs once stopping begins.

The ownership journal shares `.daemon-state.json` with resume safety, identity, runtime policy, and deferred-continuation state. Every mutation uses the same locked transaction with temporary-file sync, atomic rename, and parent-directory `fsync`; a newly created session workdir also syncs its directory-entry chain before ownership admission or runtime spawn. Capacity is measured from exact serialized JSON: 512 live records / 8 MiB, 512 terminal tombstones / 2 MiB, 1024 total records / 10 MiB, and 512 KiB per live record. First live admission of a new owner—either replay-safe `queued` or `inflight` immediately before a direct runtime mutation—and capacity recovery are one transaction: the store converts the oldest live owners to terminal failed tombstones with `ownership_backlog_reclaimed` until the new MessageID fits. Existing-owner state transitions never choose additional reclaim victims. Historical records may therefore never reject or block a valid new record. Terminal records drop replay bodies and are pruned oldest-first, while Process terminal dedup retains the same record bound. An intrinsically oversized new record is the only capacity admission failure. The previous unversioned ownership schema and known retired formats follow the direct read-version rules in [Runtime Delivery Ownership 格式兼容](../daemon/runtime-state-compatibility.md); truly unknown ownership semantics are rejected without reinterpreting or rewriting the state file.

Ownership writes for one message are serialized and generation-checked. An older queued/inflight write, stale MCP reconcile, or callback from a retired Process may not overwrite a newer mutation generation. Terminal outcomes are monotonic: once surfaced or failed is durable, no live state may replace it. The generation/terminal check and state update are one transaction, not a read-before-write convention that concurrent goroutines can bypass.

Raw stdin has no correlated read-back operation, so its physical write has stricter rules. The daemon persists the complete body as `inflight(dispatching)` immediately before writing any bytes. A write that explicitly accepts zero bytes may return to `queued` only after that transition is durable. A positive partial write, invalid write count, or otherwise ambiguous write becomes terminal failed and retires the stdin stream/child instead of appending another frame or replaying the body.

The daemon may start a fresh app-server thread only after Codex app-server authority explicitly reports the saved thread invalid or not found through the typed `thread/resume`/turn RPC error boundary. That downgrade is attempted at most once with a structured `runtime_retrying` diagnostic (`code=provider_session_fresh_fallback`, runtime, operation, reason, attempt=1). The same one-shot rule applies only to OpenCode HTTP 404/410 and Kimi ACP `session/resume` typed errors; Pi and Claude have no deterministic invalid-session fresh cutover under their current pinned protocols. Codex/Kimi first require the allowed `-32000` outer server-error code, then accept only direct structured `code`/`kind`/`type` values `thread_not_found` or `invalid_thread_id` (Codex) and `session_not_found` or `invalid_session_id` (Kimi); `reason`/`status`/`error`/`message`, bare `invalid`, bare `not_found`, and valid tokens under a different outer code do not authorize fallback. Pi's pre-prompt `get_state` is only a read-only consistency check: the pinned contract returns success=true with `RpcSessionState` or success=false with an error string and no typed invalid-session data; missing, malformed, inconsistent, transport, and ordinary failures all remain fail-closed. Explicit thread/session message compatibility forms remain bounded by the accepted Codex/Kimi outer code. Claude ordinary `--resume` has no deterministic pre-input invalid-session boundary and therefore never infers a fresh cutover from stderr. The ownership record may continue using the old thread id for read-only reconciliation; an outcome-unknown input is never blind-replayed. Transport failures, ordinary provider/config rejection, and incomplete history remain fail-closed.

Daemon-local inbox bodies embedded in an MCP tool result use the same journal, but their surfaced boundary is the **final tool-response hop**, not result construction. A freshness page atomically changes the selected queued body to `inflight(outcome_unknown)`, assigns a cryptographically random opaque receipt, persists the original AgentTeam `MessageID` and exact replay body, and installs a bounded final-hop lease before removing the body from the inbox. The original `MessageID` remains the canonical deduplication/wake identity; the receipt is only the runtime correlation key. A receipt collision or entropy failure fails closed and leaves the body queued. Freshness-page selection may keep its own stable ordering, but its historical work cannot reserve the foreground runtime lane or prevent a newly routed MessageID from being admitted and processed.

The tool text carries an exact receipt marker near the front and MCP `_meta` carries the validated receipt list. Fresh claims bypass only the historical surfaced/deferred filters for that same result; genuinely historical message ids still use the ordinary deduplication path. Each result also carries an in-process delivery-owner capability bound to the exact Process that made the claim; a mutable agent/session lookup may not redirect the final acknowledgement to a replacement Process. A complete one-hop MCP HTTP response write commits receipts as `surfaced`. A direct short/error write is ambiguous because `json.Encoder` appends a newline and a short write can still leave complete parseable JSON; it therefore sends no acknowledgement, retains the exact lease, and after the bounded deadline writes terminal `failed` without replay rather than reconciling an apparently absent marker.

Codex MCP callbacks are two-hop. The HTTP result reports `deferred` even when that first write reports short/error, because the app-server child may already have parsed it and may still complete the second hop. Only a complete generation-scoped JSON-RPC response write to that child commits surfaced; an explicit child-write uncertainty releases the receipt to read-only reconciliation. After child/output transport retirement is proven, the retired Process invalidates and releases only runtime-transferred deferred leases, never independent direct HTTP owners, and leaves their durable inflight ownership for replacement recovery. The claim-time lease is active before the HTTP outcome callback can race another reconciler, a deferred callback renews it, and renewal plus every terminal/retirement path explicitly cancels its superseded timer. The default lease is six minutes, longer than Codex's five-minute MCP request timeout, so a legitimate second hop is not replayed while it can still complete.

For receipt reconciliation, Codex performs `thread/read(includeTurns=true)` against the receipt's exact stored thread id and scans full MCP/dynamic-tool item content for the exact marker. A marker proves `surfaced`; a complete active history without it remains `pending`; incomplete items are `unknown`; only complete inactive history without it is authoritative `absent` and permits exact replay. Result budgeting must preserve the front-loaded markers and receipt metadata. Thus a crash after either response hop cannot turn a tool body into `No new messages`, silently forget it, or create a second owner for the original AgentTeam delivery.

### 2.2 Process admission and retirement

A concrete Process admission owns a lifetime-cancellation context that spans gate acquisition, prompt and attachment rendering, ownership persistence, runtime RPC or raw write, and the post-mutation terminal write. Process retirement first becomes non-accepting and cancels this lifetime, including cooperative operations whose caller used a background context, before waiting for admitted work. An unexpected child exit also becomes non-accepting and drains admitted mutations before publishing `onExit` or starting a replacement.

Public `Stop` and `Sleep` are strong completion boundaries. One caller owns termination, lifecycle-mutation drain, and delivery-gate drain; every concurrent ordinary caller waits for the same completion and may not return merely because `stopping` is already true. A recovery finalizer that already holds the delivery gate uses a separate gate-held, non-reentrant retirement operation, so it can complete state/termination cleanup without recursively waiting for its own gate.

Runtime MCP/server callbacks can be reentrant while an outer delivery holds the non-reentrant delivery gate. Inbox freshness/reconcile therefore uses a separate lifecycle-mutation lease. For a claimed tool result, that exact lease spans the final HTTP/child write, durable terminal ownership write, in-memory removal, stopped-state decision, and all synchronous wake/event/frontier/resume/retry callbacks; only then may Stop, `onExit`, or replacement proceed. Retirement rejects new leases and drains all admitted leases. This barrier combines with the per-message generation and terminal-monotonic update above: a stale `ReconcileInboxMessages` or `ReconcileSurfacedMessages` completion cannot remove a body without durable `surfaced` evidence, cannot run after Stop admission closes, and cannot regress a newer terminal owner.

Dispatcher `Close` and ordinary `Stop` are strong joins. They invalidate
session-start reservations, close new Process callback and Exec-credential
admission, cancel ownership retry workers, and wait without an internal timeout
for every admitted owner and Process exit callback to stop mutating durable
state or emitting wake signals. Every real Process registers a dispatcher-level
callback owner before construction and releases it only after the full `onExit`
wrapper returns, so shutdown does not depend on the Process remaining
discoverable in the session index. `StopForShutdown(ctx)` is the explicitly
total-bounded/cancellable variant. One caller deadline—or a default five-second
deadline when none is supplied—starts at API entry and covers server-connection
Stop, interactive Process stop/child-exit/callback, Memory watcher owners,
credential revocation, and reservation/retry joins. Those surfaces are
initiated concurrently; the implementation may not use an unbounded aggregate
wait or accumulate one timeout per Process. At the deadline the API returns,
while any component that ignored cancellation may finish best-effort in the
background, but closed session and connector generations still prevent late
publication. Code that needs a deadline must choose this API rather than
weakening `Close`/`Stop` postconditions.

Server connections are part of that same outer shutdown boundary, not detached websocket helpers. The connection manager first closes terminal admission, atomically detaches the published runtime snapshot, cancels every admitted start and manager-owned runtime lifetime, and returns one immutable drain object shared by all callers. Each server ID has a mutation generation: a newer enable/disable/register/reconcile cancels the older start, a publication loser compensating-stops its constructed runtime, and a failed reconcile may restore the previous runtime only while the failed generation is still current. Runtime lifetime contexts are owned by the manager after publication; cancellation of the API/start caller is bridged only during startup. A bounded drain waiter may time out, but the immutable drain owner continues an idempotent exact Stop so a later ordinary Close/Stop can strong-join the original run rather than inherit a false-complete timeout.

One established Session Link has its own terminal connection-run admission and explicit child-owner registry. Read/write pumps, message handlers, token and upgrade prefetch, session telemetry, sync tickers, catalog refreshes, elastic links/readers, and remote assignments all inherit that exact run context. Reconnect/Stop closes child admission, cancels and joins the stable owner snapshot, closes elastic links, then drains the shared send queue again after the join; only after this boundary may disconnect cleanup or a replacement generation proceed. Thus a cancellation-aware old handler may finish unwinding, but its frame or catalog mutation cannot cross into the next websocket generation. A bounded Protocol Stop stops waiting on its caller context without closing the exact run's done boundary; a later strong Stop joins the same run.

Profile changes that drive connection reconciliation are revisioned durable mutations. `Update` returns the exact committed snapshot and rejects stale revisions; field-local websocket persistence reloads and merges only its target field after a conflict. Runtime/config rollback and delete use the same revision plus per-ID runtime generation. The revision must advance on every write, including compensation, so an ABA sequence whose user-visible value returns from A through C back to B cannot make an older B-to-A rollback appear current. A stale request may neither overwrite a newer server configuration nor restore its old runtime over the newer winner.

## 3. Busy-turn continuation

For runtimes that cannot accept stdin while a turn is active, `agentteam daemon` may retain local continuation state in memory for the current live Process when work arrives during that same active turn. Continuation must be scoped to concrete payloads the daemon accepted, must preserve ordering, and must deduplicate against daemon delivery identity so already-surfaced items are not replayed.

Some runtimes can accept active-turn input into a runtime-owned pending queue before the next model boundary. This is only a freshness surface when the runtime later emits a correlated surfaced / observed acknowledgement for the same daemon delivery identity. Transport success alone is not enough for freshness: until the surfaced acknowledgement arrives, the daemon must keep the message outside the local continuation inbox, record only a held freshness hint, and must not advance the observed target frontier or mark the message surfaced. For human activity UX, however, transport-accepted input should render the same concrete `prompt_input` activity that direct delivery renders, without exposing queue / pending / boundary wording; the row is marked as a background delivery notice so it does not replace the runtime's current command/activity state. When the surfaced acknowledgement arrives, the daemon emits a separate lightweight agent-ack activity without repeating the message body, then advances freshness. If the runtime cannot produce the acknowledgement for this session or explicitly rejects the input, the daemon falls back to the local continuation inbox and automatically retries at a safe boundary. Transport-accepted pending input does not fall back merely because a short wall-clock ACK timeout elapsed while the runtime is still busy. A long duplicate-safe reconciliation safety ceiling may terminally fail ownership without replay when progress can no longer be proven.

Runtime-specific ack identities are part of this contract, not best-effort UI hints:

- Claude writes the daemon delivery id as stream-json `uuid` and waits for `--replay-user-messages` to replay a matching `type=user,isReplay=true,uuid=...` item.
- Codex app-server sends routed `turn/start` / `turn/steer` input with `clientUserMessageId` and waits for the streamed `userMessage.clientId` echo.
- OpenCode does not declare active-turn delivery. While a run is active, the daemon retains the body in its durable FIFO. At an authoritative idle boundary it sends one `prompt_async` with a fresh daemon-controlled OpenCode-compatible monotonic `messageID`. The matching SSE `message.updated` user event is only persistence evidence; the normal surfaced acknowledgement is the later assistant message whose exact `parentID` equals that user message id. The driver installs a universal Runner-admission reservation before the HTTP mutation and keeps it until `busy` / `retry` or that exact-parent assistant proves the new Runner. Old-turn tool/part/delta or mismatched assistant activity cannot admit a fresh reservation. After admission, the driver also maintains generation-scoped current-Runner user/assistant/compaction/summary message-id sets: an assistant must parent to the current prompt, current compaction, or a source-bound synthetic/replayed continuation, and each tool/step/text part must reference an admitted assistant. Numeric message-id ordering is not a correlation proof because OpenCode's generator can move backward on wall-clock rollback. Same-session or logical-busy state is never enough, because OpenCode can publish background `compaction.prune` updates for an old assistant after Runner idle and even after the next Runner is busy; those frames must not reopen liveness or leak stale output. A 204 response, a user row, `session.error`, `/abort` acceptance, or an `idle` observed before Runner admission does not clear the reservation or authorize a second prompt. One source-derived exception preserves the same exact-parent rule across an internal transition: runAgentTeam may auto-compact a newly admitted root user before ever creating a root-parented assistant, so an admitted current-Runner `auto=true` compaction part atomically moves the pending correlation to the generated compaction user, and the summary assistant's exact parent settles the original ownership even when auto-continuation is disabled. `session.error` is emitted immediately as a nonterminal diagnostic because OpenCode can recover and enter the Runner afterward; correlated positive activity clears its terminal candidate. A generic fatal error that survives to authoritative Runner idle, any such error while an exact-parent correlation is unresolved, or an unowned error after an already-consumed idle retires the server generation before emitting terminal diagnostics: Runner can fail before creating the exact-parent assistant, and OpenCode can publish its outer wrapping error after `Runner.finishRun` has already published idle. With `compaction.auto=false`, context overflow instead follows a two-idle state machine: processor idle starts a cleanup drain, the exact completed/error assistant update proves cleanup, and only the later Runner idle emits terminal Error+TurnEnd and permits same-session summarize. Successful upstream automatic compaction publishes `session.compacted` and continues the original Runner without AgentTeam summarize; text deltas from admitted `summary:true` assistants remain internal and are never projected as AgentTeam reply text, while a later non-summary continuation remains visible; a failed bound summary assistant is terminalized as `context_compaction_failed`. If no correlated activity arrives within the bounded Runner-admission watchdog, the driver retires the generation rather than releasing the reservation. Native interrupt is rejected while either Runner admission or an exact-parent acknowledgement remains unresolved; otherwise its fence consumes both processor/Runner idles and all abort cleanup, preserving the drain through live revert until a fresh new-run busy/retry. A caller requiring strict interruption when these proofs are unavailable must replace the Process generation. Resume bootstrap scans the full effective history and, when the max user is a persisted compaction task, installs its `auto`-mode binding before the empty `prompt_async`; a restarted runAgentTeam reads that task from storage without republishing its old part, so an absent or malformed binding fails closed rather than losing summary/continuation lifecycle evidence.
- OpenCode SSE has no replay cursor. New prompt mutation is disabled before the first data frame; setup failures may retry only before readiness. Any loss after the stream is established retires the server generation instead of reconnecting. If a frame for an already-dispatched prompt is lost, the reservation/owner remains fail-closed; a fresh connection could not prove absence or authorize replay, and Process-generation replacement is the recovery boundary. Retirement atomically invalidates session/summarize generations and any in-flight summarize fence before process cancellation, so late asynchronous completions cannot reopen a replacement generation.
- OpenCode native summarize is complete only after its HTTP request succeeds and the matching generation emits all source-derived SSE proofs: an `auto=false` compaction part, its bound summary assistant, fresh `busy → Runner idle`, and `session.compacted` (published only for successful compaction). The driver keeps prompt delivery closed until the complete fence resolves; HTTP 200 without `session.compacted`, summary/session error, timeout, or SSE loss retires the generation and emits a `context_compaction_failed` Error+TurnEnd rather than fabricating `runtime_idle`. A compaction request that Process could not start must not remain a permanent driver gate; a later successfully reserved prompt consumes that stale request. On unexpected serve exit, generation-scoped SSE/summarize producers finish, translated output drains, and Wait additionally waits for Process to request EOF after handling the last line; explicit shutdown may abort this fence. Oversized translated lines fail closed instead of deadlocking the writer.

Generic sleeping backlog is not a continuation. A sleeping agent must not auto-start solely because old unread/readable messages still exist.

### 3.1 Correlated delivery FIFO and visible-reply attribution

Codex TUI keeps already-submitted-but-not-yet-committed steer requests in
`InputQueueState.pending_steers: VecDeque`; committed user-message history echoes
pop the matching FIFO head. AgentTeam preserves that multiplicity in a bounded pending
delivery table. Every surfaced concrete message retains its message ID, exact target,
canonical `TargetKey`, target-local sequence, wake token, delivery class and wake
status terminal. Duplicate message IDs are suppressed. The owner bound is enforced
before runtime admission over the deduplicated union of pending deliveries and
correlated runtime inputs that have not surfaced; durable daemon-queued owners do
not double-count. An already runtime-accepted input still acquires its row when the
echo arrives, even if that temporarily exceeds the nominal bound; dropping its
obligation is forbidden.

Visible reply settlement is target-frontier based. A successful server response with
`sent` / `already_sent`, canonical `TargetKey` and `SeenUpToSeq` settles every ordinary
surfaced row for that target with `TargetSeq <= SeenUpToSeq`. Held responses, failed
responses, local accepted sequence numbers and local freshness snapshots do not
settle rows. A different target is never affected. An exact reaction confirmation
may settle its triggering delivery through its own server/API evidence, but reaction,
system, and Workflow callback rows are signal-only and do not enter ordinary
visible-reply final-check. Context-window synthetic recovery rows carry persisted
exact targets, one independent marker per canonical target, and participate as new
visible-reply deliveries. If an older recovery marker is already `ReplayPending`, a newer frontier is persisted as a separate deterministic marker rather than mutating the launched marker; the old process boundary settles only the old marker before the newer one starts.

Final-check is an all-pending target operation: it lists each canonical target with
an ordinary unresolved obligation, marks those rows as final-check-surfaced in one
injection, and carries no local-text diagnostic state. At TurnEnd, terminal error, or
unexpected exit, the daemon emits one terminal wake result for every unresolved row
and clears the table. A higher sequence left behind by an old frontier is reset to
awaiting-check for the next boundary; it must not be stranded or mistaken for a
completed no-reply row.

### 3.2 Ambiguous outcomes and reconciliation

Once a mutating runtime request has been dispatched, a timeout, canceled response wait, broken/closed transport, malformed transport frame, or undecodable successful response may mean either “accepted” or “not accepted.” Such an owner remains `inflight` with outcome unknown, not an ordinary retryable rejection. Only a provider-specific typed session-preflight/resume rejection may authorize the documented fresh-session fallback; ordinary provider/model/config rejection remains fail-closed at this boundary.

The daemon must never replay an uncertain body based only on transport failure. Correlated streamed acknowledgement and read-only reconciliation are independent capabilities. A runtime that has an authoritative read surface may implement reconciliation with four results:

- `surfaced`: correlated runtime history contains the identity; finalize success without replay.
- `pending`: the runtime is active and the identity may not yet be materialized; keep ownership uncertain and retry reconciliation later.
- `absent`: complete authoritative history proves the identity is not present at a terminal/inactive boundary; move the original body back to `queued` and replay it in order.
- `unknown`: history is incomplete or the protocol cannot prove either result; keep ownership uncertain.

Codex reconciliation performs one `thread/read(includeTurns=true)` per attempt and searches `userMessage.clientId`. It does not resume the old thread, poll inside the driver, or mutate the driver's primary-thread snapshot. Incomplete item views cannot prove absence; an active thread with full history but no matching item is still `pending`, because accepted input may not have materialized yet. Only a complete inactive snapshot may return `absent`. Each attempt captures the ownership record's mutation generation and store-attachment epoch; a later surfaced acknowledgement or phase transfer increments that token, and a stale read result may not overwrite the live mutation.

For a current-generation ACK-only runtime such as OpenCode, an ambiguous mutation remains live `inflight(outcome_unknown)` and waits fail-closed for a late exact streamed acknowledgement; lack of a read reconciler is never interpreted as absence and never puts the body back into the replay queue. Capacity reclaim or Process-generation replacement bounds that owner. A restored ACK-only owner for which the replacement runtime cannot perform an authoritative read is terminally failed without replay, because the old stream can no longer provide trustworthy positive evidence.

Reconciliation is bounded and daemon-owned, and only the oldest live owner is reconciled at a time. The current defaults are a 10-second timeout per read and exponential delay from 100 ms capped at 5 seconds. `unknown` and read failures have a six-attempt short budget; authoritative `pending` may continue until a 15-minute elapsed safety ceiling, with a one-second materialization grace immediately after transport success. If the result remains unprovable or the corresponding budget is exhausted, the record becomes `failed` with bounded wake-failure diagnostics. A restored owner whose runtime lacks reconciliation support follows the same fail-closed terminal result immediately; a current-generation ACK-only owner follows the late-ACK rule above instead of being failed by an unrelated reconciliation scheduler kick. The safe failure mode is always to stop replaying rather than risk duplicate user input; terminalizing the head then advances the next live owner.

### 3.3 Foreground priority and bounded background retry

One daemon-owned delivery gate serializes runtime mutations, but serialization does not grant ordinary historical backlog priority. A concrete newly routed MessageID is the foreground admission lane: it is accepted durably even when older queued or inflight owners exist, and it is handed off immediately when the current runtime is safe to mutate. Historical restore or reconciliation may not turn a current wake into an empty launch or reject its durable admission. Workflow callback authority, wake lifecycle capacity, transient recovery, and final-hop ownership remain runtime-mutation fences; a fresh owner that reaches one stays queued until that exact fence releases. Ordering remains stable within a background dequeue/reconcile lane; same-ID deduplication remains global.

Known pre-accept failures and explicit runtime rejections may return a body to `queued`. Background retry uses bounded exponential backoff and terminalizes poison owners when its retry or reconciliation budget is exhausted. Capacity admission can independently terminalize the oldest live owner before the budget expires; that failed tombstone is fsynced in the same transaction as the new owner, then removed from Process-local containers and reported through wake/runtime diagnostics. Late authoritative surfaced evidence may upgrade the tombstone but cannot restore live ownership. This bounds backlog without allowing poison work to create head-of-line starvation.

Unexpected-exit continuation, an ordinary concurrent wake, and a context-window synthetic recovery may not race to publish separate Processes for one session. They share a generation-bearing start reservation and an atomic publish fence; the reservation remains in flight through the reserved owner's post-publication durable handoff. A superseded launch is stopped before it becomes visible, and only the committed Process generation may schedule exit recovery. Session Close/Delete atomically invalidates outstanding generations and retires a Process that won publication immediately before removal. A continuation retry rechecks the generation/terminal fence before scheduling and before committing terminal state. Concrete live ownership always runs before synthetic context recovery.

Restore/rewind and delivery admission share the same delivery gate. Before rollback or detached retirement, the daemon must prove that the in-memory inbox, pending runtime inputs, handoff reservation, transient retry state, and durable journal contain no live owner. Recovery cleanup transactionally clears only session/resume/recovery fields and advances the safety epoch; it preserves delivery ownership, deferred deliveries, active task, runtime policy, and unrelated launch configuration. An ambiguous compaction mutation remains non-idle and unsafe until an authoritative runtime-idle boundary; it is never reset and blindly retried.

## 4. Server-side reliability surfaces

The server-side reliability surfaces are deliberately narrow:

- `scope_fanout_outbox` records pending / leased / delivered / retry / failed / suppressed status for fanout jobs. Its payload, status, attempts, and error fields explain whether the current routed event was handed to the daemon.
- `agent_runtime_events` records current daemon/runtime lifecycle events that are already used by shipped runtime diagnostics and failure summaries.
- Read surfaces (`history`, `search`, read-check/source/attachment checks) read from message facts and visibility predicates. They do not consume delivery, advance an agent cursor, or become an offline inbox.
- Reply-thread root previews attached to daemon delivery payloads are only a projection of the same ordinary read-check/readable predicate used by root context reads. They must not reintroduce a separate `receive` visibility mode or server-side receive ACL.
- When a first-delivery reply-thread root preview accompanies a message after the first reply, runtime input states how many earlier thread replies were omitted. The count excludes both the parent-scope root and the current delivered reply; it is only a context-availability hint and does not preload the omitted message bodies.

No scope-level runtime lifecycle table and no server-side agent receive cursor are part of this model.

## 5. Diagnostics rules

Message diagnostics may explain:

- message scope and sequence;
- active membership and notification state;
- current runtime / machine state;
- `scope_fanout_outbox` payload/status/last error for the message-level fanout job;
- existing agent runtime lifecycle failure summaries when a shipped runtime diagnostic already consumes them.

Message diagnostics must not claim a durable per-recipient message ledger, hidden inbox, server receive cursor, or runtime lifecycle rows as message truth. An agent can read a message because it is visible through the scope message source and visibility predicate; it is woken because the current event was routed into a fanout payload.

## 6. Acceptance criteria

- Ordinary scope messages do not create message-backed recipient rows or scope-level runtime lifecycle rows.
- Fanout jobs are tracked by `scope_fanout_outbox` status/error/payload, not by a second runtime lifecycle ledger.
- Agent runtime lifecycle failure summaries may continue to use `agent_runtime_events` only for current daemon/runtime lifecycle behavior.
- Runtime or wake failures surface through runtime lifecycle / wake-status diagnostics, not by inserting ordinary `scope_messages` into the conversation.
- Server-side agent `/receive`, receive visibility scans, receive request-kind modes, and agent receive cursors stay removed from production code and canonical docs.
- Reply-thread root preview preload uses ordinary read-check/readable predicate semantics; it must not keep a narrower or wider receive-only ACL.
- A first-delivery reply-thread root preview reports the number of earlier omitted replies when that number is non-zero, without adding those replies to the delivery payload.
- The daemon-local continuation inbox contains accepted pending payloads only; payloads already handed to an ack-aware runtime pending queue are excluded unless they explicitly fall back after transport/session failure; accepted pending inputs do not fall back merely because a short ACK timeout elapsed while the runtime is still busy, and the inbox is never exposed as an agent pull tool.
- Busy-turn continuation deduplicates against daemon delivery identity and never turns generic readable/unread backlog into automatic startup.
- Multiple correlated steers in one runtime turn retain one pending delivery row each. Admission remains FIFO and duplicate-safe; target-frontier settlement is independent per canonical target, and final-check/terminal handling covers every unresolved row rather than only the FIFO head.
- Correlated direct runtime input advances freshness only on a matching runtime surfaced / observed acknowledgement, never on transport-level accept alone.
- Late runtime surfaced acknowledgements for already-observed target seqs are consumed for cleanup only. Mismatched-session acknowledgements are not treated as a surface; the daemon removes the stale pending entry and returns the original message to the local fallback queue when it is not already stale. Neither case may emit duplicate `prompt_input` rows, restart wake status, or move the observed frontier.
- Every accepted handoff has one durable daemon ownership state. `queued` may be replayed; `inflight` may be replayed only after authoritative `absent`; `terminal(surfaced|failed)` is non-live evidence.
- `agent:deliver` reaches only the current accepting Process and may durably queue behind its active turn; it rejects without slash-command handling, profile/runtime synchronization, history/task mutation, start reservation, Process spawn, replacement, or restart when that Process is absent or non-accepting. `agent:start` alone owns the fallback Process lifetime.
- Unexpected Process/daemon boundaries restore only the oldest live ownership record. All restoration launches are no-prompt; queued ownership uses a correlated post-publication handoff, inflight ownership is reconciliation-first, and explicit Stop/Sleep does not auto-respawn or accept a new handoff after stopping begins. A concrete current wake always remains the foreground launch/handoff; historical restore uses a no-prompt background continuation only when there is no current wake.
- Ordinary starts and exit continuation share one generation-bearing session reservation spanning launch, atomic publish, and reserved-owner handoff; Close/Delete invalidates the generation, and at most one Process generation can own a session or deliver its queue head.
- Every stale/freshness suppression of a live owner first writes durable terminal evidence. Per-message generation checks and terminal monotonicity prevent older queued/inflight/reconcile writes from regressing a newer generation or terminal state.
- Ownership, resume safety, identity, runtime policy, and deferred continuation update `.daemon-state.json` transactionally without whole-state lost updates; handoff durability includes temp-file, renamed-state parent, and newly-created workdir-chain sync, serialized ownership caps are enforced on exact JSON bytes, and provider diagnostics are bounded.
- Raw stdin persists `inflight(dispatching)` before physical write. An explicit zero-byte write can retry only after durable `queued`; any partial/ambiguous frame becomes terminal `failed` and retires the stream/child rather than appending or replaying.
- Ambiguous post-dispatch transport outcomes never trigger blind request retry. Reconciliation and queue retries are bounded within their background lanes, while new MessageIDs remain foreground-priority and capacity admission always has a reclaim path.
- Runtime MCP inbox reconciliation is admitted through the lifecycle-mutation barrier, commits terminal `surfaced` before memory removal, is rejected after Stop closes admission, and cannot regress newer ownership.
- Process admission lifetime cancellation covers gate wait, rendering, persistence, and runtime mutation. Concurrent public Stop/Sleep calls wait for one admission strong completion; gate-held recovery retirement is explicitly non-reentrant, and unexpected exit drains admitted work before replacement. Child exit (`WaitForExit`) and `onExit` completion (`WaitForExitCallback`) are separate fences; global strong shutdown joins both without making Process Stop itself callback-reentrant.
- Codex app-server and Process translated-event lines are accumulated from bounded `ReadSlice` fragments with one hard `32 MiB` complete-line limit. Codex translated output is generation-scoped, charged by final encoded wire bytes, lossless under cancellable backpressure, abortable before replacement fencing, and fatal on oversize/encode/short-write failure.
- Runtimes with explicit turn lifecycle change root liveness only on authoritative lifecycle boundaries. Completed-turn root activity cannot reopen an idle Process, while correlated input acknowledgements and observability metadata remain consumable.
- Codex session resume uses only the current RuntimePolicySnapshot and semantic `baseInstructions` + `developerInstructions` fingerprint as cross-daemon guards. `SessionResumeSafe` remains durable for delivery ownership/reconciliation, but an unsafe value does not clear or block the provider thread; only a typed runtime invalid/not-found authority may trigger one structured fresh-session downgrade.
- Recovery cannot cross a live ownership boundary, cleanup preserves the journal, stale reconciliation cannot overwrite a newer live mutation, and ambiguous compaction remains unsafe until authoritative idle. A compaction upstream-idle latch is consumed only by the matching same-generation completion and emits runtime idle exactly once.
- Dispatcher Close/Stop cancel and strongly join session-start reservations,
  ownership retries, Memory watcher owners, Exec credential cleanup, and Process
  callbacks. Only `StopForShutdown(ctx)` is bounded/cancellable: one
  caller/default five-second deadline covers concurrently initiated channel,
  session, callback, credential, and owner joins; it has no unbounded aggregate
  wait or N-times-per-process timeout and returns at the deadline while
  ignored-cancellation cleanup continues best-effort, with late publication
  still rejected.
- Server-connection shutdown closes admission before session teardown, uses per-ID mutation generations and manager-owned runtime contexts, compensating-stops publication losers, and keeps bounded drain cleanup joinable by a later strong waiter. Every Session Link closes/cancels/joins its exact pumps and subordinate owner snapshot and performs a post-join queue drain before reconnect; no old handler, reporter, ticker, refresh, elastic link, or token request crosses generations.
- Server profile mutations advance a durable revision and return their exact committed snapshot. Runtime/config rollback, field-local connector persistence, and delete reject stale or ABA revisions; a failed older request cannot overwrite or restart over a newer configuration.
