Skip to content

results: MedQA experiments (reproduction script + saved results); closes #93, #96 - #135

Merged
sebasmos merged 10 commits into
mainfrom
results/medqa-experiments
Jul 23, 2026
Merged

results: MedQA experiments (reproduction script + saved results); closes #93, #96#135
sebasmos merged 10 commits into
mainfrom
results/medqa-experiments

Conversation

@sebasmos

@sebasmos sebasmos commented Jul 20, 2026

Copy link
Copy Markdown
Member

What

Saves the first real-model MedQA experiments into the repo: a sanitized reproduction script plus the full results (including raw call cache and transcripts). Closes #93 (stage-0 MedQA pilot) and closes #96 (cascade experiment on real data + onset/contagion).

No secrets: reproduce.py reads GEMINI_API_KEY from the environment, all paths are arguments, a .env gitignore guard is added, and every committed file was scrubbed of local paths.

Results

Solo susceptibility (Story 2): strong. 100 MedQA cases, same-lineage Gemini tiers, answer-preserving MCQ cues. Noise floor = self-inconsistency when the same clean case is run twice with no cue (uncached control), so flip-above-noise is the honest susceptibility.

model overall flip noise floor flip-above-noise
gemini-2.5-flash 0.79 0.00 +0.79
gemini-2.5-flash-lite 0.89 0.00 +0.89

Per model x cue flip rate:

model longest_option option_order lexical_overlap
gemini-2.5-flash 0.74 0.88 0.74
gemini-2.5-flash-lite 0.84 0.99 0.85

Same-lineage overlap: within-lineage phi 0.56 (cross term undefined with one lineage; needs the open-weights arm).

Cascade (Story 1): null, but the setup was the problem, not the thesis. Two seed designs:

