The Harness Atlas

Decisions consolidated into the Clara-as-a-Coworker tracker, Phase 4 (single list) — answers there count.

Every test harness, eval rig, and replay system in the PropFlowAI codebase, in one place — plus the design for one shared skeleton so building a harness stays the house craft instead of the house sprawl.

Verified 2026-08-26 · direct find/grep/git-log against ~/.claude/propflowai (main) plus 4 named worktrees · cross-checked against the eval-quality trio (eval-inventory-2026-08-20, the eval deep inspection, the Eval & Testing Roadmap) and simplification-plan-2026-08-26 §2 bucket 4 · promptfoo layer: a separate audit is running, see promptfoo-audit.md — not redone here

Read this first — the one live fact that changes everything below it: quality-gate-poc/README.md says its "Regression Gate — Leasing (required)" job is deliberately held back from actually blocking merges, pending a burn-in campaign, and even names the exact command to flip it later. A live check tonight (gh api repos/.../branches/main/protection) shows that gate already IS in the required-checks list — it is, right now, the only harness/eval check in the whole repo that can block a merge. Either the flip happened without the README being updated, or someone flipped it early. Every CI-status pill below is calibrated against this live check, not against what a workflow's name or README claims.

The short version: at least 45 separate places in the codebase test, replay, or stress Clara's behavior — and they run on 5 different "is this actually true" answers for the same question ("did this conversation go right?"). Nine of them independently reinvent "replay a conversation and grade it." One tree of ~1,850 files isn't even in version control. A second confirmation pass tonight found four separate real-PII fixtures with zero scrubbing (not one) and the branch-protection discrepancy above. Nothing here proposes new architecture — it proposes one shared skeleton (a fixture shape, a runner, a grader list, two scrubbers, one coverage ledger, one proof-of-work stamp) that every existing harness can plug into without a rewrite, plus a doctrine and a build check so the next harness lands inside the skeleton instead of beside it.

