Skip to content

Runtime API¤

This page is generated from the runtime source with mkdocstrings. The narrative walkthroughs explain why the pieces exist; this reference exposes the callable surface and implementation docstrings directly from code.

Authoring DSL¤

dsl ¤

The authoring surface: combinators. You build a skill by nesting calls, not tagged literals — the tree structure is the call structure. act is typed with ParamSpec, so literal args are checked against the tool's real signature.

Classes:

  • Tool

    Named, still-callable wrapper around a function; carries name/description

Functions:

  • tool

    Register a callable as a tool. Bare (@tool) or with args (@tool(description=...)).

  • act

    Leaf calling fn(args, *kwargs). Literal args are type-checked; a ref(...)

  • ref

    A blackboard reference for an act argument: act(fetch, ref("query")).

  • store

    Capture an action's return value onto the blackboard under key.

  • seq

    Run children in order; fail fast (AND).

  • sel

    Try children until one succeeds (OR).

  • check

    Guard leaf: succeeds iff blackboard[key] == equals.

  • guard

    Compiled state predicate over the blackboard using the restricted expression

  • guard_call

    Guard backed by a trusted runtime predicate (§21.2). arguments may contain

  • repeat

    Run child up to max times, stopping early when until (e.g. a check) succeeds.

  • invert

    Flip a child's status: SUCCESS <-> FAILURE (e.g. invert(check(...))).

  • optional

    Fail-soft wrapper: run the child but always succeed, so it can't abort a seq.

  • timeout

    Bound a child to seconds of wall-clock time; FAILURE if it overruns.

  • oneshot

    Run the child at most once per run; replay its status on later ticks.

  • predict

    DSPy-style LLM leaf from a signature like "question -> answer".

  • react

    Agentic LLM leaf: the model reasons and calls the given @tools in a loop

  • root

    Entry point of a skill. Chain .model(id) and .do(child).

Tool dataclass ¤

Tool(fn: Callable[..., Any], name: str, description: str = '')

Named, still-callable wrapper around a function; carries name/description used by react to expose it to the model for function-calling.

tool ¤

tool(fn: Callable[P, R]) -> Callable[P, R]
tool(*, name: str | None = ..., description: str | None = ...) -> Callable[[Callable[P, R]], Callable[P, R]]
tool(fn: Callable[P, R] | None = None, *, name: str | None = None, description: str | None = None) -> Any

Register a callable as a tool. Bare (@tool) or with args (@tool(description=...)).

Source code in jdsl/dsl.py
51
52
53
54
55
56
def tool(fn: Callable[P, R] | None = None, *, name: str | None = None, description: str | None = None) -> Any:
    """Register a callable as a tool. Bare (@tool) or with args (@tool(description=...))."""
    def wrap(f: Callable[P, R]) -> Tool:
        return Tool(fn=f, name=name or getattr(f, "__name__", "tool"),
                    description=description or (f.__doc__ or "").strip())
    return wrap(fn) if fn is not None else wrap

act ¤

act(fn: Callable[P, Any], *args: args, **kwargs: kwargs) -> Action

Leaf calling fn(args, *kwargs). Literal args are type-checked; a ref(...) arg resolves from the blackboard at run time. Wrap with store to capture the result.

Pass an id= (as a kwarg) to give the node a stable compiled identity (§20); it is popped before the call so it never reaches the tool.

Source code in jdsl/dsl.py
65
66
67
68
69
70
71
72
def act(fn: Callable[P, Any], *args: P.args, **kwargs: P.kwargs) -> Action:
    """Leaf calling fn(*args, **kwargs). Literal args are type-checked; a ref(...)
    arg resolves from the blackboard at run time. Wrap with store to capture the result.

    Pass an `id=` (as a kwarg) to give the node a stable compiled identity (§20);
    it is popped before the call so it never reaches the tool."""
    node_id = kwargs.pop("id", None)
    return _ident(Action(fn=fn, args=args, kwargs=kwargs), node_id)  # type: ignore[arg-type]

