Skip to main content
The runtime is what turns a Program into something that executes: the host supplies bindings through a RuntimeEnv, Interpreter::load checks everything up front, and Interpreter::run evaluates the program on an input map. This page lists the full surface, plus include_program! for compiling a .dsrs file into a Rust binary.

RuntimeEnv

What the host supplies at load: live models, host tool and hole bindings, the sandbox, and the capability grants. Secrets never travel in the artifact; model clients are bound here from host-held keys and env vars.

Interpreter::load

Loading front-loads every check; nothing is lazy and nothing waits for call time. The checks run in this order:
  1. Program::validate (the structural graph rules).
  2. program.caps must be a subset of env.grants (no ambient authority).
  3. Every model must be bindable: pre-bound by name, or client-constructible from its config.
  4. Every ToolKind::Host tool name must be bound.
  5. Every sandboxed tool and hole is registered through the full sandbox lifecycle (parse, compile, register). A hole that does not compile fails the load, not the call.
With code mode enabled there is one extra load-time refusal: two non-stop tool names in one loop that mangle to the same JS identifier.

LoadError variants

Interpreter::run

Evaluates the program on an input map, reading parameters through an optional overlay (never mutating the program) and metering spend against a budget.
  • input is a JSON object of the program’s input fields. It is checked against the program’s external signature: a missing required field or a type mismatch is RunError::Input.
  • overlay is Option<Arc<Overlay>>. When present, its base must equal the program’s hash or the run fails with RunError::Overlay before anything executes.
  • budget caps spend for this run.

Interpreter::run_collecting

run_collecting(input, overlay, budget) is run with per-leaf metadata: it returns a RunOutput — the same output map plus one LeafOutcome per successful Predict-leaf evaluation, in execution order (ForkJoin branches append in declared branch order). This is the seam Predict<S> uses to reassemble CallMetadata when it executes through the interpreter. Each LeafOutcome carries: name (the program-unique leaf name, the trace span component), raw_response, field_meta (per-field jsonish coercion flags and #[check] results, keyed by canonical field name), usage, model_config_hash, span_id (when a capture scope was active), and — for AgentLoop leaves — tool_calls and tool_executions. Scope rules: Predict and AgentLoop leaves report, Hole leaves do not; only successful evaluations report (a failed Retry attempt leaves no outcome, the succeeding one reports); an agent whose final output came from stop-tool args has empty field_meta; a replay-served leaf reports the recorded raw text and usage with empty field_meta.

Interpreter::run_conversation

run_conversation(chat, input, overlay, budget) is the conversation-in/conversation-out entry: one turn with the program’s single leaf over a caller-owned Chat, returning (RunOutput, Chat) — the turn’s output and metadata plus the extended conversation. It exists only for single-leaf programs (one predict or agent node, what Predict<S> compiles to); a multi-node graph is refused with RunError::Input.
The chat/input combinations: an empty chat with Some(input) renders the opening turn (system + demos + the formatted input, identical to conversation_opening); a non-empty chat with None is sent as-is; a non-empty chat with Some(input) appends the formatted input as the next user turn. A turn is not a run: each call records one trace span (seq increments per turn) and meters against its own budget. On an agent leaf the turn runs the full tool loop, dispatching tool calls through their bound executors. Replay works turn by turn — a span keys on the full chat sent, so a recorded conversation serves each turn with tool effects baked in. conversation_opening(&input, overlay) renders the opening Chat without calling anything: the same overlay-resolved system + demos (+ agent playbook) + input rendering a run would send. Use it to inspect or edit the first turn before run_conversation. Predict::build_chat/call_and_parse are thin wrappers over these two entries.

Caller-managed tool loops

run_conversation_caller_managed(chat, input, overlay, budget) is the same turn in suspending mode: when the model requests tool calls on an agent leaf, the loop suspends instead of dispatching and returns ConversationTurn::Suspended(ToolSuspension). Execute the calls yourself and feed the results back:
ToolSuspension::calls() is the pending calls in request order; ToolSuspension::chat() is the conversation so far, including the assistant tool-call turn. resume_conversation records one ToolRun event per result (metering the time the suspension was outstanding), pushes one batched tool-result user turn, and continues the loop under the same meters and turn cursor — trace spans, budget metering, and stop-tool semantics are identical to dispatching mode. A stop-tool call completes the turn instead of suspending; a replay scope never suspends (served turns carry every tool effect); Code Mode does not apply, since the caller executes the tools. Dropping a suspension without resuming closes its span as Cancelled. Feed a failed tool’s error text as its result to keep the conversational-repair behavior of dispatching mode.

Budget

Run-level spend limits; None means unlimited. Budget::default() and Budget::unlimited() are the same: no limits. An AgentLoop’s per-node budget chains a child meter under the run meter, so node spend also counts against the run.

RunError variants

RunError::retryable() is true only for Lm, Parse, Tool, and Hole; those are the errors Retry and Refine may intercept. Budget and CapabilityDenied are never retried.

Ambient overlays

These two functions let #[module] executable functions pick up a candidate without threading it through every call.
  • with_ambient_overlay(overlay, fut) runs a future with an Arc<Overlay> as the ambient candidate for every #[module] function called on that task. Scoping is task-local: spawned subtasks do not inherit it, and nesting replaces the outer scope. The overlay’s base is checked by Interpreter::run against each module’s program, not here, so one scope can span calls into several modules and only the matching one accepts it.
  • current_overlay() returns the ambient overlay (Option<Arc<Overlay>>) if a with_ambient_overlay scope is active on this task. #[module]-generated functions read it immediately before Interpreter::run.

default_lm

The globally configured LM, used when a module does not name a model. default_lm() returns Option<Arc<LM>>: the model set through configure(...) in the global settings, or None when nothing was configured. Generated #[module] environments use it to bind the default model ref at load.

Embedding programs with include_program!

include_program! compiles a .dsrs file into your binary:
The macro creates a module named after the file stem (qa.dsrs becomes mod qa). The path is resolved relative to your crate’s Cargo.toml directory. The generated module contains:
  • qa::SOURCE: the embedded text.
  • qa::program(): the parsed, validated program (panics on a bad file).
  • qa::try_program(): the same, but returns a Result.
  • A generated test, so cargo test fails if the file ever becomes invalid.
Validation is layered. Syntax is checked at macro expansion through dsrs-syntax — the shared .dsrs lexer and structural grammar both the macro and the full parser read from — so a malformed file breaks your build. Semantics (types, dataflow, capability rules) are checked by the full parser at first use of program(), and forced at CI time by the generated test — the sqlx-offline analogue. This is the shipping path for programs with host tools or host holes, which dsrs serve cannot bind: embed the file, bind your implementations with bind_host_tool and bind_host_hole, and serve from your own binary.

See also

  • Program and nodes: the Program value the runtime loads, and the Overlay API
  • The .dsrs file: the text format include_program! embeds
  • CLI: dsrs serve, the hosted version of this load-and-run path
  • Code Mode: the run_js surface with_code_mode enables
  • Capabilities: grants, caps ceilings, and CapabilityDenied