Skip to content

Simulated user v2, optional CHECKER (official setup), grader 0.8.7 - #1

Merged
that-guy-wade merged 14 commits into
mainfrom
exp/simulated-user-fidelity
Oct 10, 2026
Merged

that-guy-wade merged 14 commits into
mainfrom
exp/simulated-user-fidelity

Conversation

@that-guy-wade

@that-guy-wade that-guy-wade commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Experiments to make the simulated user less brittle and more realistic, and the changes that held up. Results and method are in experiments/RESULTS.md. Decided: adopt simulated user version 2; the official setup includes a CHECKER (Gemini 3.1 Pro), which also answers whether a result holds despite the simulated user's misreadings (grader 0.8.7).

Results

Replay of every decision in all 1,489 published runs (2,521 decisions), official judges:

Arm "i don't know" Declines Disputed readings
Published code 113 (4.5%) 0 142 (5.6%)
This branch 75 (3.0%) 30 142 (5.6%)
This branch + CHECKER 75 (3.0%) 30 90 (3.6%)

Live A/B (model baseline, Claude Sonnet 5.5 and Gemini 3.1 Pro, 8 tasks × 2, 32 runs per arm): "i don't know" 8/42 → 2/32 replies; runs with a disputed reading 10 → 6; outcomes waiting for a person 9 → 6; replies sent reworded 55% → 75%; flight-before-ben 2 pass + 2 pending → 4 pass.

Regrading those live records with the opt-in CHECKER (Gemini 3.1 Pro): runs waiting for a person 9 → 7 with the published code, and 6 → 3 with this branch. On the full published records the checker agreed with 31 of the 34 adjudications it could answer. (An earlier checker also answered the person-only causality question; fixed.)

Hermes Agent in Harbor with simulated user v2: 5 live trials ran cleanly; flight-before-ben (the case that started this) passed twice with no human annotation and no CHECKER.

Impact on scores: the five published model assistants on the 12 tasks where v2 could matter, 3× each (180 runs per arm): pass rate 64.8% → 65.0%, per-model differences within noise; "i don't know" 7/98 → 2/89 replies. On the 1,530 full published records, this grader reproduces all 918 trial 3–5 outcomes from the published answers.

Changes

  • Simulated user version 2 (assistanteval/simulated_user.py, recorded in each run):
    • An offer of optional further help gets "no thanks, that's all for now" instead of "i don't know"; the grader checks a decline as it checks "i don't know". Independent audit: 23/27 confirmed by two outside models, none rejected.
    • Before "i don't know", a proposal the reader missed is routed to its approval rule when both the reader and the judge read it as one (with the reader alone, outside models rejected 8/21).
    • Writer prompt that says what to keep and what not to add: 52% → 67% of replies reworded in testing, no rise in meaning changes.
    • Opt-in ASSISTANTEVAL_USER_STYLE=terse|distracted (wording only; tested against the "same meaning, adds nothing" check).
  • Grader:
    • Opt-in CHECKER role: a model other than the judge settles re-checks of the simulated user's approval decisions when the reader is unsure. Unset, verdicts are unchanged (parity suite).
    • 0.8.6: an end that keeps the length of the event it moves is sourced in code (parity difference 14).
  • Tasks: preference facts for three tasks from experiments/gaps.py, reviewed by hand; go-ahead twins' contradictory goal sentence removed.
  • experiments/: replay probe, gap discovery, live A/B scripts, style-overlay and checker audits. Data from published runs stays local (data terms).

Comparability

New runs with simulated user version 2 are not directly comparable with published runs on tasks where the user said "i don't know". Grader 0.8.6 differs from 0.8.5 only for kept-length ends. CHECKER changes verdicts only when set.

Testing

  • 660 tests pass; ruff format and F401/F811/F841 clean; parity goldens pass with one documented difference.
  • Replay over all published runs, independent audits, and 96 live runs (see RESULTS.md).

Complexity

Package: +180 / −32 lines. SimulatedUser.reply CC 18 → 23 (the decline and routing branches); Ladder.decide_one 8 → 10 (checker rung); new helpers ≤ 6. Experiments add ~3,000 lines of scripts and write-up outside the package.

Follow-ups (not in this PR)

  • Reader calibration on short rewordings (most remaining writer fallbacks); escalating them to the judge was measured and rejected (it let an implicit yes through).
  • Grow task preferences with gaps.py plus human review as new assistants are run.

Linear: ORO-2748

🤖 Generated with Claude Code

RetriggerConfidence Score: 4/5

Fix the lost saved checker answers before merging; the experiment failures are lower-impact follow-ups.

Fix All in Claude CodeFindings

  1. P1 Offline grading drops checker answers ▶
  2. P2 Prompt experiment exits immediately ▶
  3. P2 Baseline replay rejects checker ▶

Summary

This PR adds simulated user version 2, optional CHECKER grading, task preferences, and experiment scripts.

  • The simulated user declines optional help and checks missed proposals against its rules.
  • An optional CHECKER answers grading questions the reader cannot settle.
  • The grader recognizes event end times that preserve the event’s length.
  • Experiment tools replay conversations and compare simulated-user and grader results.
  • Style experiments measure how user reply wording changes without changing decisions.

Review scope: the previous-review commit was unavailable, so the full PR diff was used. No previous threads or numbered findings were supplied; GitHub authentication was unavailable to retrieve them. Checks were based on source reads, not runtime execution.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  Q[Grading question] --> H{Saved human answer?}
  H -->|Yes| D[Use answer]
  H -->|No| R{Allowed reader is sure?}
  R -->|Yes| D
  R -->|No| J{Allowed judge is sure?}
  J -->|Yes| D
  J -->|No| C{Allowed checker is sure?}
  C -->|Yes| D
  C -->|No| A[Ask a person]
