# Codex Root/Child Thread Ownership And Outbox Recovery

## Why This Boundary Exists

One Oysterun Codex session can contain two kinds of provider work at the same
time:

- the **root turn** that owns the user's current Matrix message; and
- one or more **child turns** created by Codex subagents to help that root
  turn.

Codex app-server reports both kinds through the same process. Every relevant
lifecycle notification includes an exact `threadId`. Oysterun must therefore
classify ownership before changing delivery state.

The root thread is established only by a successful Host-owned
`thread/start`, `thread/resume`, or `thread/fork` response. It is immutable for
the lifetime of that adapter. A later `thread/started` notification can confirm
the same root or describe a child, but cannot replace it.

## Events That May Change Root Delivery

Only an event whose `threadId` exactly matches the established root thread may:

- set or clear the active root turn;
- enter Host2 `turn.started` correlation;
- complete or fail the active outbox row;
- clear the root waiting state;
- drain the next queued Matrix message; or
- become the target or success proof for Interrupt.

Root completion must also match the exact active root turn ID. A stale,
duplicate, malformed, or mismatched completion is diagnostic evidence, not
permission to complete whichever message is currently active.

Child turns are kept in a bounded in-memory ledger. Child messages, tools, and
controls may remain visible and usable, but carry
`provider_thread_scope: "child"`. SessionManager excludes that scope from root
outbox correlation. Child completion cannot clear the root, emit root
completion, or start the next queued message.

## Queue And Race Behavior

`turn/start` has two observations: its JSON-RPC response and a
`turn/started` notification. Either may arrive first. Both paths converge on
the same root turn ID; the first announces the root start and the second is an
idempotent duplicate. A different root turn remains a fail-closed P075
identity conflict.

While the root is active, later user messages remain queued. Exact root
completion drains the queue once. If `turn/start` fails after no provider
acceptance, the adapter removes only its transient payload; the Host outbox
retains the actionable failed/ambiguous row and owns Retry or Skip.

## Interrupt Truth

Codex Interrupt targets only the established root thread and active root turn.
The Host waits for the app-server result before saying that Interrupt was
accepted. `no_active_turn`, already-terminal, rejection, transport failure,
and timeout are returned as non-accepted outcomes. SessionManager then restores
the delivery state to ready and re-arms safe queue progress. A duplicate
request cannot start a parallel provider interrupt.

## Bounded Diagnostics

Normal lifecycle data exposes ownership through enums, booleans, and counts:

- `root_turn_state`
- `active_root_turn_present`
- `active_child_count`
- `outbox_wait_reason`
- `interrupt_state`

New child notices redact provider thread and turn identifiers. The child
thread ledger retains at most 64 threads and 16 active turns per child thread;
terminal child turns are removed immediately. There is no periodic provider
poll and no new transcript owner.

## Verification

The focused P098 test uses real protocol-shaped fixtures with both `threadId`
and turn ID. Its causal sequence is:

```text
root starts
-> child starts and completes
-> root identity remains active
-> root completes
-> queued message starts once
```

P075 remains the authority for same-root duplicate start idempotency and
different-root conflict handling. P055 remains the authority for bounded
lifecycle/outbox snapshots. Real-provider and browser human-path verification
must use an isolated task stack and synthetic content; it must not mutate the
reported production session.
