> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pandaprobe.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Upgrading to v0.7

> Coming from v0.6: the four breaking changes and how to adapt

v0.7 is the *"signal that discriminates"* release. v0.6 closed the loop but drove it with the wrong measurement, and a measured benchmark run **regressed**. This page is what you need to change.

<Note>
  Most integrations need **two edits**: drop `task_hint` from your `system_context()` call, and add `settle=True` to your turn scope. Everything else migrates itself.
</Note>

## Why it changed

Three findings from a measured run, each of which is a breaking change below:

1. **The trigger fired on everything.** `agent_reliability` / `agent_consistency` are worst-case rollups that floor near `~0.2` for essentially every real session, so "score below 0.5" was true almost always.
2. **Promotion was close to random.** Candidate rules were scored on that same non-discriminating metric, so the promote/retire decision carried little information.
3. **The agent operated the harness instead of doing the task.** Handed 14 tools, its whole rule corpus in every prompt, and a check-your-mailbox-every-turn mandate, **9 of the 13 rules it wrote were about gaming its own diagnostic protocol.**

And one bug underneath all of it: the trend machinery was **inert**. Score 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.

## 1. The trigger is trace-level

**What changed.** `trigger_mode` defaults to `"trace"`. Instead of two session composites per turn, the harness scores trace-level metrics in [three tiers](/harness/loop/evaluation) and Tier 1 breaches on the *trajectory* — a stall or a regression — never on an absolute floor.

**What you do.** Nothing, to get the new behavior. But two things are worth knowing:

* Your **thresholds carry different meaning now.** `HARNESS_RELIABILITY_THRESHOLD` and `HARNESS_CONSISTENCY_THRESHOLD` only apply in session mode. What matters in trace mode is `gate_window` (the primary knob), `gate_target`, `gate_drop`, and the Tier-2 thresholds. Re-[calibrate](/harness/closed-loop/calibration).
* **Notices look different.** Each metric now carries `trace_id` and `tier`, the notice carries a `scope_hint`, and the condition vocabulary gained `stall` and `regression`. If you parse notices or signatures downstream, update those consumers.

**To keep the old behavior** — for an A/B baseline, or to defer the migration:

```bash theme={null}
export HARNESS_TRIGGER_MODE=session
```

Session mode is fully supported and unchanged. Note that the [outcome verifier](/harness/closed-loop/outcome-verifier) is trace-mode only.

## 2. Turn boundaries need a barrier

**What changed.** `Harness.settle(session_id)` and `harness.turn(session_id, settle=True)` are new. This is the fix for the inert-trend bug: without a per-turn barrier, an agent runs several turns ahead of its own diagnosis and a session can end before its first notice arrives.

**What you do.**

```python theme={null}
# v0.6
async with harness.turn(session_id):
    await my_agent_step(...)

# v0.7
async with harness.turn(session_id, settle=True):
    await my_agent_step(...)
```