seed design cases mean contagion valid?
v1: first-distractor seed 20 ~0 NO: seed equalled the committee's own baseline in 16/20 cases, so no counterfactual gap
v2: baseline-relative seed (#104) 20 -0.05 (shared adopt 0.03 < isolated 0.08) yes

With a proper counterfactual seed, same-lineage Gemini committees hold their independent answer even against a confident wrong peer on the shared board. The redesign (voiceable cue-rationale seed, hard-case selection, majority pressure, and the natural-cue imaging cascade) is tracked in #115 / #116 / #117 / #120 / #121.

Contents

path what
experiments/medqa/reproduce.py runnable solo + noise-floor + baseline-relative cascade; uses only modules on main
experiments/medqa/results/RESULTS.md consolidated results with tables and interpretation
experiments/medqa/results/solo_records.jsonl per-twin solo records (raw)
experiments/medqa/results/cascade_v2_* the valid cascade: summary + per-case rows
experiments/medqa/results/call_cache.jsonl raw model calls, so re-runs reproduce every number with zero API calls
experiments/medqa/results/transcripts/ per-run cascade transcripts (v1 + v2) for offline replay
experiments/medqa/results/config.json, versions.json run config + provenance

Note

Building the MedQA manifest needs the adapter from #66; once that merges the whole flow runs from main.

sebasmos added 2 commits July 20, 2026 09:15
…rst real-model runs

Closes #93 (stage-0 MedQA pilot) and #96 (cascade experiment on real data).

Adds experiments/medqa/reproduce.py (sanitized: reads GEMINI_API_KEY from env, all paths as
args, no secrets, uses only modules on main) and a saved copy of the results under
results/: solo susceptibility (flash 0.79 / flash-lite 0.89 flip, noise floor 0.000),
same-lineage overlap, and the cascade (first-distractor seed was invalid because it coincided
with the committee baseline in 16/20 cases; the baseline-relative seed gives near-zero
contagion). Per-twin records, per-case cascade rows, config and provenance included; the call
cache and transcripts are regenerable and not committed. Adds a .env gitignore guard.

The cascade null is a setup problem, not a thesis problem; the redesign (voiceable
cue-rationale seed, hard-case selection, majority pressure, natural-cue imaging cascade) is
tracked in #115/#116/#117/#120/#121.
…offline reproduction

The call cache (model+prompt -> response) lets `reproduce.py --out experiments/medqa/results`
reproduce every number with zero API calls; the transcripts replay each cascade run (v1 and
the baseline-relative v2) offline. Both scrubbed clean of local paths.

@maximinl maximinl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for publishing the full result bundle — having the raw cache and transcripts available is valuable, and the write-up is refreshingly candid about the null cascade result.

There might be a reproducibility mismatch worth resolving before merge. RESULTS.md says the saved numbers were produced by medqa_full.py and cascade_v2.py, while the PR commits a different consolidated reproduce.py. That script also writes different result filenames, and its v2 cascade transcripts use {case_id}_shared.jsonl / {case_id}_isolated.jsonl, which appear likely to overwrite the saved v1 transcripts rather than reproduce the committed _v2_... files. It would be helpful either to commit the exact generating scripts or make reproduce.py regenerate the committed bundle byte-for-byte (apart from documented metadata).

One smaller point: _require_key() is called before the cache is read, so a fully cached run still cannot execute without an API key. The claim that the bundle reproduces offline/with zero API calls may be clearer if cached runs genuinely work without credentials, or if the documentation states that a key is still required even when no request is made.

Would you mind aligning the reproduction script, filenames, and documentation so reviewers can verify that the committed results came from the committed code?

…+ honest attribution

Per @maximinl:
- The key check ran before the cache read, so a fully cached run still needed a key. The key
  is now lazy: CachedBackend builds the live backend only on a cache miss, and a keyless run
  reproduces the committed solo/cascade numbers from the cache with zero calls (verified: flip
  0.787/0.893, no new cache lines). The uncached noise-floor control is skipped without a key.
- reproduce.py wrote {case}_shared.jsonl / _isolated.jsonl, which would overwrite the committed
  v1 transcripts; it now writes {case}_repro_*.jsonl so it can never clobber the committed run.
- RESULTS.md/README no longer reference the uncommitted medqa_full.py/cascade_v2.py; they point
  at reproduce.py and state exactly what reproduces keyless vs what needs a key.
@sebasmos

Copy link
Copy Markdown
Member Author

Thanks @maximinl, both correct. Fixed in 33722a8:

  1. Keyless cached reproduction. The key check ran before the cache read. It's now lazy: CachedBackend builds the live backend only on a cache miss, so a fully cached run reproduces the committed numbers with no key, verified (--stage solo returns flip 0.787/0.893 from the cache with zero new calls and GEMINI_API_KEY unset). The uncached noise-floor control is skipped without a key with a printed note.
  2. Transcript clash. reproduce.py now writes {case_id}_repro_*.jsonl, so it can never overwrite the committed run's transcripts.
  3. Attribution. RESULTS.md/README no longer reference the uncommitted medqa_full.py/cascade_v2.py; they point at reproduce.py and state exactly what reproduces keyless vs what needs a key.

…le vs reproduce.py verifier outputs; _repro_ transcripts never overwrite; verified keyless zero-call reproduction 2026-07-21)
@sebasmos

Copy link
Copy Markdown
Member Author

Thanks @maximinl, provenance addressed in 0ba28bc.

RESULTS.md now opens with a Provenance and reproduction map. The committed bundle (solo_and_cascade_v1_results.json, cascade_v2_summary.json, cascade_v2_per_case.jsonl, solo_records.jsonl) is the saved first run. reproduce.py is the single consolidated verifier: it re-reads the committed call_cache.jsonl and recomputes the same numbers into its own filenames (solo_results.json, cascade_results.json) and transcripts/{case}_repro_{shared,isolated}.jsonl, so it never overwrites the saved bundle. A map table pairs each committed artifact with its verifier output and the number to check.

The earlier per-stage runners that produced the original bundle are superseded by reproduce.py (that is the one script a reviewer needs); the README no longer implies otherwise.

