Toolnexus.Agents.Runtime (toolnexus v0.12.0)

Copy Markdown View Source

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 ConversationStore for every handle (conversation id = the handle's deterministic id) — transcripts genuinely survive turns and durable resume reads real history;
  • ONE MetricsRegistry shared 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 (see Toolnexus.Agents.registry/1)
  • :transport — the LLM transport function (see Toolnexus.Client :transport); the runtime wraps it with the turn gate
  • :llm — client settings shared by every handle: %{base_url, style, model, api_key, retries} (agent model: "inherit" resolves to llm.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)
  • :clockToolnexus.Agents.Clock.t() (default: the real clock)
  • :store — a ConversationStore struct (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

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

close(rt, opts \\ [])

Verb: graceful stop-all — close(root), cascading leaf-first.

conversation_store(rt)

The runtime-wide ConversationStore (conversation id = handle id).

ctx(rt)

The runtime's shared context (root handle, store, gate, trace...).

interrupt(rt, pid)

Verb: abort the in-flight turn → idle, drained inbox restored.

list(rt)

Flat listing of every handle (id, state, rolled-up tokens, inbox depth).

max_observed_concurrent_turns(rt)

High-water mark of concurrent LLM calls through the turn gate.

new(opts)

@spec new(keyword() | map()) :: pid()

Start a runtime and return its pid (raises on bad options).

post(rt, pid, item)

Verb: post an item (%{from, channel, text}) to a handle's inbox.

resume(rt, answer)

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.

root(rt)

The root handle.

run_turn(rt, pid, prompt, one_shot \\ nil)

Host-side fused wake+wait on one handle (resume cascades, heartbeats, fixtures).

shutdown(rt)

@spec shutdown(pid()) :: :ok

Graceful stop-all (close(root)) followed by full runtime teardown.

snapshot(pid)

Read-only snapshot of one handle.

spawn_agent(rt, parent, def_name, budget \\ nil)

Verb: spawn a child of parent from registry definition def_name.

trace(rt)

All transition-trace lines (§0 conformance surface).

wait(rt, pid, timeout_ms \\ nil)

Verb: wait for the next result or the last result.

wake(rt, pid, prompt \\ nil)

Verb: wake an idle handle (drains the inbox into one turn).