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
58
59
60
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
66
67
68
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
46
47
48
49
50
51
52
53
54
55
56
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, input_sources: dict[str, str] | None = None, examples: list[dict[str, Any]] = list())

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, input_sources: dict[str, str] | None = None, examples: list[dict[str, Any]] = list())

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).

  • to_package –

    Build a portable package from this tree without changing direct run().

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
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
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
to_package ¤
to_package(*, tool_contracts: dict[str, ToolContract] | None = None, task_family: str = '', version: str = '0.1.0') -> BehaviorPackage

Build a portable package from this tree without changing direct run().

tool_contracts maps each callable's exposed name to its portable ToolContract. Python implementations are never embedded in the package.

Source code in jdsl/tree.py
708
709
710
711
712
713
714
715
716
717
def to_package(self, *, tool_contracts: dict[str, ToolContract] | None = None,
               task_family: str = "", version: str = "0.1.0") -> BehaviorPackage:
    """Build a portable package from this tree without changing direct run().

    `tool_contracts` maps each callable's exposed name to its portable
    ToolContract. Python implementations are never embedded in the package.
    """
    from jdsl.package.authoring import package_from_root
    return package_from_root(self, tool_contracts=tool_contracts,
                             task_family=task_family, version=version)

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 []