Harness.create() provisions the workspace, wires every component (hook, mailbox, journal, rules, eval set, task tools, the repair agent), and schedules the startup health check.
It also accepts the two optional developer seams:
1. The system context
This is a constant: one sentence saying learned rules exist and naming the four read-only tools. No rule bodies, no scope index, no notice count — and building it reads nothing from the workspace. There is nothing to pre-select, because the agent conditions its own retrieval on the task with
harness_rules_search and harness_rules_read.session_id is required so the call shape stays uniform across integrations; task_hint is accepted and ignored.
2. The tools (optional)
as_anthropic_tools, as_langchain_tools, and as_openai_function_tools convert them to native formats — see the task toolset.
Route every
harness_* name to harness.task_tools.call rather than filtering names yourself. Enforcement lives in the dispatcher, so a hallucinated administrative call is rejected safely instead of reaching your domain executor.3. The turn boundary
Three equivalent styles — pick whichever fits your loop:end_state you pass is what the outcome verifier receives and what a captured eval case carries as its replay input:
Use the same session id for the harness turn and the SDK trace context (
pandaprobe.session(session_id)) — the evaluation scores whatever traces landed under that session.4. The barrier (recommended)
Turn-end is fire-and-forget by default. To make a lesson take effect inside the session, await the turn’s evaluation and repair:settle() waits for the turn’s evaluation, notice persistence, and one bounded repair attempt. It deliberately does not wait for a validation round — a replay can need the very resource this turn holds, so awaiting it here could deadlock.
The barrier has its own generous budget (barrier_timeout_s, default 180s), separate from drain_timeout_s. On expiry the work continues detached and timed_out is set — a slow platform costs you latency, never correctness.
settle() consumes the turn’s pending evaluation. If you settle and then settle the same turn index again to fetch the report, the second call finds no new traces and returns an impoverished one. Take the report from the call that did the work.Determinism helpers
Everything afteron_turn_end is detached and non-blocking. When a test or script needs to observe results deterministically:
refresh* and drain_validation are bounded joins — correctness never depends on them; each background task handles its own result.

