# runtime

## Atomic remembered approvals

`api.approve_escalation_with_fact_at` commits a captured approval and one
`ReservedFactChange` through the same writer transaction. Both the escalation
sequence and prior fact sequence are expectations; either conflict writes
neither value and returns `RaceLost`. The helper accepts only a reserved fact
key distinct from the decision key. Runtime treats grants and the fact payload
as opaque JSON; client policy validation owns their meaning. The existing
`approve_escalation_at` remains the one-call door. See protocol 041.

`api.edit_reserved_facts` is the same guard without a decision: a list of
`ReservedFactEdit` (`ReservedFactSet` of a `ReservedFactChange`, or
`ReservedFactRemove` of a cell read at a sequence) applied in one transaction,
each under the sequence its caller observed. A cell that moved loses the whole
edit as `RaceLost`; nothing is retried, because the caller's read was a
question put to a person. Every key must be reserved. The remembered-permissions
forget is its one caller (protocol 073).


## Streamed response handoff

Generation and poll `RequestSpec` values carry the response entry already
reserved by the effect intent. The gateway uses that identity to let the
terminal retain its bounded streamed answer until the exact durable entry is
visible; request completion alone is not a presentation handoff. See
`protocol-change/036-stream-response-handoff.md`.

## Orphan recovery through the tool surface