ref ¤

ref(name: str) -> Ref

A blackboard reference for an act argument: act(fetch, ref("query")).

Source code in jdsl/dsl.py
75
76
77
def ref(name: str) -> Ref:
    """A blackboard reference for an act argument: act(fetch, ref("query"))."""
    return Ref(name)

store ¤

store(node: Action, key: str) -> Action

Capture an action's return value onto the blackboard under key.

Source code in jdsl/dsl.py
80
81
82
83
def store(node: Action, key: str) -> Action:
    """Capture an action's return value onto the blackboard under key."""
    node.store_as = key
    return node

seq ¤

seq(*children: Node, context: str | None = None, id: str | None = None) -> Sequence

Run children in order; fail fast (AND).

Source code in jdsl/dsl.py
86
87
88
def seq(*children: Node, context: str | None = None, id: str | None = None) -> Sequence:
    """Run children in order; fail fast (AND)."""
    return _ident(Sequence(children=list(children), context_system=context), id)  # type: ignore[return-value]

sel ¤

sel(*children: Node, context: str | None = None, id: str | None = None) -> Selector

Try children until one succeeds (OR).

Source code in jdsl/dsl.py
91
92
93
def sel(*children: Node, context: str | None = None, id: str | None = None) -> Selector:
    """Try children until one succeeds (OR)."""
    return _ident(Selector(children=list(children), context_system=context), id)  # type: ignore[return-value]

check ¤

check(key: str, equals: Any, *, id: str | None = None) -> Check

Guard leaf: succeeds iff blackboard[key] == equals.

Source code in jdsl/dsl.py
96
97
98
def check(key: str, equals: Any, *, id: str | None = None) -> Check:
    """Guard leaf: succeeds iff blackboard[key] == equals."""
    return _ident(Check(key=key, equals=equals), id)  # type: ignore[return-value]

guard ¤

guard(expression: dict[str, Any], *, id: str | None = None) -> Guard

Compiled state predicate over the blackboard using the restricted expression language (§21.2), e.g. guard({"in": [{"ref": "order.status"}, ["pending"]]}).

Source code in jdsl/dsl.py
101
102
103
104
def guard(expression: dict[str, Any], *, id: str | None = None) -> Guard:
    """Compiled state predicate over the blackboard using the restricted expression
    language (§21.2), e.g. guard({"in": [{"ref": "order.status"}, ["pending"]]})."""
    return _ident(Guard(expression=expression), id)  # type: ignore[return-value]

guard_call ¤

guard_call(predicate: Callable[..., Any], arguments: dict[str, Any] | None = None, *, predicate_id: str | None = None, id: str | None = None) -> GuardCall

Guard backed by a trusted runtime predicate (§21.2). arguments may contain ref(...) values resolved from the blackboard at run time.

Source code in jdsl/dsl.py
107
108
109
110
111
112
def guard_call(predicate: Callable[..., Any], arguments: dict[str, Any] | None = None, *,
               predicate_id: str | None = None, id: str | None = None) -> GuardCall:
    """Guard backed by a trusted runtime predicate (§21.2). arguments may contain
    ref(...) values resolved from the blackboard at run time."""
    node = GuardCall(predicate=predicate, arguments=arguments or {}, predicate_id=predicate_id)
    return _ident(node, id)  # type: ignore[return-value]

repeat ¤

repeat(child: Node, *, until: Node | None = None, max: int = 3, context: str | None = None, id: str | None = None) -> Repeat

Run child up to max times, stopping early when until (e.g. a check) succeeds.

Source code in jdsl/dsl.py
115
116
117
118
def repeat(child: Node, *, until: Node | None = None, max: int = 3, context: str | None = None,
           id: str | None = None) -> Repeat:
    """Run child up to `max` times, stopping early when `until` (e.g. a check) succeeds."""
    return _ident(Repeat(child=child, until=until, max=max, context_system=context), id)  # type: ignore[return-value]

invert ¤

