The agent runtime (SPEC §7D) as OTP.
Handles are GenServers under a runtime-owned DynamicSupervisor; the six verbs
(spawn/post/wake/wait/interrupt/close) are the GenServer protocol; a
Run is a monitored process; the global turn gate is a monitoring semaphore wrapped
around the LLM HTTP call ONLY — injected through the client's first-class
:transport seam (§8 Gap 2), so it composes against a real base_url.
The runtime owns the cross-cutting infrastructure (§7D lifecycle obligations):
- ONE
ConversationStorefor every handle (conversation id = the handle's deterministic id) — transcripts genuinely survive turns and durable resume reads real history; - ONE
MetricsRegistryshared by every handle's client — no per-turn client processes leak; - an injectable clock (
Toolnexus.Agents.Clock) for every timer/timeout; - the handle table (root + parent links), rebuildable by walking snapshots.
All owned processes are linked to the runtime process and stopped by
shutdown/1. Downward traversal (close/2, list/1, resume/2) runs in the
CALLER's process — from outside the tree — per the rootward-call discipline
documented on Toolnexus.Agents.Handle.
Options
:registry— map of agent name → definition (seeToolnexus.Agents.registry/1):transport— the LLM transport function (seeToolnexus.Client:transport); the runtime wraps it with the turn gate:in_process— a model running IN THIS PROCESS (ADR 0030): a baregeneratefunction (seeToolnexus.Client.create_in_process/1), the semantic counterpart to:transport, for a host whose model is an Elixir function rather than a wire endpoint. Internally this is turned into a transport by calling the SAMEToolnexus.Client.in_process_transport/1exportcreate_in_process/1uses — no duplicated adapter — and wrapped by the identical turn gate as:transport. Mutually exclusive with:transportand:llm;new/1raisesArgumentErrorat construction time if more than one is set (never resolved by precedence).:llm— client settings shared by every handle:%{base_url, style, model, api_key, retries}(agentmodel: "inherit"resolves tollm.model):max_concurrent_turns— global turn-gate width (default 8):inbox_cap— bounded-inbox size (default 8):shutdown_ms— graceful-close budget for a running turn (default 200):clock—Toolnexus.Agents.Clock.t()(default: the real clock):store— aConversationStorestruct (default: runtime-owned in-memory)
Summary
Functions
Returns a specification to start this module under a supervisor.
Verb: graceful stop-all — close(root), cascading leaf-first.
The runtime-wide ConversationStore (conversation id = handle id).
The runtime's shared context (root handle, store, gate, trace...).
Verb: abort the in-flight turn → idle, drained inbox restored.
Flat listing of every handle (id, state, rolled-up tokens, inbox depth).
High-water mark of concurrent LLM calls through the turn gate.
Start a runtime and return its pid (raises on bad options).
Verb: post an item (%{from, channel, text}) to a handle's inbox.
Route an Answer to the DEEPEST suspended handle; it resumes from its checkpoint
(turns/usage grow, never reset), then the upward cascade re-runs each suspended
parent — whose re-invoked task REATTACHES to the existing child by task key.
The root handle.
Host-side fused wake+wait on one handle (resume cascades, heartbeats, fixtures).
Graceful stop-all (close(root)) followed by full runtime teardown.
Read-only snapshot of one handle.
Verb: spawn a child of parent from registry definition def_name.
All transition-trace lines (§0 conformance surface).
Verb: wait for the next result or the last result.
Verb: wake an idle handle (drains the inbox into one turn).
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
Verb: graceful stop-all — close(root), cascading leaf-first.
The runtime-wide ConversationStore (conversation id = handle id).
The runtime's shared context (root handle, store, gate, trace...).
Verb: abort the in-flight turn → idle, drained inbox restored.
Flat listing of every handle (id, state, rolled-up tokens, inbox depth).
High-water mark of concurrent LLM calls through the turn gate.
Start a runtime and return its pid (raises on bad options).
Verb: post an item (%{from, channel, text}) to a handle's inbox.
Route an Answer to the DEEPEST suspended handle; it resumes from its checkpoint
(turns/usage grow, never reset), then the upward cascade re-runs each suspended
parent — whose re-invoked task REATTACHES to the existing child by task key.
Returns the TaskResult of the TOPMOST handle the cascade re-ran (ADR 0025 / D3,
addendum A4) — the outermost work the answer unblocked; the resumed leaf's own
result when nothing above it was suspended. A host no longer has to wait a second
time to learn what the answer produced. The answer may be an
%Toolnexus.Answer{} or a plain map with atom OR string keys (§10).
IDEMPOTENCY: a resumed turn REPLAYS the suspended turn's own input against the
pre-turn checkpoint, so that leaf's own tools RE-RUN. Tools reachable from a
wait_for suspension must be idempotent. Transcript replay on resume is tracked
as its own change and is NOT implemented here.
The root handle.
Host-side fused wake+wait on one handle (resume cascades, heartbeats, fixtures).
@spec shutdown(pid()) :: :ok
Graceful stop-all (close(root)) followed by full runtime teardown.
Read-only snapshot of one handle.
Verb: spawn a child of parent from registry definition def_name.
All transition-trace lines (§0 conformance surface).
Verb: wait for the next result or the last result.
Verb: wake an idle handle (drains the inbox into one turn).