Runtime Interpreter¤
The interpreter lives in jdsl/tree.py.
Every node implements _tick(ctx) -> Status. The public tick(ctx) wrapper adds
trace events when tracing is enabled.
Run Entry¤
Root.run(...) creates a RunContext:
ctx = RunContext(
blackboard=Blackboard(inputs),
model=model,
model_id=self.model_id,
trace_sink=trace_sink,
capture_id=capture_id,
episode_id=episode_id,
)
If the root has a model id and no explicit model object was passed,
LanguageModel.from_config() is used. If a trace sink is present, runtime node
ids are assigned and episode events are emitted.
The important detail is that Root.run always returns the context, not just a
status. User code reads outputs from ctx.blackboard, inspects provenance with
ctx.blackboard.activity, and can attach a trace sink for compilation.
Programmatic execution can also bypass Root.run and call root.tick(ctx) with
a hand-built RunContext. The compiler tests use that path when running a loaded
package with a fake model.
Status¤
Only two statuses exist:
Status.SUCCESS
Status.FAILURE
Status.SUCCESS is truthy. A tool can return Status.FAILURE to let a parent
selector recover without raising an exception.
Composites¤
Sequence is behavior-tree AND:
for child in children:
if child fails:
return FAILURE
return SUCCESS
Selector is behavior-tree OR:
for child in children:
if child succeeds:
return SUCCESS
return FAILURE
These two nodes are enough to express most routing: predict writes a field,
then sel(seq(check(...), act(...)), fallback) consumes it.
Actions¤
Action._tick resolves all refs, emits a tool-call start event if tracing is
enabled, calls the function, then emits a completion or failure event.
Return handling:
- exception: trace failure, then re-raise
Status: return it directly- any other value: succeed; store it if
store_asis set
The blackboard write uses the action label as provenance:
act(search_titles) -> titles
Refs are resolved in two passes:
- direct blackboard key lookup, such as
ref("customer") - restricted path lookup through
jdsl.ir.expr.resolve_path, such asref("orders[$selected_index].id")
That second pass is what lets compiled packages wire tool ids without asking the model to copy them.
Predict¤
Predict is a stateless one-shot model call. It reads declared input fields from
the blackboard and sends one user message. It does not append earlier assistant
output to the next model call.
Single output:
question -> answer
The model's stripped text is written directly to answer.
Multiple outputs:
ticket -> category, urgency
The model is asked for a JSON object and each key is written separately. If JSON
cannot be parsed, the leaf returns FAILURE.
Compiled signatures may attach output schemas. In that case Predict._coerce
validates/coerces values before writing them.
This design keeps memory explicit. A later leaf sees earlier results only if they were written to the blackboard and named in its signature. That makes traces and compiled packages easier to audit because every model input field is visible.
React¤
React is a model-driven tool loop inside one leaf.
- derive JSON schemas from the provided
@toolfunctions - ask the model for a tool call or final answer
- run requested tools
- feed tool results back to the model
- stop when a final answer arrives or
max_stepsis hit
Unknown tools and tool exceptions become error observations inside the loop. The leaf fails if no final answer arrives.
React derives each tool schema from the wrapped Python function signature. It
maps common Python annotations to JSON Schema types, marks parameters without
defaults as required, then calls LanguageModel.converse. Anthropic and
OpenAI-compatible providers use different wire formats, but React sees the
same provider-neutral ModelTurn and ToolCall objects.
When tracing is enabled, the internal loop is not hidden. React emits
react.started, toolset.exposed, model.requested, model.responded, every
tool-call event, and react.finished. The compiler can then mine the internal
trajectory instead of seeing only the final answer.
Scoped Context¤
Every node can have context=.... Node._run_with_context pushes that text onto
the ContextWindow for the subtree and pops it afterward. The model sees the
joined stack as the system prompt.
This lets a root set task-wide policy and a leaf or subtree add local policy without permanently contaminating later leaves.
Runtime State¤
RunContext is defined in jdsl/context.py.
It carries:
blackboard: shared values and write provenancewindow: scoped system contextmodelandmodel_idstate: per-run scratch, used byoneshot- trace fields: sink, capture id, episode id, event source