invert(child: Node, *, context: str | None = None, id: str | None = None) -> Invert

Flip a child's status: SUCCESS <-> FAILURE (e.g. invert(check(...))).

Source code in jdsl/dsl.py
121
122
123
def invert(child: Node, *, context: str | None = None, id: str | None = None) -> Invert:
    """Flip a child's status: SUCCESS <-> FAILURE (e.g. invert(check(...)))."""
    return _ident(Invert(child=child, context_system=context), id)  # type: ignore[return-value]

optional ¤

optional(child: Node, *, context: str | None = None, id: str | None = None) -> Optional

Fail-soft wrapper: run the child but always succeed, so it can't abort a seq.

Source code in jdsl/dsl.py
126
127
128
def optional(child: Node, *, context: str | None = None, id: str | None = None) -> Optional:
    """Fail-soft wrapper: run the child but always succeed, so it can't abort a seq."""
    return _ident(Optional(child=child, context_system=context), id)  # type: ignore[return-value]

timeout ¤

timeout(child: Node, *, seconds: float = 30.0, context: str | None = None, id: str | None = None) -> Timeout

Bound a child to seconds of wall-clock time; FAILURE if it overruns.

Source code in jdsl/dsl.py
131
132
133
def timeout(child: Node, *, seconds: float = 30.0, context: str | None = None, id: str | None = None) -> Timeout:
    """Bound a child to `seconds` of wall-clock time; FAILURE if it overruns."""
    return _ident(Timeout(child=child, seconds=seconds, context_system=context), id)  # type: ignore[return-value]

oneshot ¤

oneshot(child: Node, *, context: str | None = None, id: str | None = None) -> OneShot

Run the child at most once per run; replay its status on later ticks.

Source code in jdsl/dsl.py
136
137
138
def oneshot(child: Node, *, context: str | None = None, id: str | None = None) -> OneShot:
    """Run the child at most once per run; replay its status on later ticks."""
    return _ident(OneShot(child=child, context_system=context), id)  # type: ignore[return-value]

predict ¤

predict(signature: str, *, instructions: str | None = None, context: str | None = None, id: str | None = None) -> Predict

DSPy-style LLM leaf from a signature like "question -> answer".

Source code in jdsl/dsl.py
141
142
143
144
145
146
def predict(signature: str, *, instructions: str | None = None, context: str | None = None,
            id: str | None = None) -> Predict:
    """DSPy-style LLM leaf from a signature like "question -> answer"."""
    inputs, outputs = _parse_signature(signature)
    return _ident(Predict(inputs=inputs, outputs=outputs, instructions=instructions,  # type: ignore[return-value]
                          context_system=context), id)

react ¤

react(signature: str, *, tools: list[Any], instructions: str | None = None, max_steps: int = 6, context: str | None = None, id: str | None = None) -> React

Agentic LLM leaf: the model reasons and calls the given @tools in a loop (native function-calling) until it answers. Signature is "inputs -> answer" with a single output field.

Source code in jdsl/dsl.py
149
150
151
152
153
154
155
156
157
158
def react(signature: str, *, tools: list[Any], instructions: str | None = None,
          max_steps: int = 6, context: str | None = None, id: str | None = None) -> React:
    """Agentic LLM leaf: the model reasons and calls the given @tools in a loop
    (native function-calling) until it answers. Signature is "inputs -> answer"
    with a single output field."""
    inputs, outputs = _parse_signature(signature)
    if len(outputs) != 1:
        raise ValueError(f"react signature {signature!r} must have exactly one output (the answer).")
    return _ident(React(inputs=inputs, outputs=outputs, tools=list(tools),  # type: ignore[return-value]
                        instructions=instructions, max_steps=max_steps, context_system=context), id)

root ¤

root(name: str, *, system: str | None = None) -> Root

Entry point of a skill. Chain .model(id) and .do(child).

Source code in jdsl/dsl.py
161
162
163
def root(name: str, *, system: str | None = None) -> Root:
    """Entry point of a skill. Chain .model(id) and .do(child)."""
    return Root(name=name, context_system=system)

