Controlling evaluation cost
The three-tier ladder is itself the primary cost control: the expensive metrics only run when the cheap ones say something is wrong.
Tier 1 is deliberately cheap —
coherence is embedding-based rather than an LLM judge — so the always-on rung is the least expensive one. Tier 3 is opt-in because every LLM-judge metric reads the whole trace, so cost scales with metric count.
Controlling repair cost
The repair agent is the loop’s second model consumer, and it only runs when a notice was posted — so the tier ladder gates it too. It is bounded on four axes:RepairResult.usage reports normalized input/output tokens and cost whenever the provider supplies them, so repair spend is measurable per episode rather than inferred. Repair also makes at most one episode per settled turn.
Validation is the third consumer, and the least predictable: each replay re-runs your agent. validation_round_budget_s bounds replay work per round, and regression_sample bounds a regression run.
Four independent dials bound the spend on top of that:
Two structural savers:
- Supersede — a newer turn cancels the session’s in-flight evaluation; you never pay for a stale turn’s scores.
- One batched run per turn — Tier 1 covers every new trace in a single create + poll, not one run per trace.
regression_sample), and every replay invocation is time-bounded (replay_timeout_s).
Latency and the barrier
The barrier is the one place the harness deliberately makes your loop wait. It buys in-session healing; the cost is per-turn latency bounded bybarrier_timeout_s (default 180s).
Degradation ladder
The harness’s core invariant: nothing on the harness’s side may break or stall your agent. Every failure mode has a defined, observable degradation:Concurrency model
on_turn_endis synchronous and cheap: parse, gate, schedule — it returns before any I/O.- Evaluations run as detached tasks under a global semaphore; candidate validation is a single-flight background round, bounded by
validation_round_budget_swith forward-trial fallback for candidates replay cannot reach; blocking file I/O runs on the thread pool. - The trajectory gate’s fold is history-dependent, so a whole turn’s traces are folded in order — in one thread hop and one history write per trace, not one per metric.
- All workspace stores are lock-guarded with atomic writes (unique temp file + rename) and append-only logs — one workspace safely serves many concurrent sessions, and readers never observe a half-written file.
- Per-session bookkeeping is bounded (a few thousand sessions) with oldest-first eviction, so long-lived processes don’t grow without limit. Evicting a session also drops its trace seen-set.
refresh(session_id), refresh_all(), and drain_validation(timeout=...) are bounded joins (drain_timeout_s by default; drain_validation returns whether it finished, and settle_validation(timeout=...) is the phase-boundary call) for tests and explicit callers — correctness never depends on calling them. settle() is the one wait that is part of the design; hook.pending_sessions is the read-only view for host-side phase barriers.
Scaling out
Score history is local by default, which means a session handled by two replicas has two partial series. The history source is a small Protocol, so a shared remote store can replace the local JSON file without touching anything else — that is the supported fix for replica fan-out.The gate’s state (running peak, stall counter) lives in the same
state/score_history.json entry as the score series itself — no second file, no second lock — so it inherits whatever durability that store has.HARNESS_ROOT or mount a shared volume: all stores are multi-session safe within a process, and cross-process safety rests on atomic renames and append-only files.
Operating recommendations
- Settle per turn, not per task. This is the one integration mistake that silently disables detection — the gate needs a series. See the barrier.
- Start in shadow mode (
observe_only=true) and calibrate your Tier-2 thresholds andgate_targetagainst real traffic. - Tune
gate_windowto your task horizon — long-horizon agents need a wider window than short interactive turns. - Wire an outcome verifier if you have any ground truth at all. It is the strongest promotion signal available, and it costs nothing per turn.
- Turn on capture (
capture_eval_cases=true) so the closed loop has scenarios to replay, and curate a few protectedwincases. - Wire a replay function — it upgrades rule validation from statistical to counterfactual and unlocks regression runs.
- Schedule
pandaprobe-harness-eval(nightly, or after prompt changes) and alert on a non-zero exit. - Call
settle_validation()at phase boundaries — before archiving a workspace, snapshotting a ruleset, or reporting results. Waiting on evaluations alone can freeze a candidate that had already earned promotion as permanently provisional. - Watch the journal —
rule_promote/rule_retiretell you what is being learned;validation_verdict’spending_reasontells you why something is not decided yet;needs_humannotices tell you when to step in.