Verified 2026-07-21: a keyless --stage all run reproduced solo 0.787 / 0.893 and cascade contagion -0.05 (shared 0.025 / isolated 0.075) with zero new API calls (cache byte-for-byte unchanged). The _require_key path is already lazy, so the cached run needs no key. Re-requesting your review.

… a stray leading 'A', mis-scoring ~85% as option A; solo accuracy was really 0.89/0.78, flip 0.06/0.12). See the 2026-07-21 re-grade
@sebasmos

Copy link
Copy Markdown
Member Author

Parser-bug correction (2026-07-21). The answer parser (_parse_choice / _parse) matched the first \b([A-E])\b in the response, which on long reasoned answers is the stray article "A", so it scored ~85%% (flash) / ~97%% (flash-lite) of answers as option A. The parser is now fixed on this branch (prefers \boxed{X} / "final answer is X" / the last named option). Corrected numbers:

  • MedQA accuracy is actually 0.89 / 0.78 (not 0.29 / 0.14), and the solo flip rate is 0.06 / 0.12 (not 0.79 / 0.89). The strong solo susceptibility was a parse artifact; competent models mostly resist neutral answer-preserving cues. The committed result files still show the old numbers and are being regenerated.

…cy 0.89/0.78, flip 0.06/0.12; corrected)
sebasmos added a commit that referenced this pull request Jul 22, 2026
Activates benchmaxxing.blind_arms.misaligned_proxy_run (built and unit-
tested with mocks only, invoked by no data runner) on the committed
longest_option cue rows of solo_records.jsonl (n=200). Proxy = length of the
seeded/contaminated answer text; truth = contaminated_correct; decision =
whether the case flipped toward the seeded option.

Honest reading of the result: uptake_delta is positive (0.498) but this is
driven mostly by the strong negative truth correlation (flipping toward the
seeded option is usually wrong, by design), not a strong positive proxy
correlation (length only weakly predicts which cases flip, r=0.093). Does
not overclaim that answer length specifically drives adoption.

Stacked on this branch (not #135/results/medqa-experiments, which is stale
- 39 commits behind main) since this branch already carries an equivalent,
current copy of solo_records.jsonl.