<Warning>
  **Audit your turn granularity while you are here.** If your loop calls `on_turn_end` once per *task* rather than once per *turn*, the trajectory gate gets one sample per series and can never fire — the same failure that made v0.6's trend detection inert. Framework adapters wire the framework's *natural* boundary, which for a coarse one-call-per-workflow API is not the boundary you want. See [Framework adapters](/harness/integrations/frameworks#turn-granularity-matters).
</Warning>

The barrier is on its own `barrier_timeout_s` (default `180s`), separate from `drain_timeout_s`. On expiry the work continues detached and `SettleResult.timed_out` is set.

## 3. The rules workspace is agent-owned

**What changed.** `harness_rules.md` is now a **skill root** — protocol, tool table, and a generated References index, with **no rule text**. Rules live in `rules/global.md`, `rules/scoped.md`, and any `<topic>.md` the agent creates. The system context injects no rule bodies; the agent pulls them with the new `harness_rules_read`.

**What you do.**

```python theme={null}
# v0.6 — the hint pre-selected which rule bodies to inline
system_prompt = harness.system_context(task_hint=user_input) + MY_PROMPT

# v0.7 — no rules are inlined, so there is nothing to pre-select
system_prompt = harness.system_context() + MY_PROMPT
```

`PandaHarnessHook.startup_context()` and `compose_system_preamble()` lost the same parameter.

**Your existing rules migrate themselves.** `rules.jsonl` records are migrated **on read**, preserving v0.6's meaning:

| v0.6 record                              | v0.7 scope |
| ---------------------------------------- | ---------- |
| No tags (was implicitly always-injected) | `global`   |
| Has tags                                 | `scoped`   |

No migration command and no rewrite pass; the first render regenerates the `rules/` subtree. If you have a customized `harness_rules.md`, note that provisioning never overwrites an existing root — but the References section below the generated marker *is* regenerated.

<Note>
  Retrieval is now keyed on **`scope`**, not on whether a rule happens to carry tags. In v0.6 any rule added without a source notice had no tags and therefore became a permanent global by accident. `tags` still exist, for ranking.
</Note>

## 4. Four tools were removed

**What changed.** 14 tools → 10.

| Removed                  | Replacement                                                                                  |
| ------------------------ | -------------------------------------------------------------------------------------------- |
| `harness_history`        | The notice and the gate now carry the trajectory.                                            |
| `harness_journal`        | `harness.journal.recent(...)` — an operator surface now.                                     |
| `harness_reflect`        | Express rule grouping directly: move rules into a `<topic>` scope.                           |
| `harness_evalset_list`   | `harness.evalset.cases(...)` or `pandaprobe-harness-eval --list`.                            |
| `harness_evalset_attach` | `harness.evalset.attach_input(case_id, payload)`, or supply `end_state` in the turn payload. |

Added: **`harness_rules_read`**. And `harness_rule_add` gained a **`scope`** argument, defaulting to the source notice's `scope_hint`.

**What you do.** If you enumerate tool names, or assert a tool count, update it. `HarnessToolset` no longer accepts `history=` or `evalset=` — nothing left in it reads them.

## New in v0.7, opt-in

* **[Outcome verifier](/harness/closed-loop/outcome-verifier)** — `Harness.create(..., verifier=...)`. A `(session_id, end_state) -> float | bool | None` oracle becomes the `outcome_correct` metric and, when present, decides promotion instead of a judged proxy. Prefer a **continuous** score: a pass/fail flag that is almost always `0` discriminates no better than the metrics this release replaces.
* **Tier 3** — `enable_tier3=true` adds `plan_adherence`, `plan_quality`, `step_efficiency` to explain a confirmed Tier-2 breach. Never a breach source of its own.
* **`hook.pending_sessions`** — a read-only view for host-side phase barriers, so callers no longer need the private task map.

## Behavior fixes worth knowing

These need no action, but they change what you'll observe:

* **Validation and regression re-score on trace metrics** (Tier 1 + Tier 2, against the replay's last trace), not the session composites. Deltas in a regression report will name different metrics than they did in v0.6.
* **Promotion target is chosen per case** from the deltas that actually arrived, in trust order: `outcome_correct` → the rule's `metric` → the triggering signature. A wired verifier with no verdict for a particular task no longer vetoes promotion.
* **Severity mapping is deliberate.** A Tier-1-only fire is advisory `trend` and captures **no** eval case; only a Tier-2-confirmed breach is a `breach` and becomes a promotable case. Expect a smaller, cleaner eval set.
* **One platform run per turn** for Tier 1, over every new trace, and one history write per trace 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.
* **`--signal-weights` is session-only.** It is a validation error on the trace path, so the harness omits it there.

## A quick checklist

<Steps>
  <Step title="Drop task_hint">
    `harness.system_context()` — no arguments.
  </Step>

  <Step title="Add the barrier">
    `harness.turn(session_id, settle=True)`, or an explicit `await harness.settle(session_id)`.
  </Step>

  <Step title="Confirm turn granularity">
    One `on_turn_end` per agent **turn**, not per task. Verify it: after a multi-turn session, `state/score_history.json` should hold more than one sample per (session, metric).
  </Step>

  <Step title="Update tool-name lists and notice parsers">
    10 tools; `harness_rules_read` is new; notices carry `trace_id`, `tier`, `scope_hint`; signatures can say `stall:` / `regression:`.
  </Step>

  <Step title="Re-calibrate">
    Tune `gate_window` to your task horizon, and re-measure the Tier-2 thresholds against your traffic.
  </Step>

  <Step title="Consider a verifier">
    If you have any ground truth, wire it — it is the strongest promotion signal available and costs nothing per turn.
  </Step>
</Steps>