Runtime Nodes¤

tree ¤

Behavior-tree nodes and the tree-walking interpreter (no codegen).

seq=AND, sel=OR, act=call a tool, check=guard on the blackboard, predict=LLM leaf, root=entry point. Every node ticks to a Status; determinism is in the tree, the model only at predict.

Classes:

  • Status
  • Node

    Base node. Subclasses implement _tick; tick wraps it with trace

  • Action

    Leaf calling a tool. SUCCESS = didn't raise; a returned Status is honored;

  • Sequence

    Run children in order; fail fast (AND).

  • Selector

    Try children until one succeeds (OR).

  • Repeat

    Run child up to max times, stopping early when until succeeds (checked

  • Check

    Guard leaf: SUCCESS iff blackboard[key] matches equals. String matches are

  • Guard

    Compiled state predicate (§21.2): SUCCESS iff the restricted expression is

  • GuardCall

    Guard backed by a trusted runtime predicate (§21.2 guard_call). Domain

  • Predict

    DSPy-style LLM leaf: read input fields, ask for output fields as JSON,

  • React

    Agentic LLM leaf: the model reasons and calls @tools in a loop (native

  • Invert

    Flip the child's status: SUCCESS <-> FAILURE.

  • Optional

    Fail-soft: run the child but always report SUCCESS, so a failing step never

  • Timeout

    Run the child with a wall-clock bound; FAILURE if it doesn't finish in

  • OneShot

    Run the child at most once per run; latch and replay its status on any

  • Root

    Entry point: one child + name/system/model. Also a builder.

Status ¤

Bases: Enum

Node ¤

Base node. Subclasses implement _tick; tick wraps it with trace emission (node.enter/node.exit, §35 PR1). Context is scoped to the subtree.

node_id is the optional stable identity used by compiled artifacts (§20): author-supplied via the DSL id= argument, otherwise a path-derived runtime id is assigned by assign_runtime_ids when a run is being traced. Tree path is deliberately not the persistent identity — a compiler may insert nodes.

Methods:

  • effective_id

    The id used in traces/IR: author id if set, else the runtime path id.

  • label

    Short human-readable label for rendering / write provenance.

  • tick

    Public entry: emit enter/exit around the subclass _tick when tracing.

effective_id ¤
effective_id() -> str | None

The id used in traces/IR: author id if set, else the runtime path id.

Source code in jdsl/tree.py
55
56
57
def effective_id(self) -> str | None:
    """The id used in traces/IR: author id if set, else the runtime path id."""
    return self.node_id or self._runtime_id
label ¤
label() -> str

Short human-readable label for rendering / write provenance.

Source code in jdsl/tree.py
63
64
65
def label(self) -> str:
    """Short human-readable label for rendering / write provenance."""
    return type(self).__name__.lower()
tick ¤
tick(ctx: RunContext) -> Status

Public entry: emit enter/exit around the subclass _tick when tracing.

Source code in jdsl/tree.py
43
44
45
46
47
48
49
50
51
52
53
def tick(self, ctx: RunContext) -> Status:
    """Public entry: emit enter/exit around the subclass `_tick` when tracing."""
    if ctx.trace_sink is None:
        return self._tick(ctx)
    from jdsl.trace.events import EventKind
    enter = ctx.emit(EventKind.NODE_ENTER, payload=self._trace_meta())
    parent = enter.event_id if enter is not None else None
    status = self._tick(ctx)
    ctx.emit(EventKind.NODE_EXIT, parent_event_id=parent,
             payload={**self._trace_meta(), "status": status.value})
    return status

Action dataclass ¤

Action(fn: Callable[..., Any], args: tuple[Any, ...] = (), kwargs: dict[str, Any] = dict(), store_as: str | None = None, context_system: str | None = None)

Bases: Node

Leaf calling a tool. SUCCESS = didn't raise; a returned Status is honored; any other return is stored under store_as.

Sequence dataclass ¤

Sequence(children: list[Node] = list(), context_system: str | None = None)

Bases: Node

Run children in order; fail fast (AND).

Selector dataclass ¤

Selector(children: list[Node] = list(), context_system: str | None = None)

Bases: Node

Try children until one succeeds (OR).

Repeat dataclass ¤

Repeat(child: Node, until: Node | None = None, max: int = 3, context_system: str | None = None)

Bases: Node

Run child up to max times, stopping early when until succeeds (checked after each pass). SUCCESS when until is satisfied — or when there's no until (a fixed loop). FAILURE if max is reached unsatisfied, or the child fails. Behavior-tree repeat/retry decorator; do-while, not while.

Check dataclass ¤

Check(key: str, equals: Any, context_system: str | None = None)

Bases: Node

Guard leaf: SUCCESS iff blackboard[key] matches equals. String matches are lenient — case-insensitive, whitespace- and surrounding-punctuation-trimmed — because the value is usually fuzzy model text ("Yes." should match "yes"). Non-string values compare with plain ==.

Guard dataclass ¤

Guard(expression: dict[str, Any], context_system: str | None = None)

Bases: Node

Compiled state predicate (§21.2): SUCCESS iff the restricted expression is true over the blackboard. This is the general guard check is too narrow for (§5.4) — it reads refs/paths and combines them with eq/in/and/or/… . The expression is a safe JSON tree, never arbitrary code.

GuardCall dataclass ¤

GuardCall(predicate: Callable[..., Any], arguments: dict[str, Any] = dict(), predicate_id: str | None = None, context_system: str | None = None)

Bases: Node

Guard backed by a trusted runtime predicate (§21.2 guard_call). Domain logic that exceeds the expression system references a named capability the runtime supplies; the package only names it, it never ships its code.

Predict dataclass ¤

Predict(inputs: tuple[str, ...], outputs: tuple[str, ...], instructions: str | None = None, context_system: str | None = None, output_schemas: dict[str, dict[str, Any]] | None = None, signature_id: str | None = None)

Bases: Node

DSPy-style LLM leaf: read input fields, ask for output fields as JSON, write them back. FAILURE if nothing parseable comes back.

output_schemas (compiled signatures, §18) attaches a JSON-schema fragment per output; when present the parsed value is coerced to the declared type and validated (integer index, enum, …) before it is written. Absent (the authoring default) leaves behavior unchanged — a single free-text output stored verbatim.

React dataclass ¤

React(inputs: tuple[str, ...], outputs: tuple[str, ...], tools: list[Any] = list(), instructions: str | None = None, max_steps: int = 6, context_system: str | None = None)

Bases: Node

Agentic LLM leaf: the model reasons and calls @tools in a loop (native provider function-calling) until it answers. Reads input fields, runs each tool the model picks, feeds results back, and writes the final answer to the single output field. FAILURE if the model answers empty or max_steps is hit without a final answer.

Invert dataclass ¤

Invert(child: Node, context_system: str | None = None)

Bases: Decorator

Flip the child's status: SUCCESS <-> FAILURE.

Optional dataclass ¤

Optional(child: Node, context_system: str | None = None)

Bases: Decorator

Fail-soft: run the child but always report SUCCESS, so a failing step never aborts its parent sequence (py_trees' FailureIsSuccess).

Timeout dataclass ¤

Timeout(child: Node, context_system: str | None = None, seconds: float = 30.0)

Bases: Decorator

Run the child with a wall-clock bound; FAILURE if it doesn't finish in seconds. The child runs in a worker thread — on timeout it is abandoned (Python can't kill it), so use this for read-only/idempotent work like an LLM or lookup call.

OneShot dataclass ¤

OneShot(child: Node, context_system: str | None = None)

Bases: Decorator

Run the child at most once per run; latch and replay its status on any later tick (only observable inside a repeat/loop). State is per-run.

Root dataclass ¤

Root(name: str, child: Node | None = None, context_system: str | None = None, model_id: str | None = None)

Bases: Node

Entry point: one child + name/system/model. Also a builder.

Methods:

  • run

    Execute the skill and return the final RunContext (read ctx.blackboard).

run ¤
run(*, model: Any = None, trace_sink: Any = None, capture_id: str = 'cap_local', episode_id: str = 'ep_local', trace_source: Any = None, **inputs: Any) -> RunContext

Execute the skill and return the final RunContext (read ctx.blackboard).

Pass a trace_sink to capture a canonical event stream for this run (episode episode_id under capture_id); omit it and the run is untraced and behaves exactly as before.

Source code in jdsl/tree.py
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
def run(self, *, model: Any = None, trace_sink: Any = None, capture_id: str = "cap_local",
        episode_id: str = "ep_local", trace_source: Any = None, **inputs: Any) -> RunContext:
    """Execute the skill and return the final RunContext (read ctx.blackboard).

    Pass a `trace_sink` to capture a canonical event stream for this run
    (episode `episode_id` under `capture_id`); omit it and the run is
    untraced and behaves exactly as before."""
    from jdsl.context import Blackboard
    if model is None and self.model_id is not None:
        from jdsl.provider import LanguageModel
        model = LanguageModel.from_config()
    if trace_sink is not None:
        assign_runtime_ids(self)
    ctx = RunContext(blackboard=Blackboard(inputs), model=model, model_id=self.model_id,
                     trace_sink=trace_sink, capture_id=capture_id, episode_id=episode_id,
                     trace_source=trace_source)
    if trace_sink is not None:
        from jdsl.trace.events import EventKind
        ctx.emit(EventKind.EPISODE_STARTED, payload={"skill": self.name, "inputs": dict(inputs)})
    self.tick(ctx)
    if trace_sink is not None:
        from jdsl.trace.events import EventKind
        ctx.emit(EventKind.EPISODE_FINISHED, payload={"skill": self.name})
    return ctx

Runtime State¤

context ¤

Runtime state threaded through a run: the blackboard, the context window, and the model handle. Nodes read/write the blackboard and scope system text on the window as the tree is walked.

Classes:

  • Write

    One record of a blackboard write: what, to what, by whom, over what.

  • Blackboard

    Shared key/value store for one run — a dict with provenance. Every write is

  • Ref

    Placeholder for blackboard[name], resolved when an act runs.

  • ToolCall

    A provider-neutral tool call the model asked for: name + parsed arguments.

  • ModelTurn

    One provider-neutral assistant turn: free text and/or tool calls. Empty

  • ContextWindow

    A stack of system fragments, pushed/popped so context is subtree-scoped.

  • RunContext

Write dataclass ¤

Write(key: str, value: Any, writer: str, previous: Any = None, overwrote: bool = False)

One record of a blackboard write: what, to what, by whom, over what.

Blackboard ¤

Blackboard(initial: dict[str, Any] | None = None, /, **kwargs: Any)

Bases: dict[str, Any]

Shared key/value store for one run — a dict with provenance. Every write is recorded in activity with its writer, and overwrites by a different writer are flagged (the silent-clobber bug when two leaves share an output name). Read with normal dict access; write via set(key, value, writer=...) to attribute it.

Source code in jdsl/context.py
33
34
35
36
37
38
39
40
41
42
def __init__(self, initial: dict[str, Any] | None = None, /, **kwargs: Any) -> None:
    super().__init__()
    self.activity: list[Write] = []
    self._writer: dict[str, str] = {}
    # optional trace hook: RunContext installs a callback so every write can be
    # emitted as a blackboard.write event (§35 PR1) without the blackboard
    # needing to know about the trace layer. None => no capture, no overhead.
    self.on_write: Callable[[Write], None] | None = None
    for k, v in {**(initial or {}), **kwargs}.items():
        self.set(k, v, writer="input")

Ref dataclass ¤

Ref(name: str)

Placeholder for blackboard[name], resolved when an act runs.

ToolCall dataclass ¤

ToolCall(id: str, name: str, arguments: dict[str, Any])

A provider-neutral tool call the model asked for: name + parsed arguments.

ModelTurn dataclass ¤

ModelTurn(text: str = '', tool_calls: list[ToolCall] = list())

One provider-neutral assistant turn: free text and/or tool calls. Empty tool_calls means the model is done and text is its final answer.

ContextWindow dataclass ¤

ContextWindow(_system: list[str] = list())

A stack of system fragments, pushed/popped so context is subtree-scoped.

RunContext dataclass ¤

RunContext(blackboard: Blackboard = Blackboard(), window: ContextWindow = ContextWindow(), model: LanguageModel | None = None, model_id: str | None = None, state: dict[int, Any] = dict(), trace_sink: TraceSink | None = None, capture_id: str = 'cap_local', episode_id: str = 'ep_local', trace_source: EventSource | None = None, _event_tail: str | None = None)

Methods:

  • emit

    Emit one canonical trace event on this run's sink. No-op (returns None)

emit ¤
emit(kind: str, *, payload: dict[str, Any] | None = None, actor: str = 'system', parent_event_id: str | None = None, blob_refs: list[str] | None = None) -> TraceEvent | None

Emit one canonical trace event on this run's sink. No-op (returns None) when there is no sink. The sink assigns sequence + hash chain.

Source code in jdsl/context.py
143
144
145
146
147
148
149
150
151
152
153
def emit(self, kind: str, *, payload: dict[str, Any] | None = None, actor: str = "system",
         parent_event_id: str | None = None, blob_refs: list[str] | None = None) -> TraceEvent | None:
    """Emit one canonical trace event on this run's sink. No-op (returns None)
    when there is no sink. The sink assigns sequence + hash chain."""
    if self.trace_sink is None:
        return None
    from jdsl.trace.events import TraceEvent
    event = TraceEvent.new(kind, self.capture_id, self.episode_id, payload=payload,
                           actor=actor, source=self.trace_source,
                           parent_event_id=parent_event_id, blob_refs=blob_refs)
    return self.trace_sink.emit(event)

Providers¤

provider ¤

The LLM backend. LanguageModel.generate dispatches by model-id prefix, pulls keys from per-provider routers, and rotates them on auth/rate-limit failures. Tinker base models use its OpenAI-compatible text-completions API.

Classes:

  • LanguageModel

    Provider-dispatching language model with key rotation.

Attributes:

DEFAULT_MODEL module-attribute ¤

DEFAULT_MODEL = 'claude-opus-4-8'

LanguageModel ¤

LanguageModel()

Provider-dispatching language model with key rotation.

Methods:

  • converse

    One tool-calling turn. messages is neutral history (user/assistant/tool

Source code in jdsl/provider.py
24
25
def __init__(self) -> None:
    self._routers: dict[str, RoundRobinRouter] = {}
converse ¤
converse(*, system: str, messages: list[dict], tools: list[dict], model_id: str | None = None) -> ModelTurn

One tool-calling turn. messages is neutral history (user/assistant/tool items), tools is neutral specs (name/description/parameters). Returns a ModelTurn: final text, or tool calls to run and feed back. Used by react.

Source code in jdsl/provider.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def converse(self, *, system: str, messages: list[dict], tools: list[dict],
             model_id: str | None = None) -> ModelTurn:
    """One tool-calling turn. `messages` is neutral history (user/assistant/tool
    items), `tools` is neutral specs (name/description/parameters). Returns a
    ModelTurn: final text, or tool calls to run and feed back. Used by react."""
    model = model_id or DEFAULT_MODEL
    provider = config.provider_for_model(model)
    if provider == "tinker":
        raise RuntimeError("Tinker models support predict/generate only; react requires chat tool calling.")
    router = self._router(provider)
    backend = _anthropic_converse if provider == "anthropic" else _openai_converse
    last_error: Exception | None = None
    for _ in range(_MAX_ATTEMPTS):
        try:
            return backend(api_key=router.current(), provider=provider, model=model,
                           system=system, messages=messages, tools=tools)
        except _RetryableAuthError as err:
            last_error = err.__cause__ or err
            router.rotate()
    raise RuntimeError(f"LanguageModel.converse failed after {_MAX_ATTEMPTS} attempts "
                       f"for provider {provider!r}.") from last_error

config ¤

Credential storage + provider inference. Keys live in ~/.local/share/recon/auth.json as {"": {"api_keys": [...]}}, and a .env in the working directory is loaded on import.

Functions:

Attributes:

SUPPORTED_PROVIDERS module-attribute ¤

SUPPORTED_PROVIDERS = ('anthropic', 'openai', 'deepseek', 'google', 'tinker')

ENV_KEYS module-attribute ¤

ENV_KEYS = {'anthropic': 'ANTHROPIC_API_KEY', 'openai': 'OPENAI_API_KEY', 'deepseek': 'DEEPSEEK_API_KEY', 'google': 'GOOGLE_API_KEY', 'tinker': 'TINKER_API_KEY'}

BASE_URLS module-attribute ¤

BASE_URLS = {'deepseek': 'https://api.deepseek.com', 'openai': None, 'tinker': 'https://tinker.thinkingmachines.dev/services/tinker-prod/oai/api/v1'}

provider_for_model ¤

provider_for_model(model_id: str) -> str
Source code in jdsl/config.py
33
34
35
36
37
38
39
40
def provider_for_model(model_id: str) -> str:
    name = model_id.lower()
    if name.startswith("claude"): return "anthropic"
    if name.startswith("deepseek"): return "deepseek"
    if name.startswith(("gpt", "o1", "o3", "o4")): return "openai"
    if name.startswith("gemini"): return "google"
    if name.startswith(("thinkingmachines/", "inkling")): return "tinker"
    return "anthropic"

config_dir ¤

config_dir() -> Path
Source code in jdsl/config.py
43
44
45
def config_dir() -> Path:
    base = os.environ.get("XDG_DATA_HOME") or (Path.home() / ".local" / "share")
    return Path(base) / "recon"

auth_path ¤

auth_path() -> Path
Source code in jdsl/config.py
48
def auth_path() -> Path: return config_dir() / "auth.json"

load ¤

load() -> dict[str, dict[str, list[str]]]
Source code in jdsl/config.py
51
52
53
def load() -> dict[str, dict[str, list[str]]]:
    path = auth_path()
    return json.loads(path.read_text()) if path.exists() else {}

save ¤

save(config: dict[str, dict[str, list[str]]]) -> None
Source code in jdsl/config.py
56
57
58
59
def save(config: dict[str, dict[str, list[str]]]) -> None:
    path = auth_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(config, indent=2))

add_keys ¤

add_keys(provider: str, keys: list[str]) -> list[str]

Merge keys into provider's list, de-duplicating. Returns the merged list.

Source code in jdsl/config.py
62
63
64
65
66
67
68
69
70
71
72
def add_keys(provider: str, keys: list[str]) -> list[str]:
    """Merge keys into provider's list, de-duplicating. Returns the merged list."""
    if provider not in SUPPORTED_PROVIDERS:
        raise ValueError(f"Unknown provider {provider!r}. Supported: {', '.join(SUPPORTED_PROVIDERS)}.")
    config = load()
    merged = list(config.get(provider, {}).get("api_keys", []))
    for key in keys:
        if key not in merged: merged.append(key)
    config[provider] = {"api_keys": merged}
    save(config)
    return merged

keys_for ¤

keys_for(provider: str) -> list[str]

Stored keys for provider, else the provider's env var.

Source code in jdsl/config.py
75
76
77
78
79
80
def keys_for(provider: str) -> list[str]:
    """Stored keys for provider, else the provider's env var."""
    stored = load().get(provider, {}).get("api_keys", [])
    if stored: return list(stored)
    env_value = os.environ.get(ENV_KEYS.get(provider, ""))
    return [env_value] if env_value else []