Loading

Reviews (3) · Last reviewed commit: "Official setup: the CHECKER also answers..." · Reviewed by Greptile

that-guy-wade and others added 3 commits October 10, 2026 17:20
… of more help

- Before "i don't know" (and on a finished turn), ask about each action
  the user has a rule for with the grader's own re-read question.
- Answer an offer of optional further help with "no thanks, that's all
  for now"; the grader checks a decline as it checks "i don't know".
- Record the simulated user's version (2) with each run.
- Grader 0.8.6: an end that keeps the length of the event it moves is
  sourced in code.
- Preference facts for three tasks, found by experiments/gaps.py and
  reviewed by hand.
- experiments/: replay harness over recorded conversations, gap
  discovery, live run script.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…r styles

- Route a missed proposal to its approval rule only when the reader and the
  rules judge both read it as one: with the reader alone, independent models
  rejected about a third of the routings.
- Grader: optional CHECKER role, a model other than the judge that settles
  re-checks of the simulated user's approval decisions when the reader is
  unsure. Unset, verdicts are unchanged.
- Writer: a prompt that says what to keep and what not to add (52% -> 67%
  of replies reworded in testing, no rise in meaning changes), and an
  opt-in ASSISTANTEVAL_USER_STYLE of terse or distracted.
- Experiments: style overlays, an independent audit of routed decisions,
  probing of full run records, live A/B summary.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- experiments/RESULTS.md: method, population replay, independent audits,
  live A/B, writer and style results, what did not work, comparability.
- Go-ahead twins: drop the user goal sentence that contradicted their
  openings (documentation only; no code reads it).
- Share the simulated user's earlier-messages helper.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@that-guy-wade that-guy-wade self-assigned this Oct 10, 2026
that-guy-wade and others added 11 commits October 10, 2026 17:45
The Harbor smoke's scripted user reached the decline check, which the
keyword judge did not know.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A live Hermes run ended "if you'd rather have the debrief on Monday,
tell me and I'll ask Ben"; the reader could not tell whether it had
finished, and the user said "i don't know". An unsure reading that is
an offer now gets a decline, which the grader checks as it checks
"i don't know".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A question with no rungs (whether a result holds despite the simulated
user's misreadings) is a person's alone; the checker answered it too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…reement

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- contracts.Stop: a host's reason to end a run now (such as a lost
  account lease) is recorded with its code, like a stop signal.
- run_task.stop_on_signals is public.
- grader and report commands take the answers store's class, for a host
  whose saved answers need reading another way.
- An installed runner never asks git for its revision: it would name the
  repository the package is installed in.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…er 0.8.7)

The causality question names the checker as its only rung: never the
reader or the judge, whose readings it weighs. Without a checker it waits
for a person as before. The official setup now sets a CHECKER (Gemini 3.1
Pro). On the 360-run sweep, runs waiting for a person fall from 21 to 3
(published simulated user) and 20 to 1 (version 2).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@that-guy-wade that-guy-wade changed the title Simulated-user fidelity: fewer "i don't know", fewer disputed readings Simulated user v2, optional CHECKER (official setup), grader 0.8.7 Oct 10, 2026
@that-guy-wade
that-guy-wade marked this pull request as ready for review October 10, 2026 21:09
@that-guy-wade
that-guy-wade merged commit 7b4ae78 into main Oct 10, 2026
2 checks passed
recheck = "reader" in q.rungs and "judge" not in q.rungs
rungs = {*q.rungs, *(("checker",) if recheck else ())}
for rung in ("reader", "judge", "checker"):
if rung not in rungs or (rung == "checker" and not self.checker):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Offline grading drops checker answers

When a run was settled by CHECKER, regrading with --offline skips its saved answers. Ladder(store) has no live checker, so this condition skips the rung before _answer can read the cache. The run can return to needs_human_annotation despite having all its answers saved. Allow saved checker answers without requiring a live checker.

Fix in Claude Code Fix in Codex

Comment on lines +114 to +116
if CURRENT not in system:
raise SystemExit("_voice's prompt changed: CURRENT no longer matches")
return super().__call__(system.replace(CURRENT, TIGHT), user)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Prompt experiment exits immediately

The documented tight.py run command exits on its first writer call. TightWriter requires the old CURRENT text, but _voice now sends REWRITE, which already equals TIGHT. This prevents teammates from rerunning the comparison. Keep explicit old and new prompts in the experiment rather than requiring production to still use the old one.

Fix in Claude Code Fix in Codex

Comment thread experiments/probe.py
Comment on lines +246 to +248
checker = endpoint("CHECKER") # only code with a checker rung takes one
extra = {"checker": Cached(judge_for(checker), cache)} if checker else {}
ladder = Ladder(store, reader=reader, judge=rules, **extra)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Baseline replay rejects checker

With CHECKER set by the documented official setup, AE_CODE cannot replay the published checkout. This code passes checker= to the imported Ladder, but the base checkout's constructor has no such argument. The baseline comparison fails before measuring any decisions. Check whether that checkout supports the checker before passing it, and leave the baseline arm checker-free.

Fix in Claude Code Fix in Codex

@that-guy-wade
that-guy-wade deleted the exp/simulated-user-fidelity branch October 12, 2026 07:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant