The producing side, step by step
- Turn end. Your loop (or a framework adapter) calls
on_turn_endwith the session id and turn payload. This returns immediately — nothing on this path blocks your agent. - Admission gates. Cheap producer-side controls run first: a hard per-process eval budget, per-session turn sampling, and a per-session rate limit. A newer turn supersedes the session’s in-flight evaluation.
- Detached evaluation. A background task checks platform health (once, memoized), then runs one batched session evaluation under a global concurrency cap and polls it to completion. See Evaluation & detection.
- Trend detection. Every resolved score feeds a local dual-EWMA detector, so a slow decline is caught even when no absolute threshold is crossed.
- Validation cadence. Every handled report — healthy or not — feeds the candidate-rule validation engine: forward trials get their denominator, and a single-flight task evaluates open candidates. See Rule validation.
- Notice gate. Identical conditions are de-duplicated per session, recoveries are journaled, and a circuit breaker escalates notice storms to a single
needs_human. - Persistence. A surviving condition becomes a
DiagnosticNoticeinmailbox/pending/, a verbose dump undertraces/, a journal event — and, when capture is enabled, a replayable eval case.
The consuming side
harness.system_context() returns one block to prepend to your system prompt:
- The rendered rules — validated rules first, then candidates under a clearly-labeled “Provisional rules (under evaluation)” section. With retrieval on, only the rules relevant to the current situation render.
- The standing protocol — instructions to check the mailbox each turn and, for each pending notice: read → inspect traces → consult memory → record a rule → acknowledge.
- The banner — a count and max severity when notices are pending; nothing otherwise.
pandaprobe-harness-agent companion CLI.
The workspace on disk
Everything the harness knows lives under one directory — inspectable, greppable, and durable across process restarts:Journal event types
The journal is the harness’s cross-run memory. Every notable event is one JSON line:| Type | Written when |
|---|---|
notice | A diagnostic notice is posted (or would be, in observe_only mode). |
ack | The agent acknowledges a notice, optionally linking the resolving rule. |
rule_add | A rule is recorded (as a candidate by default). |
rule_promote | A validator promoted a candidate to active — with the reason and which validator. |
rule_retire | A rule was retired (by a validator, the agent, or a regression run) — with the reason. |
validation | Announced once when no replay function is wired and validation falls back to forward trials. |
evalset_capture | A breaching session was captured as an eval case. |
regression | A regression run completed, with per-case results. |
recovery | A previously-alerting session scored clean again. |
health | The startup health check ran (ok or degraded, with the reason). |
reflect | The agent ran harness_reflect. |
skip | An evaluation was skipped (e.g. the eval budget is exhausted). |
One healing cycle, end to end
A concrete run (this is exactly whatexamples/closed_loop_self_heal.py prints):
- Turn 1 repeats an identical payment call;
agent_reliabilityscores0.30→ notice posted, the session captured as a replayable failure case. - Turn 2: the agent pulls the notice, inspects the flagged trace, records “Never call the payment tool twice without verifying the transaction status” → a candidate rule.
- The harness replays the captured failure with the candidate in force; the replayed session scores
0.92→ the candidate is promoted (rule_promote, validatorreplay). - The validated rule re-enters the system context; subsequent turns score healthy (recovery).
- A regression run replays the corpus against the current rules: the old failure is
improved, the protected winunchanged— CLEAN.

