Skip to main content
The harness needs two touchpoints with your loop, whatever it is built with: the system context in the prompt and a turn boundary. Attaching the read-only task tools is a third, optional one. No adapter required.
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.
repair_model — or HARNESS_REPAIR_MODEL — is required. The repair agent makes its own model calls, and no billable default is selected for you. Use observe_only=True to evaluate and journal without mutating anything.
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.
Because it never changes, a prompt fixed at construction time is as good as one rebuilt per turn. The session_id is required so the call shape stays uniform across integrations; task_hint is accepted and ignored.

2. The tools (optional)

Four read-only operations, or none — the loop works either way. Evaluation, notices, repair, and validation all run regardless; the tools only decide whether your agent can consult what was learned. 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

Delimit turns, not tasks. Score history is keyed per session, so a loop that fires turn-end once per task gives the trajectory gate exactly one sample per series — it can never reach gate_window, and nothing will ever fire. This is the easiest way to silently disable detection altogether.
Three equivalent styles — pick whichever fits your loop:
Or call the hook directly with a raw payload — the 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.
Turn-end is fire-and-forget by default. To make a lesson take effect inside the session, await the turn’s evaluation and repair:
or explicitly, when you want the diagnosis back:
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 after on_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.
At a phase boundary — before you archive a workspace, snapshot a ruleset, or report results — wait on pending_sessions and validation_pending, then call settle_validation(). Waiting on evaluations alone lets you snapshot while candidates are still being decided, which records a rule that had already earned promotion as permanently provisional. drain_validation() returns False when it gave up, so you can tell a completed drain from a timeout.

Everything on the facade