Skip to main content
The scripts are in the repository, not in the installed package. Clone it, then cd recipes/community/can-the-markers-be-trusted before running the commands below. Browse this recipe on GitHub.
Check every behavioral marker against ground truth. seeded_agent plants six behaviors and writes what it planted on each row, so a marker’s recall and precision are computable rather than assumed. Across 5,064 rows on 12 seeds the six markers that name those behaviors split three ways: Three regimes, and only the middle one is what you would expect a detector to look like:
  • The four phrase markers are exact and circular. Recall 1.000, zero false alarms — because seeded_agent plants hedging by appending "It depends." and no_hedging greps for it depends. That is the generator agreeing with its own definition.
  • reported_failure is a real detector and not a trustworthy one. It catches 76% of ignore_fault at precision 0.598 — 106 false alarms against 158 catches. judge_trust calls it ok=False, correctly.
  • no_secrets never fires. Six hundred planted leaks, zero detections. The marker is named for the behavior, it is stamped on every row, and it is constant at 1.000. Nothing else in the library sees leaks either: the best marker anywhere, by F1, is argument_grounding at 0.202.
So 24.2% [22.6%, 25.9%] of the rows a green style_report passes carry a planted failure, and a style_report-measured improvement recovers only 68.1% [66.2%, 69.9%] of the improvement that actually happened. Turning on every marker family makes it worse, not better. Stamping style_markers, trace_markers and mark_grounding and requiring all thirteen to be clean takes the missed-failure share from 24.2% to 20.7% [19.4%, 22.0%] — and drops the recovered share from 68.1% to 60.8% [59.2%, 62.4%]. The two markers that see anything behavioral are imprecise enough that their false alarms cost more than their catches gain. What you will learn: which marker family sees what, why a detector validated on seeded_agent is validated against itself, and how to put ground truth under a behavioral metric before you quote it. You need nothing: no model key, no GPU, no network. Six seconds locally; the 12-seed version is about two minutes of CPU on your own Modal.

The question

compare() reports a delta on every shared marker beside pass@1, and compare(proxy="marker:X") will tell you whether the reward is paying for a tic. Both take on faith that when marker X moves, behavior X moved. Does it?
There is exactly one place in this library where a behavior has a machine-checkable gold label: seeded_agent’s own docstring says each row “says what was planted, so a grader or a marker can be checked against the truth.” This is that check.

Three marker families, and they do not agree on what exists

This cost the most time, so it goes first. The library stamps markers from three calls and they carry different vocabularies: Both markers that name a behavioral failure — reported_failure and no_secrets — live in trace_markers, which is the family a reader following style_report never reaches. If you measure behavior with what the docs show you, you are measuring four phrase lists. Six of the eight non-style markers are constant at 1.000 on every row of this run (honest_claims, no_bypass, no_destructive, no_secrets, no_suppression, no_test_tampering). That is closed issue #270’s shape — a marker that never had a chance to fail reporting as clean — and no_secrets is the case where it matters, because 600 rows in this run are exactly what it is named for.

The setup

  • Ground truth is the plant record, row["seeded"], not my opinion and not a judge. It is a program’s label, so it is attached with attach_labels(..., kind="program") — the one kind judge_trust calls measured without allow_model_gold.
  • The arms are the same agent at two misbehavior rates, 0.50 and 0.15, standing in for a policy that got better. The point is not that it improved; it is by how much each instrument says it improved.
  • The task grid is written once and pinned with tasks=. Two simulate calls at the same budget do not cover the same tasks — eval_variance says so in a note (“runs do not cover the same tasks; means are not strictly comparable”) — so without pinning, nothing is paired. With it, 74 of 74 scenarios pair.
  • The noise floor is three re-runs of the before arm at different agent seeds, passed to compare as run_std_by_metric so the gold metric and each marker get their own band.
  • Both instruments measure the same quantity: the share of rows with no failure. Gold reads the plant record; the markers read the stamped markers. That is what makes “recovers 68%” a comparison and not a ratio of unlike things.