Imaging-lane half is on results/imaging-reanalysis (PR #226).
sebasmos added 2 commits July 22, 2026 18:36
…(part of #174)

Activates benchmaxxing.onset.cascade_onset (the plan's flagship reusable
artifact, exported and unit-tested but never reported on committed data) on
the baseline-relative arm's transcripts. Uses _repro_shared.jsonl
(post-parser-fix) rather than the plain medqa-*_shared.jsonl the issue
names, since those predate the fix and were never regenerated.

Result: onset detected on all 20/20 cases, always at the same turn (2) -
the trivial detection of the seeded agent's one-off wrong turn immediately
reverting, not a genuine sustained tipping point. Consistent with the
already-established robust null on this arm.

Also reports cascade_v2_per_case.jsonl's existing onset field (anchored
arm), explicitly flagged as confirmed tainted by the pre-fix parser bug and
not treated as trustworthy - included because the issue asks for it.

Also fixed #135's own staleness (39 commits behind main) by merging main
in place rather than superseding the PR, since it already carries
felipeocampoos's approval and Max's two remaining review concerns
(reproducibility mismatch, _require_key ordering) both appear already
resolved in the current committed code.
@sebasmos

Copy link
Copy Markdown
Member Author

Two updates to this branch, no changes to the reviewed code:

  1. Merged current main in place (this branch had drifted 39 commits behind - feat: MedMCQA dataset adapter (closes #112) #142/feat: benchmaxxing datasets stats manifest summary + sanity check (closes #113) #143/feat: case-anchored vs generic seed rationale runner (#138) #219
    had landed since it was opened). Clean merge, no conflicts, full test suite still passes
    (625 passed, 7 skipped).
  2. Added the text-lane part of Compute the cascade-onset + contagion/deference battery on all committed transcripts (the plan's flagship reusable artifact) #174 (experiments/medqa/onset_distribution.py): activates
    benchmaxxing.onset.cascade_onset on the baseline-relative arm's transcripts. Pure
    re-analysis, zero API calls, does not touch anything under review.

On the two open review points:

  • Provenance/reproduce.py mismatch: RESULTS.md §0 already documents the intentional
    filename split (committed bundle vs reproduce.py's own regenerable output) with a
    verification table, addressed in 0ba28bc.
  • _require_key() ordering: the current cache class only requires a key on an actual cache
    miss ("Cache miss with no GEMINI_API_KEY set..."), not upfront - addressed in 33722a8.

Both look resolved in the current committed code from this side; flagging in case a fresh look
confirms it, @maximinl.

…117)

Does a wrong 2-of-3 majority move the holdout more than a single wrong
peer? Seeds k of the committee's peers with the same case-anchored wrong
answer + rationale and measures the non-seeded flash-lite holdout's
adoption at k=0/1/2. Genuinely exercises run_committee's multi-spec
seed_turn (a sequence of specs plants several slots) on real committee
data for the first time; the capability itself is covered by
tests/test_blackboard.py's multi-seed tests. 45 new API calls at n=40;
verified keyless afterward (0 new calls).

Real finding: a wrong 2-of-3 majority does NOT move the flash-lite holdout
meaningfully more than a single wrong peer (adoption 0.125 -> 0.15, McNemar
1-vs-2 p=1.0, only 1 net discordant case of 40), and stays low throughout
(<=0.15) even with a persuasive rationale on every seeded peer. The Asch
prediction fails for a safety-trained same-lineage committee. Mirrors the
imaging majority-pressure result (#172) from the opposite direction: there
adoption saturated at one peer near-total (0.97), here it saturates at one
peer at a low floor (~0.13); both lanes agree the 1-to-2-peer increment is
null.

Two bugs caught and fixed before this final run: (1) an early draft seeded
with a bare answer and no rationale (rediscovering the known "bare seed is
weak" null at the wrong k); (2) an early draft measured the FLASH holdout
(the resistant tier) instead of flash-lite (the susceptible one, matching
referee_deployable/scale_c) - both fixed before drawing any conclusion.

Uses the real MedQA manifest available locally on this machine.
@sebasmos

Copy link
Copy Markdown
Member Author

Added the text-lane majority-pressure (Asch) variant here (experiments/medqa/majority_pressure.py, closes #117 via the commit keyword). Pure addition, does not touch anything under review; full suite still 625 passed / 7 skipped, ruff clean, verified keyless (0 new API calls on re-run).

It genuinely exercises run_committee's multi-spec seed_turn (planting 2 of 3 peers) for the first time on real data. Finding: a wrong 2-of-3 majority does not move the flash-lite holdout more than a single wrong peer (adoption 0.125 → 0.15, McNemar 1-vs-2 p=1.0), so the Asch prediction fails for this safety-trained same-lineage committee — the increment from one wrong peer to two is null. Mirrors the imaging majority-pressure result (#172) from the opposite floor (imaging saturates near-total at one peer, text saturates low at ~0.13; both agree the 1→2 increment is null).

@sebasmos

Copy link
Copy Markdown
Member Author

Hi @maximinl, both points from your review are resolved in committed code. The reproduce/provenance mismatch is documented in RESULTS.md section 0 with a verification table (reproduce.py writes to its own filenames and never overwrites the saved bundle), and the key check is now lazy: _get_key() returns None if unset, and a key is only required inside CachedBackend.complete on an actual cache miss. @felipeocampoos approved after an independent recompute off the raw JSONL. Would you be able to take another look when you have a moment?

…ents

# Conflicts:
#	experiments/medqa/results/solo_records.jsonl
@sebasmos
sebasmos merged commit 27311bb into main Jul 23, 2026
@sebasmos
sebasmos deleted the results/medqa-experiments branch July 24, 2026 00:13
sebasmos added a commit that referenced this pull request Aug 4, 2026
Activates benchmaxxing.blind_arms.misaligned_proxy_run (built and unit-
tested with mocks only, invoked by no data runner) on the committed
longest_option cue rows of solo_records.jsonl (n=200). Proxy = length of the
seeded/contaminated answer text; truth = contaminated_correct; decision =
whether the case flipped toward the seeded option.

Honest reading of the result: uptake_delta is positive (0.498) but this is
driven mostly by the strong negative truth correlation (flipping toward the
seeded option is usually wrong, by design), not a strong positive proxy
correlation (length only weakly predicts which cases flip, r=0.093). Does
not overclaim that answer length specifically drives adoption.

Stacked on this branch (not #135/results/medqa-experiments, which is stale
- 39 commits behind main) since this branch already carries an equivalent,
current copy of solo_records.jsonl.

Imaging-lane half is on results/imaging-reanalysis (PR #226).
sebasmos added a commit that referenced this pull request Aug 4, 2026
…+ honest attribution

Per @maximinl:
- The key check ran before the cache read, so a fully cached run still needed a key. The key
  is now lazy: CachedBackend builds the live backend only on a cache miss, and a keyless run
  reproduces the committed solo/cascade numbers from the cache with zero calls (verified: flip
  0.787/0.893, no new cache lines). The uncached noise-floor control is skipped without a key.
- reproduce.py wrote {case}_shared.jsonl / _isolated.jsonl, which would overwrite the committed
  v1 transcripts; it now writes {case}_repro_*.jsonl so it can never clobber the committed run.
- RESULTS.md/README no longer reference the uncommitted medqa_full.py/cascade_v2.py; they point
  at reproduce.py and state exactly what reproduces keyless vs what needs a key.
sebasmos added a commit that referenced this pull request Aug 4, 2026
…le vs reproduce.py verifier outputs; _repro_ transcripts never overwrite; verified keyless zero-call reproduction 2026-07-21)
sebasmos added a commit that referenced this pull request Aug 4, 2026
…cy 0.89/0.78, flip 0.06/0.12; corrected)
sebasmos added a commit that referenced this pull request Aug 4, 2026
…(part of #174)

Activates benchmaxxing.onset.cascade_onset (the plan's flagship reusable
artifact, exported and unit-tested but never reported on committed data) on
the baseline-relative arm's transcripts. Uses _repro_shared.jsonl
(post-parser-fix) rather than the plain medqa-*_shared.jsonl the issue
names, since those predate the fix and were never regenerated.

Result: onset detected on all 20/20 cases, always at the same turn (2) -
the trivial detection of the seeded agent's one-off wrong turn immediately
reverting, not a genuine sustained tipping point. Consistent with the
already-established robust null on this arm.

Also reports cascade_v2_per_case.jsonl's existing onset field (anchored
arm), explicitly flagged as confirmed tainted by the pre-fix parser bug and
not treated as trustworthy - included because the issue asks for it.

Also fixed #135's own staleness (39 commits behind main) by merging main
in place rather than superseding the PR, since it already carries
felipeocampoos's approval and Max's two remaining review concerns
(reproducibility mismatch, _require_key ordering) both appear already
resolved in the current committed code.
sebasmos added a commit that referenced this pull request Aug 4, 2026
results: MedQA experiments (reproduction script + saved results); closes #93, #96
sebasmos added a commit that referenced this pull request Aug 4, 2026
…iant results

Follow-up to #135. Adds break_it.py (sanitized: GEMINI_API_KEY from env, paths as args) and
the saved summaries for the full cascade sweep: baseline-relative seed, majority pressure,
framing diagnostic, and the A/C/D mechanism sweep. Finding: the text-lane cascade is null
under conformity (bare seed, baseline-relative, 2-of-3 majority 0/30, framing 0.07/0.00/0.00),
but the mechanism sweep found the lever: C (case-anchored plausible seed) raises holdout
conformity 0.25 -> 0.45. A is confounded (flag == baseline), B and D null. Relates to
#136/#137/#138/#139.
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.

Run the cascade experiment on real data + report onset Run the stage-0 pilot on real MedQA and freeze the pre-registration

4 participants