`effects.ToolSurface.recover: Option(fn(ToolRun) -> Recovery)` lets a surface
that keeps its own record of finished calls (a remote executor's ledger) say
what became of an orphaned call: intent durable as `CallEffectPending`, no live
effect in this incarnation. `None`, every local surface, leaves `tool_key`
resolving to `planner.ObservedToolOrphaned` exactly as before. With `Some(f)`,
`tool_key` returns `KeySpawned` after `spawn_recovery` starts an effect, built
by the same `spawn_tool_worker` as `spawn_tool`, under the call's own
`ToolEffect` token. So `has_live_tool`, `KeyWait` and the abort path treat it as
the tool it stands in for. It calls `f` with the run a replay would use: the
effective arguments from `op.tool_args`, the replay policy persisted in the
`CallEffectPending`, no grants. `f` may block; the driver is not parked on it.
The answer arrives as `ToolRecovered` and `recovery_done` maps it without any
change to `machine`: `Recovered(outcome)` settles through `settle_tool`, the
path a fresh run's `ToolDone` takes; `OutcomeUnknown` is the orphan observation
with `replay_still_safe: False`; `NotStarted` is that observation with `True`
when the persisted policy is `ReplaySafe` and the current registration still
says so, otherwise a `ToolFailed("the call never reached the executor and did
not run")`. A recovery effect killed by abort, or one that dies unanswered,
takes `effect_exit`'s ordinary tool path. `runtime` reads the argument key
through `machine/internal/build.tool_args_key`, the one place that format is
spelled. See `protocol-change/078`.

## Purpose

The orchestration plane's live half: the OTP tree that turns an open
session into a running one. One StorageWriter owns the commit path, one
strand driver per strand interprets the pure machine's actions against an
injected effect seam, and `runtime/api` is the session-facing surface —
open/recover, prompt, steer, follow-up, abort, close, plus the multi-strand
operations (create a subagent strand, message another strand, await its
result), the `fact.*` blackboard, and durable escalation decisions. WP-E,
extended by the M3 runtime wave.

## Key Types

- `runtime/api.FactHandle` is an opaque internal host capability containing
  only the session writer Address. `fact_handle` projects it before a host
  retains a fact callback. `fact_cell_with` and
  `put_reserved_fact_expecting_with` share the Runtime doors' decoding,
  reserved-key check, conditional transaction and error mapping. Each request
  resolves the current writer; replacement preserves the handle, while
  namespace retirement makes it unavailable. A writer death after admission
  still crashes the caller when the commit's acknowledgement is uncertain.
  Lease loss remains `SessionStolen` and stale expectations remain
  `FactConflict`. The handle carries neither Effects nor a cached PID.

- `effects.failure_observation` derives diagnostic identity from the captured
  `RequestSpec`. Provider effects retain their own deadline or stop through
  cancellation acknowledgement; the inner custodian records only cancellation
  receipt. `settle_failure` persists these facts beside any `retry_after_ms`
  in the existing assistant diagnostics field. Successful messages are unchanged.

- `runtime/api.Runtime` — the live session handle: the `SessionTree`, the
  `Session`, the session's canonical `SessionId` (`api.session_id`), the
  `Effects` record, the strand name, and the `RunSettings`. `api.open`
  mints that id on a session that has none — the one place every session,
  file-backed or in-memory, forked or fresh, passes through on its way up
  (`protocol-change/008`).
  It addresses **one strand at a time**; `api.on_strand` rebinds the same
  tree to a sibling, so every operation works for subagents too.
- `runtime/api.{Drain, draining, drain_within}` — everything `api.drain`
  reads, and nothing else: the tree it addresses strands through and the
  session it reads their state from. `drain` is `drain_within` applied to
  `draining`, and the contract is one shared budget, every abort requested
  before any terminal is awaited, and each strand awaiting the operation it
  was observed running. The projection exists for holders that must be able
  to drain a session later and do nothing else with it — the daemon's session
  manager keeps one per admitted session — because a held or copied `Runtime`
  pays for a full copy of `Effects`, and neither field here is duplicated per
  session.
- `runtime/api.open_published` and `runtime/supervisor.start_published`
  expose an internal publication boundary before recovery. The first
  child-start callback publishes the exact root and direct drain witness
  before the writer, factories or drivers start. Refusal prevents recovery;
  restarts below the significant temporary drain ledger do not republish.
  The callback only transfers custody: runtime calls and synchronous close
  would block startup. The caller is an exit-trapping resource owner and
  must establish surviving custody itself; the API does not create it.
- `runtime/api.{CreateStrandError, Delivery}` — subagent creation, and
  whether a cross-strand message landed as a `Steered(entry)` on an open
  run or `Started(operation)` on an idle strand. `create_strand` is
  `validate` + `seed_strand` + `adopt_strand`; `adopt_strand` is exposed
  on its own because a crash between the seed commit and the brief commit
  leaves a strand nothing else can finish.
  `seed_strand` refuses a name that begins with `parent_reference_prefix` (`^`)
  as `InvalidStrandName`, because a delegate session's children name a parent on
  another orchestrator as `^main` in their lineage and the reference must never
  be a strand (protocol-change/084). The `delegate/` fact prefix is the fifteenth
  reserved corner: the Agency keeps `delegate/ended/{operation}` there.
- `runtime/api.ApiError` — why an operation failed. `RuntimeUnavailable`
  means the writer address had no live binding before a request was sent;
  a caller can retry later. This differs from `CommitFailed(error:)` and
  `SessionStolen(held_by:)`. The second is `tx.LeaseLost` classified at
  the admission surface (`commit_failure`): another writer owns the
  session, so nothing this runtime commits can land and the remedy is to
  reopen, not to retry. The admission ladder finishes on it rather than
  retrying — `retry_admission` decrements only on `Retry`, so spending
  four attempts against a fence that refuses all four would report
  `RaceLost` and name the wrong cause.
- `runtime/api.{put_reserved_fact, put_reserved_fact_expecting,
  delete_reserved_fact, delete_reserved_prefix, edit_reserved_facts,
  reserved_facts, reserved_fact_key}` — the
  harness-only door to the reserved corners of `fact.custom`, and the
  predicate naming them. Deliberately disjoint from `put_fact`/`facts`,
  which refuse and hide the same keys. `put_reserved_fact_expecting` is
  `put_fact_expecting`'s compare-and-set on the reserved side — the
  `expected: None` form is how a harness component mints a record under
  its own prefix exactly once instead of read-then-blind-write (#162) —
  and `delete_reserved_fact` is the blackboard's one delete door, reserved
  only, so a retired record leaves no tombstone for every later prefix
  scan to discard (#164); nothing a model reaches can make a cell vanish.
  `delete_reserved_prefix` is that delete in its **set** form and not a
  loop over the singular one: it lists the prefix, commits one `core/tx`
  of `DeleteRegister` writes so the set is never half retired, and
  answers how many there were — the only observation left once the cells
  are gone. Zero commits nothing. It refuses an unreserved prefix like
  every door on this side of the reservation, so it can never become a
  bulk delete over the model-writable blackboard, where no delete door
  exists at all. **A prefix is a path and the caller owns that
  discipline**: this door deletes what the store matches, so a prefix
  stopping mid-segment (`schedule/fired/main`) reaches `mainly`'s cells
  too, and callers pass prefixes ending in the separator
  (`client/schedule.strand_prefixes`) because only a namespace's owner
  knows where its segments end. Its caller is
  `client/scheduleseam`, which retires a cancelled schedule's fired-marks
  with it and a settled strand's whole scheduling footprint on a run
  end.
- `runtime/supervisor.SessionTree` — a rest-for-one supervisor over six
  children in order: the significant temporary **drain ledger**, the
  restartable strand **registry**, the **StorageWriter**, the
  **StrandSupervisor** (a `factory_supervisor` of strand drivers, one per
  strand, restarted individually), the **subagent StrandSupervisor** (a
  second factory with its own tolerance, for strands a host's `subagent`
  predicate names), and the **strand booter** (a worker whose start lists
  the `strand.*` registers and starts a driver for every strand found,
  routing each to its factory). The writer, registry and drain ledger bind
  reference addresses in `SessionTree.namespace`; its routing-only lifetime
  ends when the root exits. It ends *only* there: the namespace owner is
  spawn-linked to whoever assembled the tree, and `start_published`
  unlinks it alongside the root once publication has acknowledged
  custody, so a transient builder's abnormal exit can no longer strand a
  fully assembled session behind addresses that resolve to nothing. `SessionTree.drains` retains the ledger's direct
  subject independently, because routing death cannot certify effect drain.
- `runtime/supervisor.shutdown(tree, grace_ms:)` — the orderly stop
  `api.close` is built on: children terminated in reverse start order
  with reason `shutdown`. The grace bounds the `sys:terminate` handshake, but
  the drain ledger then keeps the root alive until every provider reaper has
  stopped; the writer lease is not released on an inconclusive drain. The tree
  unlinks from its starter, so a host that wants to hear about its death
  monitors `SessionTree.supervisor` (`client/host` does).
- `runtime/lineage.{Lineage, CallSite}` — the durable ledger of who
  spawned whom, one `fact.custom` cell per spawned strand under the
  reserved `lineage/` prefix: parent edge, depth, the minting call site,
  the brief operation, the child's tools, an **absolute** deadline, the
  detach flag, and the durable reap mark. `is_descendant` is the walk the
  addressing rule is decided by, and it fails closed.
- `runtime/registry.Message` — the strand-incarnation registry actor: strand
  name ↔ its driver's reclaimable `weft/registry.Address`. This actor owns
  the reference namespace; repeated strand allocation creates no atoms.
  It also retains the current typed OTP handles for the `Primary` and
  `Subagent` factories. Their child-start callbacks publish before the booter
  runs, and lookup rejects a dead predecessor until replacement publishes.
  The factories themselves are unnamed.
  Reaper claims live only
  in `runtime/internal/drain_registry`; keeping them out of this restartable
  actor prevents a name-registry restart from erasing ownership barriers.
- `runtime/internal/drain_registry.Message` — the session-local actor owning
  each logical strand's complete unadjudicated reaper chain. It precedes the
  restartable registry and is a significant temporary child: a normal reaper
  `Down` retires a generation, while a `Down` that says the reaper destroyed
  its ownership set kills the ledger and stops the session instead of
  inventing an empty ownership history. `noproc` is the exception, and
  `drain_registry.Verdict` is where the three are told apart: it is what a
  monitor answers about a pid that was already gone, never a reason a process
  exits with, and both the claim and the monitor it installs travel as
  signals, so a reaper that ends before the monitor request lands — after
  its claimant was released as readily as before — can be met no other way.
  A weft scope holds itself alive until every effect it adopted has exited
  and says `weft_drain_proof_lost` when it cannot, so a pid met as `noproc`
  left nothing running: it retires like a normal exit.
- `runtime/internal/provider_custodian.Prepared` — one provider request as a
  parked owner plus the one-way `begin` permit that releases it. The worker
  behind it is a `weft/state_machine`, `Parked → Forwarding → Cancelling →
  Draining` over data that is `Awaiting` before the permit and `Serving`
  after it. `Parked` selects only the permit and the custodian's stop; the
  step that grants the permit installs the real selector with
  `sm.with_selector`, because the inner stream's events subject is created
  by that handler and must be delivered to this process. Its cancellation
  grace is the `Cancelling` state's **state timeout**, so it is armed by the
  move in, cancelled by the move out, and never re-armed by the `keep` that
  every late delta produces.
- `runtime/writer.Message` — the writer actor's mailbox; `writer.Event` is
  the `Committed(ordinal, seqs, ts)` published to subscribers.
- `runtime/writer.Subscriber` distinguishes `Direct(Subject(Event))`
  observers from `Routed(Address(Event))` restartable services. Publication
  sends a lossy hint without executing subscriber callbacks in the writer.
  A routed destination is resolved afresh; an absent binding never fails a
  commit or queues a hint for a future incarnation.
- `runtime/strand_runtime.Message` — the driver's mailbox.
- `runtime/api.Options.logger` / `runtime/strand_runtime.Options.logger`
  — the injected `telemetry/log.Logger` every strand of the session
  logs through (§0.2; `log.discard()` by default, so a runtime nobody
  configured is silent). `strand_runtime.start` scopes it to the strand
  it is starting, and each dispatched effect narrows it again to
  `{op, step}`.
- `runtime/effects.Effects` — everything the driver does to the world, in
  one injected record: `clock`, `entropy`, `timers`, `provider`
  (`ProviderSurface`), `tools` (`ToolSurface`), `hooks` (`Hooks`).
- `runtime/effects.{RequestSpec, ToolRun, ToolOutcome, Clearance,
  ExecutionMode}` — the shapes the seam is written in; `conformance/wiring`
  and `client/demo` fill them with the real gateway, broker, and registry.
  `ToolRun.strand` carries the dispatching driver's own durable name, so a
  tool can be judged against its lineage rather than against a name a
  model claims. `ToolRun.grants` carries what *this call's* clearance
  consumed — the far end of the channel `ClearanceQuery.grants` opens,
  and the reason an approval can change a policy decision at all. A
  replayed call carries none.
- `effects.Hooks.context` — the phase-3 slot: `fn(OpId,
  List(AgentMessage)) -> List(AgentMessage)`, applied in
  `strand_runtime` to a generation attempt's projection just before the
  request goes out. The list is transient, never a durable entry, which
  is what keeps the slot inside the replay rule the rest of the record
  is held to: a crash before the consuming commit re-projects and
  re-transforms. `default_hooks` supplies identity; the only producer is
  `client/extension/hooks`, the extension hook bus.
- `runtime/hooks.Registry` — the one seam production wiring, tests, and the
  simulation runner all build their `effects.Hooks` through: safe defaults,
  a pipeable setter per slot, `build` at the end.
- `runtime/hooks.Projected` — what a compaction decision is taken from:
  a strand's projected `messages`, how many leading ones a compaction at
  the head of the branch `carried` over (its summary plus its
  retained-tail copy), and that compaction's `previous_summary`.
  `hooks.project` reads one from a session; `hooks.uncompacted` wraps a
  plain message list.
- `runtime/hooks.with_tool_references` — enables retained-tail result
  references only after the host has authorized exact history retrieval. It
  preserves the frozen cut and replaces only successful text-only results of
  at least 4,096 bytes when the bounded reference is smaller. Provenance is the
  original durable entry UUID; copied compaction tails resolve through their
  parent branch and fail closed on positional or projection mismatch.
- `runtime/hooks.{estimate_message, context_tokens, preparation,
  threshold, overflow}` — the compaction arithmetic: characters over
  four for a not-yet-priced message, pi's newest-durable-usage fold for
  a whole context, the one preparation builder, and the two signals
  built over them.
- `runtime/effects.ThresholdQuery` — `{operation, strand, messages,
  carried, previous_summary}`, asked at every checkpoint boundary. It
  carries the strand's projection rather than asking the hook to read
  one: the driver has projected the branch for the step already, and a
  hook that projected again on its own was doing the step's largest
  piece of work a second time (issue #359). The three trailing fields
  are `hooks.Projected` spelled out, because `effects` sits beneath
  `hooks`.
- `runtime/effects.OverflowQuery` — `{operation, strand}`, asked once
  when a settlement classifies as a run's first context overflow. It
  names the strand because a preparation is built from a *strand's*
  durable projection, and one `Effects` record serves every strand of a
  session.
- `runtime/escalation.{Escalation, CallScope, Action, Claim, Status,
  claimed, scoped_to, bound_to}` — the durable record of a broker denial
  awaiting a decision, its decision, and its single consumed
  re-execution. `CallScope` is the exact call identity (`{operation,
  strand, step, source index, call id}`) an approval is attributed to;
  only the clearance whose coordinates match can spend it. `Action` is
  what the claimant would actually *run* — `{tool, digest, preview}`,
  all three computed by `client/escalate` and stored here opaquely —
  and it is what consent binds to (#65): the record id is a digest of
  the *want*, which says nothing about the command behind it.
  `claimed` is the transition that moves a record to a *later* call
  raising the same want: pending moves and refreshes its denial and
  action, approved moves and keeps its grants **only when the claimant's
  action digest matches**, and everything else — a mismatched action, a
  rejection, a consumption — re-opens as a fresh pending question with
  no grants. Re-opening costs a human an answer, so a record counts its
  `asked` questions and `claimed` answers `Exhausted` past the caller's
  `max_asks` rather than re-opening again (#66). A re-opening after a
  consumption restarts the count at one, since an approval that ran once is
  not a question the model is wearing down. `bound_to` is
  `scoped_to`'s sibling and fails the same way: a record naming no
  action matches nothing.
- `runtime/api.{escalations, escalation, escalation_cell,
  escalations_below, raise_escalation_for, claim_escalation,
  approve_escalation, deny_escalation, consume_escalation,
  consume_escalation_at}` — the durable escalation surface.
  `escalation(runtime, id)` is the bounded point lookup a parked call
  polls on (`client/escalate`), rather than listing and decoding the
  whole reserved prefix once a second; `escalation_cell` is the same
  read carrying the seq, for a caller that will decide something from
  what it read; `escalations_below` is the bounded count question a
  capped raiser asks; `claim_escalation` is the raise-or-take-over door
  parking uses, taking the claimant's `Action` and a `max_asks` bound
  and answering `Claimed` or `Exhausted`, where `raise_escalation_for`
  stays write-once (and actionless) for callers that mean "record this,
  once". The two consume doors differ in exactly one thing:
  `consume_escalation` re-reads and consumes whatever it finds, which is
  right for a host holding no earlier read, while
  `consume_escalation_at` CASes at the seq the caller checked, which is
  what a caller that checked a scope or an action owes its own check
  (#68).
- `runtime/api.{Mark, steer_marking}` — a steer admission and a
  write-once reserved claim in **one transaction**. The door an injector
  needs and `steer_quietly` cannot give it: an injector that must act at
  most once has to make "the message is queued" and "the claim is spent"
  one durable fact, because the two orders fail in opposite directions —
  marking first loses the injection to a crash in between, queueing
  first double-injects after one. The mark's own stale expectation is
  told apart from the operation-state seq the admission ladder retries
  on: a lost race against the strand reloads, a taken mark answers
  `FactConflict` at once. `client/rulescan` is the only caller.
- `runtime/api.{accept_quietly_marking, send_to_strand_marking}` —
  `steer_marking`'s argument carried onto the *other* admission door.
  `accept_quietly_marking` folds the same write-once `Mark` into a fresh
  run's acceptance transaction instead of a steer's — `marked` and
  `commit_admission` are already generic over `core/tx.Tx` and
  `Option(Mark)`, so this is `accept_request` threading the mark it
  already had nowhere to go, not a new commit shape.
  `send_to_strand_marking` is `send_to_strand`'s reconciliation (steer an
  open run, fall back to a fresh run on an idle one, retry the steer if a
  run opens in the gap) with that same mark carried through whichever
  path lands. `client/schedulescan` is the caller: a `wake = true`
  scheduled heartbeat is the first caller ever needing exactly-once
  admission on the fresh-run path, because nothing before it was allowed
  to wake an idle strand at all.
- `runtime/api.{FactCell, fact_cell, put_fact_expecting}` — the
  compare-and-set half of the blackboard: read a cell with the seq of
  the write that put it there, then write only if it has not moved. The
  only way a read-modify-write over a shared cell is expressible;
  `put_fact` remains last-write-wins and now says so.

## Relationships

- **Depends on**: `core`, `storage`, `session` (`runtime/hooks.project`
  reads a strand's durable projection straight from the session store),
  `machine` (drives
  `next_action` and reuses its codecs and queue helpers), `telemetry`
  (the injected logger and the correlation context — a leaf package
  over `core`, so this edge adds nothing transitively), `provider`
  (`stream.StreamHandle` and `retry.classify` — the spec DAG §0.1 writes
  `E → A,B,C,D`, so this provider edge is a divergence worth knowing
  about), `gleam_erlang`, `gleam_otp`, `weft` (the strand driver is a
  `weft/actor`, adopted for `continuing` — the recovery barrier as a
  guaranteed-first message; the parked provider request worker is a
  `weft/state_machine`, adopted for the state timeout that *is* the
  cancellation grace; see loom#159 for the adoption plan).
- **Depended on by**: `client` (the gateway dispatches protocol commands
  onto `runtime/api`), `conformance` (the simulation runner drives sessions
  through `runtime/api`).
- **FFI**: two declarations, both in `runtime/internal/ffi_sup` over
  `runtime_ffi.erl`. `terminate_supervisor` is `sys:terminate/3`, the only
  graceful stop an OTP supervisor offers a process that is not its
  parent, and the one thing `gleam/otp/static_supervisor` does not wrap.
  `send_to_pid` is a bare `!` to an already-resolved `Pid` — what
  `writer.publish` uses so a named subscriber's name is looked up exactly
  once (issue #43); no wrapped `Subject`-taking function in `gleam_erlang`
  reaches a specific pid without re-resolving it. Nothing else: time,
  entropy, and timers arrive through `Effects`, and everything effectful
  is injected, which is why the whole plane runs under a logical clock in
  simulation.

## Traffic

- **Actor messages**
  - `writer.Message` (all calls except `Subscribe`/`RenewTick`):
    `Commit(tx, reply)`, `GetEntries`, `GetRegister`, `ListRegisters`,
    `ScanBranch`, `ScanUsage`, `Stats`, `Subscribe(subscriber)`,
    `RenewTick`. Senders: `runtime/api`, `runtime/strand_runtime`, and the
    conformance harnesses — except `RenewTick`, which nobody sends. It is
    a `weft/actor` **periodic timeout** armed by `writer.start` when
    `Session.lease_interval_ms` is `Some`, so the writer is a `weft/actor`
    rather than a `gleam/otp/actor`. The tick fires into a subject weft
    creates inside the writer's own process and never registers under a
    name, which is what makes a predecessor's timer unable to reach a
    replacement claiming the same registered name; the writer used to
    carry a private subject in its state for exactly that defence, and no
    longer does. A renewal that fails stops the writer abnormally, so the
    rest-for-one supervisor re-runs the open path.
  - `strand_runtime.Message` (all casts): `AwaitPredecessors(resolution)`
    (the guaranteed-first barrier message weft's `continuing` injects at
    init — opaque, so nothing else can construct it), `Nudge` (the
    doorbell), `PollTick(generation)` (the checkpoint poll, which also grants
    one deferred poll permit; the generation names the arming it belongs to,
    and a tick from a superseded arming is dropped), `RetryDue`,
    `RequestAbort`,
    `ProviderDone(token, terminal)`, `ToolDone(token, outcome)`,
    `EffectExit(down)`. Callers use the stable
    address resolved to the current subject, while the reaper, effect workers,
    and timers use a
    private direct subject bound to this driver incarnation. The split prevents
    a predecessor's late result from resolving the stable name to a replacement
    and settling newly replayed work with the same durable token. The ledger
    acknowledgement arrives on a dedicated subject the `AwaitPredecessors`
    handler blocks on; every message that lands meanwhile — aborts included —
    queues in the mailbox and is handled once the barrier opens, so no effect
    is driven before it and no intent needs hand-carrying through a gate.
  - `registry.Message` (all calls): `Ensure(strand, reply_with)` — mint or
    return the reference address a strand's driver binds — plus
    `Lookup(strand, reply_with)` and `Known(reply_with)`.
    `PublishFactory(kind, factory, reply_with)` replaces one typed factory
    handle; `LookupFactory(kind, reply_with)` reads it. The root's child-start
    callbacks are the only publishers, so replacement follows start order.
    Senders: `runtime/supervisor`'s strand factory and booter, and
    `runtime/api` when it rings a doorbell or addresses a sibling strand.
  - `runtime/internal/drain_registry.Message` (call):
    `Claim(strand, reaper, reply_with)` atomically publishes the new
    incarnation, snapshots its predecessors, and releases the caller only once
    the ledger's original monitors have proved that entire snapshot drained
    normally. The call is made by a *claimant* the reaper's scope adopts as
    a leaf owner before releasing it, naming the scope as the pid to monitor
    (`strand_runtime.claim_through`); the scope cannot settle while the
    claimant is inside the call, so the ledger's monitor always lands on a
    live pid — a claim made from the driver instead let a driver killed
    mid-claim leave the ledger monitoring a scope that had already drained.
    That ordering is the claim protocol's own guarantee and not the ledger's
    safety net: the monitor is still a signal in flight when the claim is
    answered, and the ledger reads a `noproc` claim as a departure whatever
    the claimant did. The driver blocks
    on the claimant's verdict in its guaranteed-first `AwaitPredecessors`
    handler; meanwhile an abort simply queues in the mailbox behind the
    barrier. Sender: `runtime/strand_runtime` through the closure the
    supervisor injects.
  - `writer.Event.Committed` fan-out uses `writer.Subscriber`: direct
    subjects for incarnation-local observers, reference addresses for
    restartable services such as `client/gateway.commit_forwarder`.
- **Commits**: every post-boot `Tx` in the session flows through
  `writer.commit`, whose mailbox *is* the serialization order. The driver
  commits exactly what the planner hands it — `Transition`/`Finish`
  transactions and `Dispatch` intent transactions — and treats
  `StaleExpectation` as "reload and re-plan".
- **Registers**: the driver loads `strand.config`, `strand.state`,
  `strand.leaf`, `op.meta`, `op.state`, and the sibling payload registers
  (`op.tool_args`, `op.preparation`, `pending.entry`) through
  `session`'s typed accessors, then writes back whatever the planner's
  transactions carry. `strand.last_result` is written and never read.
  `api.create_strand` seeds a new strand's `strand.config` / `strand.leaf`
  / `strand.state` before starting its driver. The blackboard and
  escalations are `fact.*`: `api.put_fact` / `fact` / `facts` over
  `fact.custom`, and `runtime/escalation` and `runtime/lineage` store
  their records in `fact.custom` under the reserved `escalation/` and
  `lineage/` key prefixes — escalations guarded by the register's own
  seq, lineage cells last-write-wins. `put_fact` is last-write-wins too;
  `put_fact_expecting` is the same write with the caller's read seq
  asserted, which is what makes a shared cell safely updatable.
- **Wire**: consumes `provider/stream.StreamEvent` — zero or more `Delta`
  then exactly one `Settled` or `Failed` — from a cancellable `StreamHandle`,
  and `effects.ToolOutcome` from the tool surface. A provider wait deadline
  invokes the handle's cancellation capability before returning a terminal
  observation.

## Invariants

- **A provider retry hint survives settlement and recovery.** The runtime
  preserves a retryable error's `retry_after_ms` in the existing assistant
  diagnostics JSON. The machine persists the later of that minimum delay and
  its captured exponential policy as `GenerationRetryWait.not_before`.
  Missing, malformed, negative, or shorter hints cannot shorten the policy.
  Summary failures use the same convention in `OperationError.details` when
  their producer supplies it. Terminal cancellation remains terminal.

- **An idle assembly actor hibernates, and the interval is one value.**
  `runtime/residency.hibernate_after_ms` is the quiet period after which a
  session's weft actors shed their heaps, and every site takes that constant
  rather than a number of its own; in this package `registry` and
  `strand_runtime` take it. Two actors deliberately do not. `writer` holds a
  `RenewTick` heartbeat through `actor.periodic`, which a handler can neither
  cancel nor re-time, so its mailbox is never quiet. The booter is a
  stateless placeholder with nothing to reclaim.
  Hibernation returns garbage
  only — the live set, which is dominated by this session's `Effects` copies,
  survives it — so it is a complement to sharing that value, never a
  substitute. `docs/design-notes/daemon-memory.md` has the measurement.

- **The checkpoint poll runs at two periods, and which one is decided by the
  drive it follows.** `strand_runtime.State.occupancy` records what the last
  completed drive's `load` found. `Occupied` re-arms at `poll_interval_ms`
  (200 ms), which is the rate a deferred suspension is granted its next
  permit at and nothing else — every other in-flight step is answered by its
  own monitor or its own timer; `Unoccupied`
  re-arms at `idle_poll_interval_ms` (two minutes), which clears
  `residency.hibernate_after_ms` and is the reason the strand can hibernate
  at all — it holds the largest `Effects` heap in an assembly and used to be
  permanently awake. The backstop is *kept* in both states rather than
  dropped in one, so the doctrine of design §4.6 still holds verbatim: a
  doorbell lost between a caller's commit and its nudge costs latency, and
  the latency it costs an idle strand is the idle period rather than never.
  Every drive re-arms, through the single `arm_poll` call in `finish`, so the
  period follows the occupancy immediately: a message that opens work on an
  idle strand arms the short tick while handling that message. That is
  load-bearing rather than a nicety. A turn is almost always shorter than the
  idle period, so a strand that had to wait for the pending idle tick before
  its period could change would run the whole turn without a short tick, and
  the short period would be unreachable in ordinary use — which is what it
  was until the correction recorded in
  `docs/design-notes/daemon-memory.md`. Two things wait on it: a deferred
  suspension's permit, and `api.steer_marking`, the quiet marked injection
  `client/rulescan` and `client/schedulescan` commit onto an *open* run
  without ringing.
  Two rules keep one live chain. A drive whose occupancy names the period
  already outstanding (`State.armed_poll_ms`) arms nothing, so a session
  configured with one period for both occupancies arms exactly one deadline
  for its whole life — which is what `client@schedulescan_test`'s
  one-deadline assertion reads. A drive that wants the other period arms it
  under a new `State.poll_generation`, and the tick it superseded — which the
  `Timers` seam gives nothing to cancel with — is dropped by `polled` on
  arrival for carrying the older number. The price is one stale wake per
  period change, two per turn. The arming
  also follows the drive rather than opening it, which is what lets the period
  be read from what that drive found and stops a halting strand leaving a
  timer behind. `runtime/idle_poll_test` asserts both periods, the single
  chain, and that becoming occupied arms the short period without waiting.

- **A call the model keeps repeating into failure is stopped at
  clearance.** Before `clear_tool_call`, `tool_clearance_key` reads the
  branch's newest `repeat_guard.window` message entries back to the latest
  compaction and asks `repeat_guard.judge` how many consecutive turns made
  this call and nothing else, by name and `core/json.canonical`
  arguments, and saw every copy fail; a turn that also did other work is
  progress and breaks the streak. At `refuse_after` (3) the call is refused unrun with
  `RefusalContinues`; at `end_after` (4), which only a repeat past that
  refusal reaches, it is refused with `RefusalEndsRun` and the run
  completes as `CompletedByTerminatedTools`. Any user message breaks a
  streak, so an operator's reply or an advisor's nudge buys the model a
  fresh start. The count is read from write-once entries, so recovery
  judges from the same entries the first incarnation did, and a read that
  fails reads as no history rather than halting the strand: the guard is
  a brake on a loop, not a correctness condition. A call whose arguments
  never parsed comes through the same key: the guard judges it first, and
  otherwise it is refused with `planner.malformed_refusal`'s text, never
  handed to `clear_tool_call`, so a model repeating broken JSON is bounded
  the same way.
- **One writer, structurally.** All commits are calls into one actor, so
  "transactions on one session are serialized" is a property of the process
  topology, not a convention. Reads route through it too, keeping a single
  storage path and one instrumentation point.
- **The drive loop is the machine's contract, verbatim**: load registers →
  build `PlannerInputs` with a fresh id generator each pass → `next_action`
  → commit and re-plan on `Transition`/`Finish`; on `Dispatch` commit the
  intent *then* start the effect; on `AwaitEffect(key)` resolve the key; on
  `Wait` schedule a timer or a poll permit; on `Fault` stop abnormally.
- **Restart callbacks retain only their own inputs.** `api.open_published`
  projects option fields before constructing runtime and driver builders.
  `supervisor.start_published` captures the classifier and driver builder
  separately, so publication and booter specifications do not inherit the
  entire effects graph. An actual started-supervisor regression grows the
  writer subscriber list and checks that only OTP's initial writer specification
  and active writer entry retain it.
- **Crash recovery and cold start are the same code.** A restarted strand's
  first drive re-reads its registers and resumes (spec §3.1); the tree is
  rest-for-one, so a writer crash restarts the writer *and* the whole
  strand factory, while a strand crash restarts only that strand.
- **Recovery boots *all* strands, not just `"main"`.** The booter lists the
  `strand.*` registers and starts a driver for every strand it finds, so a
  cold open, a writer crash (the factory restarts empty and the booter
  repopulates it), and a booter crash all converge on "list the store,
  start what is missing". Subagents created mid-session seed their
  registers first, which is why every later reboot finds them.
- **Reference addresses are minted once per strand and outlive their drivers.**
  The registry precedes the writer in the rest-for-one order, so it survives
  writer and strand crashes; a replacement driver binds the *same*
  reference and stays addressable, and doorbells resolve through `lookup` at
  ring time rather than caching pids. A registry crash replaces its namespace
  while the
  earlier drain ledger preserves effect ordering; only a whole-tree reboot
  starts both actors empty.
- **Factory handles belong to an incarnation, not a permanent name.** The
  root publishes each unnamed factory's PID and typed OTP handle in the
  registry before acknowledging its start. Each strand start resolves that
  slot again. A subagent-factory restart replaces only its own slot, retaining
  the primary factory and its drivers; registry restart rebuilds both slots
  through the existing rest-for-one order.
- **Inter-strand messaging is the queue machinery, not a mailbox.**
  `send_to_strand` durably enqueues a steer onto the target's open run — or
  accepts a fresh run when it is idle — and only then rings the doorbell,
  so the payload is durable before any ephemeral signal (design §4.6).
- **Doorbell loss is harmless by construction.** `Nudge` only wakes the
  strand early; the payload always travels in the commit. The periodic
  `PollTick` finds queued work anyway, and any commit racing the strand's
  own surfaces as a stale expectation, forcing a reload that sees it. The
  `_quietly` admission variants commit without ringing at all.
- **Effect processes are monitored by the driver and adopted by its
  reaper.** A tool process that dies without reporting settles as a synthetic
  in-band tool error. A provider death instead faults the driver: fabricating
  a retryable transport failure could dispatch beside the stream subtree that
  outlived it. Each driver incarnation also starts a *reaper*: a weft
  witnessed run (`weft.managed` task parked for the incarnation's life,
  `weft.start_witnessed`, `weft.CancelSiblings`) linked to the driver, whose
  ledger claim is made by an adopted claimant so the scope outlives the
  claim. Every
  effect adopts itself as a leaf owner (`weft.adopt_leaf`, cancel = its stop
  capability) and runs only on `Adopted`. On driver death the scope asks
  every adopted effect to stop and remains alive until all their exits
  arrive. A provider effect first creates a parked request worker inside a
  minimal custodian, publishes that custodian to the reaper beneath itself
  (`weft.adopt_under`, cancel = `stream.cancel`), and only then permits the
  worker to call the frozen provider surface. The scope therefore cancels
  the public owner when the effect exits, retains it even when the
  publication was refused, and cannot finish until both are gone; an
  abnormal owner exit is a lost proof that cancels the run and leaves the
  scope's exit reason abnormal. The parked worker is linked to the provider
  effect because they are one failure domain: an unexpected surface crash must
  still fault the effect and recover, not become a fabricated provider
  response. That worker is a `weft/state_machine`, started with a plain linked
  `sm.start` from the effect, so the link is weft's rather than something this
  module arranges. Production surfaces return a `PreparedStream`: the worker
  publishes its parked owner to the reaper before granting the begin permit,
  so a rejected publication cannot leave unowned provider work. Immediate
  `ProviderSurface` values remain only for in-memory fakes with no external
  work; they may still carry self-reaping owners to model cancellation. The
  custodian is unlinked and survives both long enough to drain their adopted
  descendants. The separate drain ledger remembers every unadjudicated reaper
  for the strand, and releases a replacement only after its original monitors
  prove that exact predecessor snapshot drained normally.
  The effect installs a `DrainWitness` before granting begin, so it can delay
  `ProviderDone` until the original monitor reports `Drained`; the reaper holds
  an independent monitor for recovery. A provider owner or reaper must exit
  normally to discharge its obligation; killed and abnormal observations fail
  closed. A reaper the ledger meets as `noproc` is the one exception, and
  retires: the pid is gone, and a weft scope cannot be gone with effects
  still running. `SessionTree`
  retains the ledger's direct subject, so
  `shutdown` waits it independently even when the root supervisor was killed
  abnormally and the service namespace has stopped. This transitive drain barrier, rather than scheduler
  timing, makes the incarnation-local `live` list sound.
- **Internal events are addressed to one incarnation, not one logical
  strand.** Public `Nudge` and external abort requests resolve the logical
  reference address across a restart. Provider completions, tool
  completions, recovery acknowledgements, poll ticks, retry wakes, and internal
  abort retries instead capture the driver's direct subject. A durable effect
  token is intentionally reused when recovery replays an effect, so token-only
  correlation is safe only after a message has first been confined to the
  incarnation that created its `Live` entry. Sending those messages through
  the stable name would let a predecessor remove a replacement's live worker.
- **Provider ownership continues below the effect process.** A provider
  effect has one absolute receive deadline which deltas cannot renew. Reaching
  it cancels the stream and waits a
  bounded acknowledgement grace. An owner-authored `ProviderCancelled` and a
  locally reported `CancellationUnconfirmed` are both terminal under retry
  classification; the latter says teardown could not be proved and therefore
  cannot authorize another attempt. The acknowledgement grace is a second,
  single scheduled timer, so late deltas cannot extend it. The effect withholds
  `ProviderDone` until its public stream owner exits, so the current driver
  cannot progress beside the old subtree either. An abort keeps the driver alive and spends the
  acknowledgement grace because a real terminal may already be queued; its
  usage still belongs in the aborted result. Driver death has no remaining
  terminal consumer, so the effect requests cancellation and exits without
  spending that grace. Its reaper already monitors the public owner and is the
  surviving drain witness: a provider pump or HTTP request may temporarily
  outlive the effect pid, but never the barrier that prevents replacement.
- **`after_commit` is the crash seam.** It runs in the writer process after
  the commit is durable and published but *before* the committer's reply,
  so an observer that kills the writer produces exactly "commit N durable,
  committer never saw it succeed". Production passes a no-op. No terminal
  result is accepted while the seam is open.
- **Abort is strand-routed and idempotent.** The durable
  `cancel_requested` marker serializes with the strand's own transitions
  and live effects are cancelled by their owner; a pre-commit crash loses
  the request exactly as pi's no-live-task case does, and callers
  re-request.
- **A lost abort race re-delivers, it never halts the strand** (ORCH-L5).
  Exhausting the stale-retry ladder must not drop a fire-and-forget request
  (nobody would learn it was lost) and must not restart the strand for a
  transient race, so the driver re-sends `RequestAbort(operation)` to itself after a
  pacing delay and keeps running. Every retry compares that same captured
  identity with durable state. An idle or successor operation makes the request
  obsolete without cancelling effects. `api.abort_operation` is the explicit
  identity seam; `api.abort` captures the current identity before sending.
- **Interrupted effects stay registered, and settle faithfully.** Live
  effects are cancelled *after* the durable marker (pi §4.6 order) but are
  not deregistered: an effect that already delivered a real settlement
  still queued in the mailbox commits under its reserved ids as `aborted`
  **retaining its reported usage** (ORCH-M3), while one that dies unreported
  settles through the monitor as a synthetic abort with unknown usage coverage.
- **Fourteen corners of `fact.custom` are reserved, and reserving hides as
  well as refuses.** `escalation/`, `operation-result/`, `lineage/`,
  `child-run/`, `prompt/`, `session/`, `rule/`, `schedule/`, `ext/`,
  `job/`, `advisor/`, `goal/`, `summary/` and `client/` are
  refused to
  `put_fact` and filtered out of `facts`, so no blackboard write can
  forge an approval, shadow a terminal result, rewrite a parent edge,
  rewrite a child run's stop record or budget,
  overwrite the pinned system prompt, re-point the session's own
  identity, mark an operator's triggered project rule or scheduled
  heartbeat as already fired so that it never fires, forge and read
  an installed extension's durable memory, mark a background job
  terminal while its process still runs, move the advisor's feed cursor
  past everything the primary will ever append and so silence the
  session's reviewer, fake the goal cell's status or accounting, write
  the summary a terminal shows in place of a reasoning block
  (`summary/<entry>/<block>`, protocol 050), or rewrite the shared run
  settings — `client/run_settings`, the one cell under `client/` — that
  every admission compares against, and so choose the queue mode and
  tool-execution mode of every later run of the session. Both are named
  constants, `api.client_fact_prefix` and `api.run_settings_key`: the
  key is a CAS expectation folded into the admission transaction, and a
  typo in a literal spelling of it would not fail to compile, it would
  silently drop the compare-and-set. Because
  the reservation also hides a namespace from its own owner, harness code
  reads and writes it through `reserved_facts` / `put_reserved_fact`,
  which refuse everything *outside* the reserved set — the two doors are
  disjoint so neither can be pressed into service as the other. The
  ledger prefix is `lineage/` rather than anything near the
  model-writable `agent/`: an integrity-critical namespace two letters
  from a model-writable one is one typo away from lineage forgery.
  `ext/` is the newest and the widest: the whole prefix is reserved
  rather than a corner of it, because the extension's own name is the
  second segment and `client/extension/memory` is what binds it — an
  extension writes `ext/<its installed name>/<key>` through
  `put_reserved_fact` and reads it back with the plain `fact`, and a
  model-writable namespace there would let a model forge what an
  extension remembers and read what it was never shown.
  `rule/` and `schedule/` are held to the same rule for the same reason
  — a fired-mark a model could write is a project rule or a scheduled
  heartbeat it could silence in advance — and to each other's, kept
  disjoint rather than sharing one prefix: a security-relevant write-once
  mark earns its own corner instead of one shared with a mechanically
  similar but distinct feature.
- **A model-spawned strand's crash loop cannot reboot `main`.** The tree
  carries two strand factories and `Config.subagent` decides, by name
  alone, which one starts a strand. The subagent factory sits *after* the
  primary one in the rest-for-one order, so its death restarts only
  itself and the booter. The runtime cannot tell a model-spawned strand
  from an operator-spawned one — lineage is a layer up — so the host
  injects the predicate, and the default says nobody is a subagent,
  which is exactly the single-factory behaviour that came before.
- **Escalations are registers, not entries.** An escalation is current
  mutable state (pending → approved → consumed) needing a bounded point
  lookup on the clearance path, and it must never move a strand's leaf or
  enter a context projection — a mid-batch denial cannot be allowed to
  reparent the conversation. Status transitions are CAS-guarded through
  the writer, so a lost race is a refused commit, never a double consume.
- **An approval is attributed, and consumed before its grants are used.**
  The record carries the exact `CallScope` it is currently raised for; a
  clearance loads only the approvals scoped to the call it is clearing
  (unscoped records match nothing — an unattributable grant must not
  widen anything), consumes them by CAS *first*, and passes into
  `tools.clear` only the grants whose consumption commit won. A lost CAS
  drops that record's grants and the clearance proceeds under the base
  policy; a crash after the consumption spends the approval without an
  execution. Both directions fail safe: one approval is worth at most one
  widened execution, of exactly the call a human approved. What the
  clearance won then travels onto the dispatch it authorized
  (`ToolRun.grants`), keyed by the call's own `{step, source index}`, so
  a carry left behind by a clearance whose dispatch never happened — a
  stale commit that re-plans, a fault — can never widen a different call.
  A **replay** carries no grants at all: its clearance belonged to a dead
  incarnation whose approval is already marked consumed, and re-widening
  from it is the one direction that would turn one approval into two.
- **A record's scope is the call holding the claim, and a claim moves.**
  The id a raiser files under is a digest of the *want*, not of the call
  (`client/escalate.record_id`), so under a retry the row is already
  there — and the call that opened it has settled, because a model that
  reads an in-band refusal retries under a freshly minted call id. So
  `claim_escalation` takes the record over under a CAS rather than
  refusing the duplicate; a scope frozen to the first attempt would name
  a call nothing can re-clear, leaving an approval nothing can spend and
  a write-once register nothing can replace. The exactness of
  `scoped_to` is untouched and is what still holds the line: one
  claimant at a time, one CAS from `Approved` to `Consumed`, and only a
  call whose denial digests to the same id can ever hold the claim —
  same want, same strand, same tool. A decided record re-opens with no
  grants, so a new cycle needs a new decision.
- **Grant and denial payloads cross the runtime opaquely.** They are stored
  as JSON in the broker's escalation vocabulary and returned uninterpreted,
  which is what keeps the spec's `E → A,B,C,D` direction intact — there is
  no broker import here. `client/grants` does the decoding.
- **Hooks decide from durable state.** The `hooks.threshold` and
  `hooks.overflow` constructors derive their signals from the strand's
  *durable* projection (`hooks.project`, read straight from the session
  store rather than through the writer, which is what makes it callable
  from a hook), so a decision taken before a crash is taken again after
  it — the same rule the simulation hooks follow. A read that fails
  projects as empty, which reads downstream as "nothing to compact": a
  strand must not be halted because a token count could not be taken.
  `compaction_note` is inside the same rule for the reason `context` is
  — its answer is transient input to a request whose consuming commit is
  the summary — and `usage` is deliberately outside it, because it fires
  *after* its commit rather than before one.
  The same rule governs `run_start`, whose first production filler is
  `client/notes`'s `agent/` digest: a hook runs on the driver, so it must
  be synchronous and non-blocking, and it is built *before* `api.open`
  returns, so there is no runtime handle to reach through — a register
  read through the session store is the sanctioned pattern for both.
- **A hook slot is set by replacement, so layers compose by wrapping.**
  Every `hooks.with_*` setter replaces its slot outright. A second layer
  that wants to add to one — `client/agency.reaping_hooks` on `run_end`,
  `client/notes.digest_hooks` on `run_start` — therefore takes the built
  `Hooks` and wraps the function already in the slot, calling it and
  adding to its result. A layer that set the slot instead would silently
  drop whatever the layer beneath it installed.
- **The threshold is computed on every pass, from one projection per
  step.** `PlannerInputs.threshold` is a value rather than a thunk
  (frozen, spec Part 1), so an open operation projects its branch on
  every driver message. The driver makes that projection once, in
  `plan`, hands it to the threshold hook inside `ThresholdQuery` and
  reads it again for the generation request, so the request and the
  threshold see one branch and the branch is scanned once. The scan is
  memoised together with its pure `hooks.Projected` result in the private
  `CachedProjection` held by `State.projection`, keyed by the leaf it was
  made from. A step whose leaf has not moved reuses both, and one whose leaf moved
  forward scans only the entries past the cached leaf's seq and joins
  them on when the oldest of them names the cached leaf as its parent
  and none is a compaction; anything else, a rewind, a fork, a
  compaction, is a full rescan. Only write-once entries and their pure
  default projection are cached; threshold decisions and request-local
  context hooks still run at their original boundaries. The request hook's
  transformed list never enters the cache. Provider worker construction projects
  the reaper before making its closure, so custody does not copy the whole
  driver's scan, projection and sibling Effects into each worker. A fresh incarnation starts without
  a cache, which is the replay rule every projection here is held to. Before this a
  1,200-entry branch was decoded three times per step (issue #359).
- **Read-only context inspection shares the compaction projection.**
  `hooks.project_from_scan` accepts a newest-first branch stopped inclusively at
  compaction, so observers can capture their own immutable endpoint without
  rereading a moving leaf. `hooks.has_reported_usage` uses the same newest
  positive post-compaction usage as `context_tokens`; it never treats the
  retained tail's old usage as a new baseline. These helpers add no effects or
  changes to compaction policy.
- **A context is counted the way the provider counts it.**
  `context_tokens` takes the newest settled assistant message's
  *provider-reported* usage and estimates only what came after it (pi's
  `estimateContextTokens`). An estimate-only fold drifts low against the
  provider — cache reads, thinking tokens, serialization overhead — and
  low is the dangerous direction: a run that believes it has room
  overflows instead of compacting.
- **Carried usage is never read.** A `CompactionEntry` carries a *copy*
  of its retained tail, so the assistant messages at the head of a
  post-compaction projection report the size of the context the
  compaction replaced. `Projected.carried` names them and the fold
  starts after them. Without the guard the threshold re-fires on the
  turn after every compaction, forever — pi guards the same way, by
  rejecting usage older than the latest compaction.
- **A retained tail never opens on a tool result.** `preparation` treats
  keep-recent as a target, preserves the newest assistant exchange and all
  following input, then aligns a result boundary backward to its call.
  This prevents threshold-triggering results from disappearing before the
  model reads them. Before the first assistant, all input is protected.
  An exchange larger than the model window can fail overflow recovery;
  the cut must not silently remove it to meet the target.
- **One preparation builder serves all three triggers.** Threshold,
  overflow and operator `Compact` use `hooks.preparation`. Prior checkpoint
  text travels separately as `previous_summary` in the frozen contract;
  the host supplies the replacement. A previously retained exchange can
  be cut once a newer exchange exists.
- **Close is an orderly shutdown, and lease release depends on its drain
  barrier.** `close` terminates the tree the way OTP terminates one —
  reverse start order, reason `shutdown` — so every strand driver is
  gone before the writer it commits through, and a close that was asked
  for is distinguishable in the logs from a fault. Durable state stops
  at a commit boundary either way, because commits are atomic in the
  storage actor. The configured grace bounds only the root's system-message
  handshake. The drain ledger has an unbounded OTP shutdown interval and keeps
  the root alive until every provider reaper exits; `close` seals the handle
  and releases the writer lease only afterward. A slow close is preferable to
  reopening beside an unconfirmed native request. Nothing terminal is written;
  reopening recovers the open operation.
- **A hint with nowhere to go is a non-event, and never the writer's
  death.** Post-commit publication skips a subscriber whose owner is
  gone. Sending into an *unregistered name* crashes the sender, so
  without a guard a supervised, restartable subscriber held by name would
  make the writer — and the whole rest-for-one tree beneath it — hostage
  to that subscriber's restart window. Events are hints and pulls are
  truth (design §3.6), so dropping one costs latency and nothing else.
  `publish_direct` resolves a named direct subscriber's pid **exactly once** — via
  `process.named`, straight into `ffi_sup.send_to_pid` — rather than
  resolving it a second time inside an ordinary `process.send`, which is
  what `process.send` does internally for a `NamedSubject` on every call.
  Before issue #43 those were two separate resolutions, and a name that
  unregistered in the gap between them still crashed the writer for a
  hint nobody needed; a plain, pid-backed subject never had this problem
  at all; `process.send` on it never re-resolves anything.
  Routed subscribers use Weft's reference lookup and send instead. The
  destination survives service replacement, but missed hints are not replayed.
- **A lost lease stops the writer abnormally** so the supervisor reboots
  the tree, whose reopen path re-acquires or fails loudly. Renewal runs on
  an idle timer at a third of the TTL. That timer targets a private subject
  bound to the current writer PID; the public reference address is only
  for callers. Otherwise each predecessor timer would resolve the address
  to the replacement and add another permanent renewal cadence after every
  restart. A commit that races the renewal loses too, and arrives as
  `tx.LeaseLost`: `runtime/api` turns it into `SessionStolen`, and
  `runtime/strand_runtime` halts the strand on it rather than reloading,
  because reloading reads the same file and meets the same fence.
- **Logical addresses, not cached pids, identify restartable services.** The
  writer and strand drivers bind reclaimable reference addresses.
  Loss-tolerant public strand messages resolve the address once and
  send the tagged envelope directly to the resolved PID; checking and then
  sending through the name would leave an unregistration race between two
  lookups. Writer calls also resolve once: an absent binding yields
  `writer.Unavailable`, while a reply carries `writer.Underlying(cause)` on
  failure. Death after sending is still an unknown outcome, not a safe retry.
  The runtime allocates no dynamic process names. A 50-cycle test executes a
  turn per session, closes it, checks root/driver/namespace death and stale
  address failure, and asserts zero atom growth after a fresh-VM warm-up.
- **Log context reaches an effect process because the closure carries
  it, not because anything is inherited.** `spawn_effect` takes the
  step-scoped logger as an argument, and the body that runs on the new
  process closes over it — Erlang `logger` metadata does not survive a
  spawn, so a driver that relied on inheritance would emit its most
  interesting lines uncorrelated. The spawned body additionally calls
  `log.adopt`, which stamps the same `{session, strand, op, step}` into
  that process's `logger` metadata so an OTP crash report *about* the
  effect process is correlated too. See `telemetry/context` for why the
  value is the mechanism and the metadata only the fallback.
- **What the drive loop logs, and at which level.** `info` for the
  durable state changes — `strand.started`, `operation.settled`,
  `operation.aborted`; `warning` for degraded-but-progressing —
  `retry.armed`, `effect.exited`; `error` only for `strand.halted`,
  which is the one thing the strand cannot recover from on its own;
  `debug` for `effect.dispatched` / `effect.settled`, which is per step
  and off by default. A tool that failed *in band* is a settlement like
  any other and stays at `debug`: that is the system working.

## Deep Docs

- [docs/architecture/orchestration.md](../../docs/architecture/orchestration.md)
  — the drive loop, the supervision tree, doorbells, the interleave
  harness.
- [docs/architecture/simulation.md](../../docs/architecture/simulation.md) —
  what the deterministic runner does to this tree.
- [docs/spec-gaps.md](../../docs/spec-gaps.md) — "From WP-E": crash
  semantics, boot seeding, close-as-crash, injected entropy, minimal writer
  events. "From the M3 runtime wave": escalations as registers, opaque
  grant JSON, abort-race pacing, parallel dispatch's exclusivity.
- [Root CLAUDE.md](../../CLAUDE.md) — repo ground rules and the doc graph.

## Child operation metadata

`runtime/child_run.Run` is a total-decoded reserved fact under
`child-run/{operation}`. `api.accept_with_child_run` commits it with continued
run admission when the target has lineage. The same transaction checks the
lineage, parent strand and parent operation sequences. `send_to_child` requires
a current sending operation for idle admission, supports an explicit positive
budget, and refuses an explicit budget for an active child. A finishable parent
checkpoint cannot transfer cleanup custody because run-end hooks precede the
terminal commit. Ordinary operator admission at that boundary is unowned.
Lineage retains original spawn identity and a default budget for later runs.
Proposal 042 specifies compatibility and the additional wire metadata.

## Async custody and guarded admission (protocol 048)

`async_execution.Execution` is the total-decoded record under
`client/async/record/{id}`. `child_run.Owner` distinguishes `ParentRun` from
`AsyncExecution`; old string owners still decode as parent custody.
`send_to_async_child` compares live execution custody in the same transaction
as run admission. `child_run.initial_key` retains the original operation
atomically before Agency lineage publication. Later operations cannot replace
that recovery identity. `steer_async` checks the same custody without waking.

`GuardedMark` extends a write-once receipt with exact sequence expectations.
A failed receipt or authority expectation returns `FactConflict`; admission
must not retry past a changed grant. This lets peer delivery atomically commit
provenance, receipt and prompt through the existing queue/acceptance paths.

`async_execution.Readiness` is stored separately under
`client/async/ready/{id}`. Its total decoder validates the unique bounded endpoint
names and idle interval. Readiness does not replace lifecycle: a finished or
lost record may retain its endpoint declaration. Peer entries carry the core
`PeerOrigin` variant through the same durable conversation codec as human
entries; grant and receipt checks remain in the existing admission transaction.

## Provider reports (protocol 081)

`strand_runtime.provider_terminal_observation` preserves the independent
request report on generation, polling, and nested-summary terminals.
`effects.settle_failure` uses the report's final attempt for the synthetic
assistant while passing the full report to the machine. A local empty
report establishes no consumption; owner loss without a report becomes an
unknown observation. The existing usage hook sees the committed aggregate
row once, and context readers use the assistant's final response usage.

## Writer residency

A writer uses `runtime/residency.hibernate_after_ms` when it has no lease.
For a leased session it uses the smaller of that threshold and half the
renewal interval (at least one millisecond). The renewal remains Weft's
fixed-delay periodic timer; hibernation neither cancels nor re-arms it.
A message wakes the same writer, preserving commit ordering, subscribers
and lease custody. The shorter quiet period lets a parked writer release
transient commit/read heap before the next heartbeat wakes its mailbox.
`writer_renewal_test` observes actual hibernation, request wake-up, repeated
renewal and abnormal retirement after lease loss.