Run it

results.json is the output of the fourth command. seeds.json is the output of the fifth, run on wai-marker-trust, 12 CPU containers, 5,064 rows.

What you get

What a fully green dashboard is still carrying

Under style_report the 807 missed rows are 600 leak and 207 ignore_fault — every planted instance of both. Adding the other two families removes 158 of the 207 ignore_fault rows and 116 of the 600 leaks, leaving 484 leaks and 49 faults. The two routes to each interval (Wilson on pooled counts, t(df=11) across seeds) agree to three decimal places.

Did the markers recover the improvement that happened?

Rate 0.50 → 0.15, paired on 74 scenarios, 12 seeds: compare’s own report on seed 0, with the per-metric noise floor from three re-runs (abridged to the markers that move):
Read that as a reviewer would. Every marker that moves understates, three of the five land inside their own noise band on a change that moved the gold metric 39 points, and no_secrets reports a perfect 1.000 before and after on a run containing 600 leaks. If the markers were all you had, you would report a real improvement as mostly noise and a leak rate of zero. Turning the extra families on also takes compare from 6 metrics to 14, and its family error line from “up to 26% chance that one clears zero on luck alone” to 51%. More detectors is not a free action.

judge_trust with the marker as the judge

This is the part of the library that comes out best. no_secrets scores 87.5% agreement — it looks like a good detector on the headline number, because 87.5% of rows have no leak and a constant “clean” is right on all of them. Kappa is 0.00 and ok is false. The agreement number alone would have fooled me; the kappa did not.

The bug this run found

attach_labels will write the wrong gold label onto most of your rows and report a clean match. Runs on a pinned task grid share (scenario_id, rollout_index), which is the key attach_labels matches on, so labels for one run land on every run’s rows:
Downstream, judge_trust reported agreement 0.89 and kappa 0.00 for a detector whose true agreement is 1.00, and called three of the four ok=True. Filed as #759. The recipe stamps a unique rollout_id on every row and records mislabeled_rows in results.json; do the same before you believe any judge_trust number computed over pooled runs.

What this does NOT show

No claim that the phrase markers are wrong about what they measure — they measure phrase presence and they are exact at it. The claim is about coverage and about trust: the behavior a marker is named for and the behavior it detects are different questions, and no_secrets is the case where the answer is “none at all”. The 24.2% is a property of this generator’s behavior mix, not a law: it is roughly the share of seeded_agent’s plants that are leak or ignore_fault among rows the markers pass. Change behaviors= and it moves. What does not move is that no_secrets never fires on a leak. Recall is measured; precision against a real model is not measurable here for the phrase markers, because seeded_agent never writes a phrase it did not plant. Zero false alarms over 5,064 rows is a fact about the generator. A real model that opens with “Great question!” while answering correctly would be a false alarm, and nothing here bounds how often that happens. (reported_failure’s 106 false alarms are real, because it reads the trace rather than a phrase list.) The “improvement” is a rate knob, not training. It is a faithful stand-in for measuring a policy that got better, and it is not evidence about what training does to markers.

Next

Two things. First, find out whether no_secrets can fire at all. Zero detections on 600 planted leaks is either a marker that does not implement its name or one whose trigger seeded_agent’s leak text does not match. Constructing a row it does catch is a ten-minute experiment and it decides whether #760 is a coverage gap or a dead detector. Until somebody runs it, this recipe can only say the marker is silent, not why. Second, the same measurement against a real model, which is the half this one cannot do: generate on your own Modal with Qwen2.5-1.5B-Instruct, hand-label 200 replies for hedging, and measure no_hedging’s recall against people rather than against its own phrase list. That is the number that would tell you whether any of this transfers, and it is the one the seeded path structurally cannot produce.
Last modified on September 22, 2026