Using the Harness and Compiler¤
The harness flow has four verbs:
capture -> inspect -> compile -> run
Use this when you have successful tool-using trajectories and want to turn the reusable parts into executable jdsl policy.
This guide covers the current CLI/API harness. The TUI is presently focused on authoring skills and does not start the ingest server or manage captures.
Install¤
Quick install (curl):
curl -fsSL https://cantorindustries.com/jdsl-py/install.sh | bash
Development setup (uv):
uv sync
The published CLI includes the harness dependency by default. The compiler and tests use fake models and can run offline.
For an existing lightweight/library installation that intentionally omitted the default harness dependency, install it explicitly with:
python -m pip install 'jdsl[harness]'
Store Location¤
The harness stores metadata, event spools, and blobs under one directory:
export JDSL_HARNESS_HOME=/tmp/jdsl-harness
If unset, the default is ~/.local/share/jdsl-harness.
1. Capture¤
Choose one capture tier. Tier A is the highest-fidelity path when the execution is already using jdsl; Tier B is for supported host hooks; Tier C is for logs that already exist.
Tier A: jdsl-Native or Gateway¤
A jdsl skill can record itself when you attach a trace sink:
from jdsl_harness.capture import CaptureCoordinator
from jdsl_harness.store import HarnessStore
store = HarnessStore("/tmp/jdsl-harness")
coord = CaptureCoordinator(store)
cap = coord.start(host="jdsl", adapter="runtime")
skill.run(
trace_sink=store.sink(cap),
capture_id=cap,
episode_id="ep_0",
email="ada@example.com",
request="cancel my order",
)
coord.mark_outcome(cap, "ep_0", reward=1.0)
coord.finish(cap)
For non-jdsl callables, wrap tools with ToolGateway. For MCP tools, use the
MCP proxy path when you need transparent tool-call recording.
Tier B: Host Hooks¤
Start the local ingest daemon:
jdsl harness serve
Start a capture in the same harness store and keep the printed token private:
uv run jdsl capture start --host claude-code --adapter hooks
# capture_id=cap_...
# capture_token=...
Then install a host shim:
plugins/jdsl-claude-plugin/
plugins/jdsl-gemini-extension/
plugins/jdsl-opencode-plugin/
The shims forward structured hook payloads to the loopback server. They fail open: if capture is unavailable, host tool execution continues.
Common environment:
| Variable | Default | Meaning |
|---|---|---|
JDSL_INGEST_URL |
http://127.0.0.1:8848 |
ingest daemon base URL |
JDSL_CAPTURE_ID |
from capture start |
capture id to route into |
JDSL_CAPTURE_TOKEN |
from capture start |
per-capture token sent in X-JDSL-Capture-Token |
JDSL_HOOK_TIMEOUT |
0.5 |
max seconds to wait in the hook |
See OpenCode Capture for OpenCode-specific steps.
Tier C: Imported Logs¤
Import generic JSONL logs:
uv run jdsl capture import runs.jsonl --capture cap_imported
Each line is one episode:
{"episode_id": "ep_1", "steps": [{"tool": "lookup", "args": {"email": "a@b.com"}, "result": {"id": "C1"}}], "outcome": {"reward": 1.0}}
2. Inspect¤
Inspect the captured lineage before compiling:
uv run jdsl capture list
uv run jdsl capture inspect cap_imported
The report shows exact value flows, deterministic candidates, residual semantic candidates, retries, and episode success.
3. Compile¤
Compile a capture into a .jdsl package:
uv run jdsl compile cap_imported --name retail --out retail.jdsl
The report includes verification status, required capabilities, declared run inputs, exact dataflow refs, deterministic coverage, and residual decision burden.
The default compiler is deterministic and heuristic. It staticizes supported structure from the evidence and leaves unexplained semantic choices as model signatures. It does not infer arbitrary Python code or guarantee behavior on tasks outside the captured evidence.
4. Run¤
Inspect and verify the package:
uv run jdsl package inspect retail.jdsl
uv run jdsl package verify retail.jdsl
Create a bindings file:
def lookup(email):
return {"id": "C1", "email": email}
def list_orders(customer_id):
return [{"id": "O1", "status": "pending"}]
def get_order(order_id):
return {"id": order_id, "status": "pending"}
TOOLS = {
"lookup": lookup,
"list_orders": list_orders,
"get_order": get_order,
}
Run the package:
uv run jdsl package run retail.jdsl --tools tools.py \
--input email=ada@example.com \
--input request="cancel my order"
If the package contains residual model leaves, pass --model <model-id> or bind
a model programmatically. Deterministic packages run without a model.
Package execution is capability-bound: every required logical tool must be
present in TOOLS before the package starts. The package itself never imports
host code by path.
Metrics¤
The compiler and package metrics focus on model responsibility:
| Metric | Meaning |
|---|---|
residual_decision_burden |
fraction of meaningful decisions still delegated to the model |
deterministic_coverage |
fraction represented by deterministic nodes |
exact_dataflow_refs |
arguments replaced by verified refs |
inputs |
run inputs inferred from varying unlinked arguments |
replay_coverage |
deterministic refs and guards reproduced against traces |
Static package metrics are structural. Runtime latency and true tokenizer counts require execution telemetry.