45+harness/eval/replay/stress directories found tonight
9independent "replay a conversation and grade it" engines
2things independently named "Crucible" — different domains, same name
1,850files in model-evals-fleet/ with zero git history
3attestation mechanisms already invented tonight, in one branch, ready to become the standard
4real-PII fixtures with zero scrubbing found live tonight (after-call-harness, evals/gauntlet, data/eval, plus a scoped gap in Cerberus's own scanner)
5checks, repo-wide, that can actually block a merge — verified live via gh api branch protection

In plain terms: Fede loves building harnesses — that's a strength, not the problem. The problem is every harness gets built from scratch: its own way of describing a test case, its own way of running it, its own way of scrubbing private info out of it, its own way of proving the result is real and not stale. This page lists everything that exists today, shows the 9 "replay and grade" systems side by side so it's obvious which parts of which one are worth keeping, and proposes the one shared skeleton — so next time someone builds a harness, they build the SCENARIOS, not the plumbing underneath them.

October 27 plan: one harness

Source: three read-only inventories done this morning (main app repo, Clara Bench and robot repos, this Atlas checked against main). The intro numbers above and everything below this section are the August 26 count, kept as history.

Answer · What we have · Consolidate · Extend · Build order · Questions for Fede

1. The answer in one sentence

The one harness is the Day 0 replay, grown from "hold or send" to the whole turn (Clara's words, tool calls, work orders, AppFolio writes) across text, email and voice with a Spanish slice, run on every core PR, with the Clara Bench judges and the Willows live run stacked on top.

Two kinds of change, two proofs. A code refactor must replay byte-identical (recorded model answers, no model call). A prompt or model change is expected to change words, so it is proven on the bench judges and runs in shadow first (Fede's Oct 25 merge-gate ruling).

223 → ~140suites (177 in the app repo + 46 in bench, robot, voice and local rigs); 53 folded, 30 dead deleted
35 → 1runners that replay or grade a Clara turn
0 → 1required checks that run Clara behavior (today the only harness-shaped one fast-passes: "0 fixtures run")
hold-or-send → whole turnwhat Day 0 checks on 1,317 real records (5,515 cells); after: reply, tool calls, records, voice
FOLDS IN 19 one-gate replays 4 conversation-replay engines 5 tour-time replays 7 identity suites 3 hold-decision copies Willows live run (rung 5) real text, email and call · scenario log · record ids as proof Clara Bench judges (prompt and model changes) noise first, then helpful · Spanish judged in Spanish · shadow Day 0 replay: the spine (rung 3) every live customer's real records, every turn words · tool calls · work orders · AppFolio writes text · email · voice · Spanish slice · ledger for intended changes Unit tests + 853 mutation controls: build hygiene, never proof Blocks the merge core PRs: identical, or a ledger row Figure 1. One spine. Everything that replays a Clara turn today becomes a fixture or a lane of it.

2. What we have

FamilyCountAlive?Blocks a merge?Note
Replay engines (Day 0, model-turn, live loop, old engines)6Day 0 alive; 2 dormantnoDay 0: 1,317 real records, 5,515 cells, manual, one Mac
One-gate replays19one-offnoeach re-asks one gate on real data
Promptfoo configs10483 wired; 21 wired nowhereno42 only run in the weekly stress run
Drift tests710aliveyes (Unit Tests)541 read source text, not behavior
Mutation controls853aliveyes (Lint)the one place "this guard goes red" is enforced
Clara Bench (repo + old A–G runners)2 lineagesalive, manualonly the set lockfrozen set v1; skill still points at the old runners
Shadow loops2alivenoboth run from unmerged local worktrees
Voice suites~10 tiersmixed; 3 deadno330 scripted scenarios run soft; 8 live agents grade nothing
Robot tests (AppFolio)11aliveyes (robot CI)Camellia only; no Western Slope or Situs baselines
Willows drivers9 + ~10alive; ~10 uncommittedno9 scripts in git; today's drivers live in a scratchpad
Stress runs (text, email, voice)6rednoweekly run last green Jul 13
One-off proofs (subscription evals, small scripts)114 + 5248 made since Sep 27no3 of 114 in any workflow

3. Consolidate

Each group folds into one place, and the old copies are deleted only after the proof in the last column passes.

Built more than onceFolds intoDeletedProof it is safe
4 conversation-replay enginesDay 0 full turn3Day 0 reproduces each engine's recorded verdicts on its own records
19 one-gate replaysDay 0 caller cells19same verdict on the same records at the matching caller
3 more copies of the hold decision (incl. the fast-passing required gate)Day 03Day 0 check is green and required before the old gate leaves
5 tour-time replaysDay 0 tour lane (tour-day fixtures)4same offered times per record
7 Willows escalation scriptswillows-live scenarios6each scenario re-run once: same PASS/FAIL, record ids logged
7 identity suitesidentity-harness + Day 0 fixtures6union of their cases, same result
2 bench runner lineagesclara-benchmark baseline runner1same scores on frozen set v1 (hash-locked)
2 shadow loopsOpus shadow arm, merged1same texts; production replies match turn for turn
4 ways to test a callrobot call (live) + scripted simulate2one recorded call, both judges agree
2 human-rating pathsSlack buttons label record152 chat grades imported, counts match
2 guest-card email replaysguest-card corpus1same grade per shared card
7 scrubbersone shared scrubber6identical output over every fixture today
Total53plus 30 dead suites (no run in 30 days, no owner)

4. Extend: the gaps

Gap todayRung it servesFix
Day 0 sees only hold or send3 real-book replaycells carry reply text, tool calls, filed records
Not in CI; snapshot only on one Mac3private off-Mac copy, compare on every core PR
No fixtures for data changes (new module state, a company going live)3fixture overlay on the frozen snapshot
No voice in the replay3real call transcripts and after-call records as cells
No simulated prospect or tenant attack (staff only)2 adversarialsimulated users on the subscription, two skeptics
No required check runs a behavior testmerge gateDay 0 compare becomes required
Weekly stress run red since Jul 135 Willowsfix or delete; nothing hides behind red
215 of 261 repair texts unrunnable (before the message mirror)benchrebuild their history from AppFolio, read-only
Live calls ungraded on 8 voice agents7 supervisedcriteria on every agent (Fede's call, see Q3)
Spanish is smoke only (27 of 421)bench + 3full slice, judged in Spanish
No coverage ledger (incident → fixture → red → green)allone ledger, rendered on this page
No shared scrubberallone scrubber (see Consolidate)
Willows drivers uncommitted5one committed willows-live suite

5. Build order

Cut by proof: each PR merges alone, dark, under 600 lines. Owner is the Clara lane (001) unless marked.

  1. Day 0 in CI. The compare runs on every PR that touches the core. The snapshot (ids, times, roles only) moves to private AWS storage, read through the same credentials the gold-mine scorecard workflow already uses. Advisory for one week, then required. Proof: a planted one-cell change goes red in CI.
  2. Whole turn in each cell. Cells record reply text, tool calls and filed records from recorded model answers (no model call). An intended change needs a ledger row. Proof: re-freeze on today's main is byte-identical.
  3. Retire the copies. The 19 one-gate replays and the 4 old engines become Day 0 fixtures, then get deleted. Proof: same verdict per record, listed in the PR.
  4. No red that nobody reads. Fix or delete the weekly stress run; wire the 21 orphan promptfoo configs or delete them. Proof: one green scheduled run, or the deletion list.
  5. Commit the Willows drivers as one willows-live suite (text, email, call) with the scenario log and record ids. Proof: today's Willows scenarios re-run from the suite.
  6. Fixture overlay for data changes on the Day 0 snapshot.
  7. Voice cells: real call transcripts and after-call outcomes in the replay.
  8. Simulated prospect and tenant attack fleet (rung 2), results onto the bench.
  9. Full Spanish slice; rebuild the 215 pre-mirror repair texts.
  10. One bench runner, one merged shadow loop, one label path; point the bench skill at the repo.
  11. Shared scrubber; coverage ledger; one npm run harness entry point.
  12. Delete the 30 dead suites (list in the PR).
  13. Gera / maintenance lane: work-order filing fixtures; robot request baselines and nightly account checks for Western Slope and Situs.

Fede's call: only the three questions below. Everything else is built and merged under the standing grant.

6. Questions for Fede

Q1. Make the Day 0 compare a required check on main?

AAdvisory one week, then required if it had zero false reds; drop the fast-passing leasing gate the same day RECOMMENDED
BRequired from day one
CStay advisory

Q2. Who re-freezes the baseline? (the code reserves it for your go)

AWeekly automatic re-freeze; every changed cell listed on the ledger for you to skim RECOMMENDED
BOnly on your go, each time
CAt the end of each refactor step

Q3. Grade every live call? (writes ElevenLabs config on 8 agents)

AYes: copy the scripted-suite criteria onto every live agent; grades only, no behavior change RECOMMENDED
BNo: grade calls in our own nightly job instead
CLeave as is

T1. A reply is held for review while a prospect waits (Proposed, pending review)

AKeep the hold as written; the card goes to the customer's Slack channel; the check stops flagging homes she already has tours at; client staff are never emailed RECOMMENDED
BHold, plus a note to the client's staff
CHold, plus a short holding line to the prospect

T2. "Cancel" arrives while the prospect has several tours booked (Proposed, pending review)

AThe automatic tour step stands down; Clara reads the message and cancels exactly the tours named RECOMMENDED
BTeach the automatic tour step to handle several tours itself

1 · The atlas (August inventory, historical)

August inventory, historical. The table and sections below are the Aug 26 count, kept as history. The current plan is October 27 plan: one harness above.

Every harness/rig found tonight, one row each. Click a column header to sort; use the chips or the search box to filter. Evidence for every row: file count and last-commit date are from direct find/git log against the repo tonight; CI status is from a path-scoped grep against .github/workflows/ (not a basename substring match — several early basename hits turned out to be false positives and were corrected). Where a fact couldn't be confirmed tonight, the cell says so instead of guessing.

All layers Prompt/message Conversation Outcome/after-call Live-call Infra/CI-integrity

shown

NamePathProvesLayer Fixture formatRunnerGrader(s) ScrubberCorpusCI status Incident originOverlap with

2 · The 9 replay systems, side by side

Nine directories independently solve "load a fixture, run it through something, diff/grade the result." None has everything. This is what each has that the others lack.

#SystemInput sourceReplay mechanismGradingIsolation (can it write prod?)Unique strength
Read, updated after a deeper pass tonight: evals/replay-harness is the strongest single candidate — it's the only one of the 9 that enters at the real production entrypoint (not a stand-in for it) AND self-verifies its own isolation live against AWS STS rather than by code-comment assertion (see Decision 1). scripts/replay-harness has the cleanest neutral plumbing (corpus + policy + runner) for a fully deterministic, zero-LLM-cost mode. quality-replay/ has the most rigorous prod-write PREVENTION stack (deny-all-by-default + always-hard-block-outbound + an ephemeral bench conversation, three layers deep — but it does write real, sandboxed DDB rows, unlike replay-harness which writes nothing) and the only real cross-system grading reuse (imports quality-gate-poc's checker instead of inventing its own). scripts/tour-decider-replay is safest by construction for its slice — it replays a pure function, so it cannot reach the network at all. The other five are either downstream scorers/comparators over outputs someone else produced (turnover-replay, vendor-recognition-replay, tour-replay) or narrower, real-corpus drift guards (trace-replay, evals/replay) — genuinely useful, but not full engines in their own right. Nothing here is thrown away — section 2's "8 gaps vs the winner" list is exactly what a consolidation has to port in.

3 · One shared skeleton

Not a rewrite. A thin, shared layer every existing harness maps onto through an adapter — the harness keeps its own scenario files, its own domain logic, its own name.

3.1 — One fixture format (superset)

Built directly from three real formats already in the repo: promise-harness/cases.ts, after-call-harness/manifest.ts (tonight's newest harness — already has two of the three attestation mechanisms below), and Cerberus's desk-row fixture format (quality-gate-poc/scripts/convert-desk-row-to-fixture.ts, per BUILD-SPEC.md §5).

interface HarnessFixture {
  id: string;                 // canonical id, e.g. "after-call-001"
  suite: string;               // owning harness, e.g. "after-call-harness", "promise-harness"
  kind: "case" | "invariant";  // "case" = replay a conversation/call; "invariant" = standing
                                 //   architecture check (e.g. voice-harness's live-tool-budget)
  layer: "prompt-message" | "conversation" | "outcome-after-call"
       | "live-call" | "infra-ci-integrity";
  subLayer?: string;           // domain vocab kept AS-IS, not renamed:
                                 //   Cerberus "input"|"loop"|"outbound", voice-harness "L1".."L4"

  provenance: {
    source: "real-transcript" | "synthetic" | "adversarial" | "prod-replay";
    sourceRef?: string;         // conversationId / call id / shard:line — byte-exact pointer
    incidentIds: string[];      // named incidents this fixture proves fixed
    piiVerified: boolean;       // required true before a real-transcript fixture can land
    scrubMethod: "log-mask" | "full-replace" | "none-needed";
  };

  input: unknown;              // opaque per-domain payload — untouched, whatever shape the
  expected: unknown;           //   suite already uses for its case/verdict

  confidence: "confirmed" | "unconfirmed";
  taxonomyRefs?: string[];      // e.g. classRef[] (systematic-pattern.md classes A-M), ["R42"]

  regressionStatus: "red-before-green-after" | "already-fixed" | "known-gap-not-fixed";
  liveDrivable: boolean;        // can this same case also run live against a real system

  addedBy: string;              // PR / commit / session that added it
  addedAt: string;               // ISO date
}

Sent to the build agent working PR 6373 / fede/after-call-poc tonight, ahead of this page, so the newest harness in the repo lands already compatible — its classRef/incidentRefs/liveDrivable/regressionStatus fields map 1:1 onto this shape with zero renaming inside its own manifest.ts.

3.2 — One runner CLI

npm run harness -- --suite promise-harness
npm run harness -- --suite after-call-harness --incident matter_849f19e9
npm run harness -- --suite all --layer outcome-after-call --ci

A single entry point that reads each suite's own fixtures (through its adapter, see 3.6) and reports pass/fail + the run-attestation stamp (3.5) in one shape, regardless of which of the 45 directories actually ran. Suites keep their own internal runner (run.ts, score.ts, whatever) — the shared CLI calls into it, it doesn't replace it.

3.3 — One grader registry

Not one grader — a registry naming which of the ~89 existing judge/grader/grade-named files is canonical for which fault class, so the next harness looks one up instead of writing #90. quality-replay/ already proves the pattern works (it imports quality-gate-poc's three-layer checker directly rather than inventing its own) — that's the model to generalize, not a new mechanism to build.

3.4 — Two scrubbers, not one (deliberately)

The prior inspection's "8 scrubbers → 1" instinct is wrong per Fede's own hot rule 7: a log-line masker and a committed-fixture replacer have genuinely different jobs. Two survive:

Log-mask
Redacts a live string inside a log line or console output — fast, partial, fine for something nobody commits. Target: src/lib/platform/security/scrub-pii-log-string.ts (already the production module; most of the 8 should import this instead of re-deriving their own regex).
Full-replace
Required for anything landing in a committed fixture — a real name/phone is replaced with an obviously-fake stand-in (Cerberus's "Solomon Brant" precedent), never partially masked, and requires an explicit piiVerified: true per row before the write happens (convert-desk-row-to-fixture.ts's existing contract). This is the harder, slower path, on purpose.

3.5 — One attestation mechanism (already invented tonight — not a proposal, a promotion)

All three pieces already exist in fede/after-call-poc, verified live tonight by an adversarial pass. The proposal is to make these the standard every suite adopts, not to invent something new.

3.6 — One coverage ledger

Incident → fixture → red-before → green-after, one row per incident, across every suite — not per-harness bookkeeping. This is what doc-index-of-harnesses (the prior inspection's cheapest, zero-risk recommendation) becomes once it's populated from the fixture format's own provenance.incidentIds + regressionStatus fields instead of hand-maintained. No such ledger exists today; every suite tracks its own incident list in its own README, which is exactly why the same regression class gets rediscovered more than once.

3.7 — Adapters, not rewrites

Existing harnessKeepsAdapter does
promise-harness/cases.tsIts own case shape, unchangedMaps caseId→id, channel→subLayer, expectedPromise/expectedStatus→expected
after-call-harness/manifest.tsclassRef/incidentRefs/liveDrivable/regressionStatus, unchangedAlready a 1:1 rename — see 3.1
Cerberus desk-row fixturesIts own 5-file case (context/ledger/inbound/trace/expected), unchangedMaps layer(input/loop/outbound)→subLayer, correctDisposition→expected
voice-harness scenario YAMLsAll 5 driver tiers, unchangedMaps each YAML's own layer tag into subLayer under live-call
evals/turnover-walk (turnover Crucible)capture-only-gauntlet.ts, false-promise-gauntlet.ts, unchangedWraps each gauntlet run as one HarnessFixture per scenario for the shared ledger only

3.8 — What gets deleted outright

4 · How we build a harness here

One page. Pin this next to the skeleton, not instead of it.

  1. Name the incident first. A harness that doesn't trace to a real bug, a real near-miss, or a named product invariant is a nice-to-have, not a gate. Write the incident ID down before writing any code.
  2. Write the fixture in the shared format (3.1). Not your own shape "for now, migrate later" — later never comes, which is the entire reason 45 directories exist today.
  3. Prove red-before, green-after. Run the fixture against the code as it was before your fix (red) and after (green), in the same commit's evidence. A fixture that was never actually red isn't proof of anything.
  4. Wire it as an enforced check with attestation (3.5). Fixture-deletion tripwire, run attestation, mutation control — all three, not the one that felt urgent tonight. If it can't be enforced yet, say so explicitly (advisory, burn-in) rather than silently leaving it manual-only forever.
  5. List it in the atlas (section 1). One row, same day it ships. The atlas is generated off the fixture format's own metadata, not hand-typed — so this step is "make sure your suite emits the metadata," not "go edit a doc."
The build check (G14-style): a pre-push check that fails a PR adding a new top-level or scripts/-level directory matching harness|replay|gauntlet|crucible|stress|eval in its name unless it (a) imports the shared runner CLI, (b) emits fixtures in the HarnessFixture shape, and (c) registers a mutation control for any new guard it ships — the same shape as the existing scripts/check-mutation-control.ts pre-push gate, extended to catch a new harness DIRECTORY the same way that check already catches a new GUARD. Until this exists, every doctrine step above is a request, not a rule — which is exactly the gap that produced tonight's finding that the newest harness in the repo doesn't yet meet the repo's own existing mutation-control rule.

5 · Decisions for Fede

D1 — Which replay engine is the base for the shared runner?

Nine systems do "replay + grade." None has everything (section 2). Picking one as the base doesn't delete the others tomorrow — it decides which plumbing new replay scenarios get written against from now on. Updated after a second, deeper pass tonight that read all 9 side by side in detail — see section 2's full comparison table.

A evals/replay-harness — the ONLY one of the 9 that enters at the real production entrypoint (runPipeline→dispatchInbound→handleIncomingMessage, the same function Twilio/SQS call), runs against an audited 2,502-row/117-eligible-conversation census, grades cheapest-first (exact/tool-match, LLM judge only on divergence), and is the only one whose isolation is verified live against AWS STS at run time — independently proof-run with 0 prod writes. RECOMMENDED
B quality-replay/ — real tool-execution-layer interception (recorded results served under the REAL reasoning loop) with the most rigorous prod-write isolation stack, and the only one that already reuses another suite's grader (Cerberus's checker) instead of inventing its own. Its own README calls it "spike quality," advisory/manual-trigger only, and it DOES write real (sandboxed) DDB rows — A writes none at all.
C Neither — combine A's real-entrypoint/audited-corpus shape with B's grader-reuse pattern (port B's "import the checker directly" trick into A's grader registry, section 3.4) rather than picking a single winner outright.

Recommendation: A, with B's grader-reuse pattern folded into the shared grader registry rather than lost. Entering at the real dispatcher is the property hardest to retrofit later (it's an architectural choice, not a config flag), and A already proves the strongest isolation contract found in either system. B's genuinely valuable idea — grade by importing an existing checker instead of inventing a new one — ports cleanly into A via section 3.4's registry, so nothing gets thrown away, only re-homed.

D2 — Migrate onto the skeleton incrementally, or freeze new harnesses until the port is done?

45 directories is a lot to move at once. The G14-style check (section 4) can land immediately either way — the question is what happens to the existing 45.

A Incremental: build the adapter for each suite one at a time, highest-incident-value first (after-call-harness and the turnover Crucible are already closest — both were touched or built this week), no stop-the-world rewrite. RECOMMENDED
B Freeze: no new harness work until every existing suite has an adapter — matches "finish this before starting that" but risks stalling tonight's live after-call-harness work, which is mid-flight right now.
C Wait for the promptfoo audit to land first, then plan both consolidations together as one pass.

Recommendation: A. The G14 check stops the bleeding immediately (no more sprawl); the adapters are additive and don't require touching a suite's own scenario files, so there's no real cost to doing them in parallel with live work instead of blocking it.

D3 — Who owns the atlas and the doctrine going forward?

The eval deep-inspection's own finding, two days ago: "today 'the standard' lives in one person's head" — nearly every mechanism here is single-author (Fede's automation account); Gera has one commit in the escalation Crucible, zero in the coworker harness.

A Status quo — Fede's automation account keeps building and maintaining new harnesses as they come up.
B Gera owns the doctrine + G14 check as the first named piece of "the standard"; the atlas table itself stays script-generated off each suite's fixture metadata (section 3.6), so it can't rot the way the 45 READMEs already have. RECOMMENDED
C No single owner — the atlas is a living doc anyone updates when they touch a harness (this is close to what happened to the 45 READMEs already, and is why none of them cross-link).

Recommendation: B. Naming an owner for the doctrine (not every harness — just the rule that new ones follow it) is the cheapest fix for the single-author problem; making the atlas itself generated rather than hand-maintained is what stops it becoming READMEs-with-extra-steps.

PropFlow Docs