pandaprobe-harness) — the self-healing envelope for PandaProbe-instrumented agents.
Current version: v0.9.0
The repair agent moves into the package
Through v0.8 the task agent administered its own workspace: it read its mailbox, inspected diagnostic traces, wrote and retired rules, and acknowledged notices. A measured AppWorld run showed the cost of that design directly — handed a 10-tool administrative surface, the agent spent its turns operating the harness instead of doing the task, and 9 of 13 rules it wrote were about gaming its own diagnostic protocol.v0.9 splits the two roles. The developer owns the task agent, its model, framework, prompts, domain tools, loop, and environment. PandaProbe owns a separate, package-owned repair agent that reads the notice, inspects the flagged trace, and writes at most one candidate rule per failure — on its own model call, in its own session, with its own trace. The task agent keeps four read-only tools and never administers anything.The repair agent needs a model.
Harness.create() raises unless you set repair_model (or HARNESS_REPAIR_MODEL), or run observe_only=True. No billable default is ever selected for you. See Upgrading to v0.9.Added
ManagedRepairAgent— a package-owned, bounded, provider-neutral tool loop over PandaProbe’s official LiteLLM wrapper. It owns the repair prompt, the capability set, and the episode lifecycle; hosts supply only model transport. Configured byrepair_model,repair_timeout_s,repair_max_turns(default 6),repair_max_tokens,repair_temperature,repair_reasoning_effort(default"none", which current OpenAI reasoning models require for function tools on the wrapped chat-completions path),trace_repair_agent, anddomain_policy.TaskToolset— the only tool surface intended for a task agent:harness_rules_read,harness_rules_search,harness_rules_list, andharness_rule_status. All read-only. Dispatch rejects every administrative or mutating call, including hallucinated ones.- Structured repair outcomes.
SettleResult.repairreturns aRepairResultcarrying status, task/repair session IDs, episode and notice IDs, recommended and selected scope, considered/existing/candidate rule IDs, suppression reason, model turns and tool calls, normalized usage, and an error category. Repair failure or timeout never fails the developer task. - Repair episodes. Related same-turn notices whose trace or signature evidence overlaps are coalesced into one episode, resolved atomically. An episode may create at most one candidate. Timeout, cancellation, or failure acknowledges nothing.
- Model-driven rule scoping. The repair agent decides where a rule is filed, from the failure evidence it already holds — part of the existing repair call, not an extra model round.
globalis the default for broadly reusable rules; a concise contextual name (an application, workflow, or domain seen in the evidence) is preferred when the rule belongs to that context;scopedis the fallback when no meaningful stable name can be determined. Custom names are an open catalog, normalized only for filename safety. RuleScopeHintandtask_summaryonTurnContext— bounded, optional host metadata that informs the scope decision without dictating it. A host’s own label for itself (a benchmark or integration name) is rejected as a scope, because it says where the agent ran rather than what failed.Harness.settle_validation(timeout=...),Harness.validation_pending, and atimeout=argument ondrain_validation(), which now returns whether it actually drained. Callsettle_validation()at a phase boundary before snapshotting or reporting a ruleset.validation_round_budget_sandreplay_env_wait_timeout_s, plusReplayContext.mark_execution_started(). The first bounds replay work per validation round; the second keeps time spent queueing for a shared environment out of the replay execution budget.- Validation telemetry. New journal events
validation_round_started,validation_candidate_started,validation_replay_case,validation_verdict, andvalidation_round_finished, with a closed-setpending_reason(trial_in_progress,no_matching_replayable_case,replay_inconclusive,candidate_not_exercised,env_wait_timeout,round_budget_exhausted).rule_retirenow carries structured evidence — the per-case deltas that decided it.
Changed
- BREAKING (workspace layout): the generated task-facing guide is
<harness_root>/rules.md, beside therules.jsonlstore andrules/scope files it indexes. It is regenerated fromrules.jsonlon every rule mutation and at startup, so it is a derived view with no authority — delete it and it comes back. - BREAKING (default scope): a new rule defaults to
global, notscoped, and a task-facing read with noscopeargument defaults toglobal.scopedis now a deliberate verdict — “specific, but no better name” — rather than a catch-all for silence. Existingglobal,scoped, and custom scope files keep working; no rule is migrated, moved, or duplicated. - BREAKING:
DiagnosticNotice.recommended_scopeandRepairAssignment.recommended_scopearestr | None.Nonemeans “no host recommendation”, which is deliberately distinct from recommending the default. - Every candidate now reaches a verdict. Replay is still the strong path, but a bounded round falls back to the cheap forward trial for candidates it cannot reach, and rounds rotate which candidate replays first. Previously a promotable candidate could accumulate observations indefinitely and never be asked.
- Replay verdicts are attributable. A replay sees only the candidate under test — active rules stay visible as the baseline — and a replay that never read the candidate cannot produce a conclusive verdict. On a failure case only the candidate’s own target metric or the authoritative outcome score can retire it; a protected
wincase still retires on any metric. In one measured run, 44% of retirements had fired on a metric the rule never claimed. harness_rule_addrejects a path-shaped scope instead of slugifying it, and validatesmetricagainst the evaluator’s metric registry. A rule whose metric matches no signature reads as “never breached”, which can invert its own verdict — so the repair model is told to correct it rather than having it silently persisted.- Task-facing context and the
rules.mdtemplate say learned rules, not “optional learned guidance”, and state plainly that PandaProbe does not automatically insert rule contents. No behavior change: nothing was ever injected. settle()waits for evaluation, notice persistence, and one bounded repair attempt. It deliberately does not wait for a validation round, because a replay can need the very resource the current turn holds.
Removed
- BREAKING: the task-agent self-administration architecture. Task agents no longer receive mailbox, trace-inspection, notice-acknowledgement, rule-write, rule-retirement, validation, or regression capabilities.
- BREAKING:
HarnessToolset,Harness.toolset,OP_SCHEMAS,build_toolset_from_env, and thepandaprobe-harness-agentcompanion CLI. UseHarness.task_tools; the task schema isTASK_OP_SCHEMAS, and repair schemas and dispatch stay package-internal. - BREAKING:
Harness.shell. Task agents no longer receive a harness-owned administrative shell. - BREAKING: the no-argument
Harness.system_context()/startup_context()and the two-argumentcompose_system_preamble(rules, mailbox). All now require a task session ID. - BREAKING:
HarnessConfig.concurrent_evalandHARNESS_CONCURRENT_EVAL— one eval run has covered every metric since v0.8, and nothing read the flag. - BREAKING: the
"legacy"Resolution.kindand the pre-v0.8severity: "relative"alias. Every resolution now names what happened; an unrecognized kind reads asno_proposal, and an unknown severity reads asbreachrather than silently de-escalating.
pandaprobe-harness-eval for regression replay and pandaprobe-harness-calibrate for threshold calibration.One trigger, one detection pipeline
v0.8 removes the legacy session-composite trigger. The trace-level three-tier trigger is now the only path.pandaprobe-harness==0.7.0 is the last release that can reproduce the session-mode ablation.Removed
- Public exports:
TrendDetector,TrendVerdict,SIGNAL_NAMES, andEwmaState. - Public members:
Metric.RELIABILITY,Metric.CONSISTENCY, andMetricEvaluator.evaluate_turn(). - Configuration fields:
trigger_mode,session_metrics,signal_weights,reliability_threshold,consistency_threshold,eval_reliability,eval_consistency,enable_trend,ewma_fast_span,ewma_slow_span,trend_margin_cross,trend_min_samples,adaptive_threshold,adaptive_margin_drop,percentile_window,percentile_floor, andhydrate_history_from_backend. - Environment variables:
HARNESS_TRIGGER_MODE,HARNESS_ENABLE_TREND,HARNESS_ADAPTIVE_THRESHOLD,HARNESS_RELIABILITY_THRESHOLD,HARNESS_EWMA_FAST_SPAN,HARNESS_ADAPTIVE_MARGIN_DROP,HARNESS_CONSISTENCY_THRESHOLD,HARNESS_EWMA_SLOW_SPAN,HARNESS_PERCENTILE_WINDOW,HARNESS_EVAL_RELIABILITY,HARNESS_TREND_MARGIN_CROSS,HARNESS_PERCENTILE_FLOOR,HARNESS_EVAL_CONSISTENCY,HARNESS_TREND_MIN_SAMPLES, andHARNESS_HYDRATE_HISTORY_FROM_BACKEND.
Changed
- Score history stores only trace series and trajectory-gate state. v0.7 files carrying EWMA state still load, and obsolete state is dropped on the next write.
- The
HistorySourceextension point remains and now exposes the gate’s atomicrecord_gated()operation. Use a shared implementation for replica fan-out; backend session-score hydration is gone. - Legacy notices with
severity: "relative"load as advisorytrendnotices, preventing a silent escalation tobreach. - Calibration reads local trace history and eval-set baselines. Platform trace score-list records are not used because they do not carry the session id required to join session labels.
The “signal that discriminates” release
v0.6 closed the loop but drove it with the wrong measurement, and a measured benchmark run regressed. Three findings: the session composites (agent_reliability / agent_consistency) are worst-case rollups that floor near ~0.2 for essentially every session, so the trigger fired on almost everything; promotion was scored on that same non-discriminating metric, so it was close to random; and the agent, handed 14 tools and a check-your-mailbox-every-turn mandate, spent its turns operating the harness instead of doing the task — 9 of the 13 rules it wrote were about gaming its own diagnostic protocol. Worse, the trend machinery was inert: history is keyed per session and hosts hooked the harness once per task-trial, so every series had exactly one sample and trend_min_samples was never reachable.Most integrations need two edits: drop
task_hint from system_context(), and add settle=True to your turn scope. See Upgrading to v0.7.Added
- Trace-level three-tier trigger (
trigger_mode="trace", the new default). Tier 1 (task_completion,coherence) scores every trace; Tier 2 (tool_correctness,argument_correctness) runs on the last trace only and only once Tier 1 breaches; Tier 3 (planning/efficiency, opt-in viaenable_tier3) only enriches a confirmed Tier-2 breach. New modulesevaluation/traces.py(TraceLocator— trace discovery, which did not exist before) andhook/tiers.py(TierRunner). - Trajectory gate (
evaluation/trajectory.py). A Tier-1 metric breaches on the shape of its series — a stall (no gain towardgate_targetacrossgate_windowtraces) or a regression (agate_dropfall from the running peak) — and any real gain resets the window, so a healthy climbing session never breaches. A Tier-1 score’s absolute floor is deliberately not a breach: an agent three steps into a task has legitimately not finished it. - Per-turn await barrier:
Harness.settle(session_id)/harness.turn(session_id, settle=True), on its own generousbarrier_timeout_s. This is what makes healing take effect within a session, and it is the precondition for the gate having a series at all. - Optional outcome verifier:
Harness.create(..., verifier=...). A developer-supplied(session_id, end_state) -> float | bool | Noneoracle emits a syntheticoutcome_correctscore that drives breaches and, when present, decides promotion. Prefer a continuous score: a pass/fail flag that is almost always0discriminates no better than the metrics this release replaces. PandaHarnessHook.pending_sessions, so host-side phase barriers no longer need the private task map.
Changed
- BREAKING (workspace layout):
harness_rules.mdis now a skill root — protocol, tool list, and a generated References index, with no rule text. Rules live in a newrules/subtree (global.md,scoped.md, and any<topic>.mdthe agent creates), andRulegains a free-formscope. Existingrules.jsonlrecords migrate on read: untagged →global, tagged →scoped, preserving v0.6’s meaning. - BREAKING (behavior): the system context no longer injects rule bodies. The agent pulls them with the new
harness_rules_read. Retrieval is now keyed onscoperather than on whether a rule happens to carry tags — which previously made any rule added without a notice a permanent global by accident. - BREAKING (toolset): 14 tools → 10. Removed
harness_history(the gate and the notice now carry the trajectory),harness_journal,harness_reflect,harness_evalset_list,harness_evalset_attach.HarnessToolsetno longer takeshistory=orevalset=— nothing left reads them. - BREAKING (signature):
Harness.system_context(),PandaHarnessHook.startup_context()andcompose_system_preamble()no longer take atask_hint. It existed to pre-select which rules to inline, and no rules are inlined now. - Tier-1 scoring issues one platform run per turn covering every new trace, and the trajectory gate folds a whole trace’s metrics in one history write instead of two per metric.
- Trace listings no longer burn the retry budget on a warm session: an empty listing is only retried while the session has never yet produced a trace.
- A wired verifier that has no verdict for a task no longer vetoes promotion. The target metric is now chosen per case from the deltas that actually arrived, in trust order (
outcome_correct→ the rule’s metric → the triggering signature). - Notices carry the triggering metric’s
trace_id,tier, and a coarsescope_hint(globalfor a trajectory fire,scopedfor a step-level breach) that defaultsharness_rule_add’s scope. - Rule validation and regression runs re-score a replay on the trace metrics (Tier 1 + Tier 2) against its last trace, not the session composites.
MetricEvaluatorgainedevaluate_trace/score_last_trace;--signal-weightsis now sent only on the session path, where the platform actually accepts it.- Severity mapping is deliberate: a Tier-1-only fire is advisory (
trend, no eval case captured), while a confirmed Tier-2 breach is abreach— so only surgical, diagnosed failures become promotable eval cases.
The “closed loop” release
v0.5 detected failures, proposed rules, and applied them — but never confirmed a rule actually helped. v0.6 closes the loop on three principles: evidence before trust, relevance over volume, and measure the foundation — all automatic, no human in the healing loop.Changed
- BREAKING (behavior):
harness_rule_addnow records a candidate rule, not an active one. Candidates still render into the system context (under a clearly-labeled “Provisional rules (under evaluation)” section — a rule must be in force to be measurable) and are promoted toactiveonly after a validator shows they help:ReplayValidator(replays the captured failing scenario through a developer-supplied replay function; promotes iff the targeted metric improves pastrule_promote_marginwith no case regressing pastrule_regress_margin) or, when no replay function is wired,ForwardTrialValidator(compares the breach rate over the nextrule_trial_min_sessionslive sessions against the baseline captured at add time). Unfavorable candidates are retired with a journaled reason. SetHARNESS_RULE_VALIDATION=falseto restore the v0.5 add→active behavior. - Rule retrieval is task-conditioned by default: the system preamble renders global (untagged) rules plus the top-
rules_context_topkrules lexically relevant to the pending notices and an optionalsystem_context(task_hint=...)— not the full set. Everything else stays reachable viaharness_rules_search/harness_rules_list. SetHARNESS_RULE_RETRIEVAL=falseto restore render-everything. harness_rule_retirenow retires candidates as well as active rules and records a reason; the rule dedup/cap count the whole live set (active + candidate).harness_reflectadditionally returnscandidate_rulesandrecent_validationsso the reflection cycle learns which kinds of rules survive validation.
Added
- Rule lifecycle (
candidate → active | retired) with auto-derived retrievaltagsand full trial bookkeeping (TrialState: baseline vs. trial breach rates, observed/breached sessions, replay attempts, verdict). - Validation engine driven automatically from the hook on every handled report — single-flight, time-bounded (
replay_timeout_s), and incapable of blocking or crashing the host loop. New journal events:rule_promote,rule_retire(with reason),validation,evalset_capture,regression. - Replayable regression eval-set: breach notices capture the session as a replayable
failurecase (opt-in viacapture_eval_cases); known-good sessions can be captured as protectedwincases (never auto-evicted). TheReplayFnseam —async (case, system_context) -> new_session_id— is how the harness re-runs your agent; wire it viaHarness.create(..., replay=...). harness.run_regression()+ thepandaprobe-harness-evalCLI: replay the eval set (wins first) against the current rule set and classify each caseimproved/unchanged/regressedvs. baseline. Without a replay function it degrades to one clear warning and all-skipped results.- Metric calibration (
pandaprobe-harness-calibrate+ acalibrate()library API): precision/recall/F1 of the breach predicate, a confusion matrix, and a threshold sweep with labels (JSON/CSV or eval-set proxies); score distribution, histogram, sweep, and inter-metric agreement without. - Five new toolset operations (9 → 14):
harness_rule_status,harness_rules_search,harness_rules_list,harness_evalset_list,harness_evalset_attach. - New facade surface:
Harness.create(..., replay=)on every factory,harness.evalset,run_regression(),validate_candidates(),drain_validation(),system_context(task_hint=...). - Ten new config knobs, all mirrored as
HARNESS_*env vars:rule_validation,rule_trial_min_sessions,rule_promote_margin,rule_regress_margin,replay_timeout_s,capture_eval_cases,eval_case_max,regression_sample,rule_retrieval,rules_context_topk.
Fixed
Rule.from_jsonno longer coerces unknown statuses toactive— a persisted candidate round-trips instead of silently self-promoting across restarts.
The “pull model” release
The harness no longer pushes alerts into agent transcripts; it posts structuredDiagnosticNotices to a filesystem mailbox that the agent pulls from via tools, and it maintains a durable journal and a structured self-heal rules store.Added
- Workspace substrate:
MailboxwithDiagnosticNoticerecords (mailbox/pending/→mailbox/processed/), an append-onlyJournal(journal.jsonl), and aRulesStore(rules.jsonl) with provenance, dedup, a rule cap, and per-rule effectiveness tracking. HarnessToolsetexposing 9 agent-facing operations, the sandbox-allow-listedpandaprobe-harness-agentcompanion CLI, and native tool registrations for the supported frameworks.Harnessfacade with zero-adapterturn()/run_turn()entry points.- Cost/latency controls: per-session eval sampling, per-session rate limiting, a global concurrency cap, and a hard per-process eval budget.
observe_onlyshadow mode; a circuit breaker escalating notice storms to a singleneeds_human; a startup health check with graceful degraded mode; backend history hydration for horizontally-scaled agents; sandbox hardening (env scoping, argv deny rules); and a sanitization trust boundary for all eval-derived text entering agent context.
Security
- Mailbox rejects notice ids that are not a single safe path component, closing a workspace path-traversal vector.
- The restricted shell catches mid-path traversal, and argv deny rules match subcommands as ordered subsequences (leading global flags no longer bypass them).
Removed
- BREAKING — the push-model alert-injection surface is gone (
inject_alert,consume_*,startup_messages,drain_pending,append_rule, and friends). Delivery is mailbox + toolset, always pull.
- Async, supersede-cancelling evaluation loop with EWMA trend detection, adaptive (relative) thresholds, and per-signature alert cooldowns.
- Single batched eval run per turn covering all active session metrics, with eventual-consistency retries and bounded run polling.
- Framework adapter suite: LangGraph, LangChain, DeepAgents, CrewAI, Claude Agent SDK, and OpenAI Agents.
- Initial public harness: the
pandaprobeCLI subprocess seam, a turn-end evaluation hook with absolute score thresholds, trace dumps undertraces/,harness_rules.md, and the Dockerised diagnostic sandbox with a restricted shell.

