Skip to content

Run the internal tool pilot on GPT-6 Sol and Claude Opus 5.5 (not a board condition) - #232

Merged
MaxGhenis merged 8 commits into
mainfrom
tool-pilot-more-models
Oct 15, 2026
Merged

MaxGhenis merged 8 commits into
mainfrom
tool-pilot-more-models

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

WIP. This extends the internal tool-use pilot from #229 (scripts/tool_pilot_argon.py) to two more board models. It is not a board condition: nothing changes in the run store, the snapshot, the exports or the dashboard, and the board's single no_tools condition is untouched. Results stay internal and are not posted here.

What changes

run --model <board model> sends each model the way the board does:

  • GPT-6 Sol (Responses API, forced-tool contract): the conversation is chained by previous_response_id, and every function call gets a function_call_output. Tools use the Responses API's flat shape, and the forced answer is {"type": "function", "name": "submit_outputs"}.
  • Claude Opus 5.5 (JSON contract; its API returns a 400 on a forced tool, per its model card): calculate_household is offered under tool_choice: auto, the answer is the board's JSON reply, and the cap is a user message. Calls after the cap get a not_run result. Each Anthropic request marks its last message as a prompt-cache breakpoint; the stored conversation stays unmarked.
  • Gemini 4 Argon keeps its Add an internal tool-use pilot for Gemini 4 Argon (not a board condition) #229 behavior, except for one change: a calculation that arrives in a forced-answer turn is refused instead of run. The property test below found that gap. It never happened in the Argon run (3 forced or repair turns, none carrying a calculation), so Argon's records and scores are unchanged.
  • Labels (<model>+pe-tool), the frozen arm-1 rows and score follow the model the records name. Records written before this change carry no model name and are read as Argon's. run refuses a directory that holds another model's records.

diagnose settles unread outputs with the board's own output:

  • The gap. A model can read a state's own variable (ca_income_tax_before_refundable_credits) rather than the board's (state_income_tax_before_refundable_credits), so classify_miss files the miss as wrong_variable_or_period.
  • The evidence. resolve_unread_output takes the holding calls: successful calls with a result within $1 of the answer, on the entities of the output's engine variables. It computes the board's output on each of their situations and on the main call's.
  • Reclassifying. Only if every one equals the answer does the miss become wrong_input_mapping, attributed on the holding call that computed the most outputs.
  • Standing. Otherwise the verdict stands, with the main call's situation as evidence and every holding call's value listed; it is marked ambiguous when the values split.
  • Never reclassified: binary outputs, and misses in a household whose calls all computed no outputs (so no situation is the main one). A standing verdict says why it stands.

Attribution:

  • Necessary inputs. attribute_miss names them by leave-one-out when only the full set of differences fixes the output.
  • Mapping kind. Each mapping miss is labelled entered_differently when the model set a cause, or put its amount under a reviewed counterpart in REENTRY_PAIRS; otherwise left_out. Other equal amounts are listed as entered_as_candidates.

misses.csv gains those columns.

Invariants (tested)

For every scripted conversation on all three paths (Hypothesis, 300 derandomized examples):

  • Every function call of a response that reaches the conversation gets exactly one result in the next request, before any user text.
  • Each Responses request chains to the response before it.
  • A reply cut off below the budget ceiling is resent unchanged at the ceiling.
  • At most cap turns run calculations, with at most 4 each, and at most MAX_TOTAL_TURNS turns reach the conversation. Requests equal turns plus truncation retries; the extra request for a retried turn is intended.
  • The shared spend counter equals the sum of the household's turn costs.
  • A JSON-contract request offers only calculate_household, under auto, and a complete household has every output.

Four mutants of the harness each fail that test on the matching assertion:

  • a retry below the ceiling;
  • user text before function results;
  • no result past the per-turn limit;
  • broken chaining.

There are also example tests:

  • Each path: prompt equality with the board, chaining, cap forcing, truncation retry at the ceiling, JSON repair and nudge, cost at each model's list price with cached input, and the cache breakpoints.
  • Diagnosis: settled verdicts (equal, different, and the main-call fallback) and leave-one-out on fakes.

pytest tests/test_tool_pilot_argon.py: 98 tests (97 fast, 1 slow that builds the engine).

Reviews

  • r1 (GPT-6.1 Sol, a56b4bc): AGREE. Its coverage notes on the property test (ordering, truncation, text beside calls) are addressed in 7e6c549.
  • r2 (e9f9efc): REQUEST_CHANGES. A coincidental probe on any entity could flip a verdict, and one test fabricated a diff. Fixed in fae34f5.
  • r3 (fae34f5): REQUEST_CHANGES.
    • A lone same-entity probe could outvote the main situation for a summed answer.
    • Disagreement reported the probe's situation.
    • A numeric coincidence could earn a re-entry label.
    • Fixed in 297b173. A mutant without the main-call check fails the new test.
  • r4 (297b173, Claude Opus 5.5 lane while the Codex lanes were busy): AGREE, with two minor notes. A tie at zero outputs could make a probe the main call, and a standing verdict didn't always say why. Both fixed in d4a8852.
  • r5 (d4a8852, GPT-6.1 Sol): AGREE, with one nit: the no-holder note could appear on binary outputs. Fixed in 29cff9f.
  • r6 (29cff9f, GPT-6.1 Sol): AGREE, no findings.

Plan

  1. A 5-household smoke on each model, with a budget cap. Done.
  2. The full 100 households for each. Done.
  3. Score and diagnose. Done, internal.
  4. Merge on green CI at 29cff9f.

🤖 Generated with Claude Code

The pilot harness ran Gemini 4 Argon only. `run --model` now takes a board
model and sends it the way the board does:

- GPT-5/6 models go through the Responses API, chained by
  previous_response_id with a function_call_output for every call.
- JSON-contract models (Claude Opus 5.5, whose API refuses a forced tool)
  get calculate_household under tool_choice auto, answer in the board's JSON
  reply, and are told about the cap in a message; calls after the cap do
  not run. Anthropic requests mark their last message as a prompt-cache
  breakpoint.
- Labels, the frozen arm-1 rows and the score follow the model the records
  name; records without one are Argon's.

A calculation that arrives in a forced-answer turn is now refused rather
than run. A property test over scripted conversations found the gap; it
never happened in the Argon run (3 forced or repair turns, none with a
calculation), so Argon's results are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
policybench-site Ready Ready Preview Oct 15, 2026 8:31pm UTC

Request Review

…ty test

The review of #232 noted that the protocol property test compared call ids
without their order, and that its scripted model never sent text beside a
call or a truncated reply. The script now does both. The test checks that
function results come before any user text, and that a reply cut off below
the ceiling is resent unchanged at the ceiling. The request count is bounded
by turns plus truncation retries, which is intended: a retried turn costs
one more request. Four mutants of the harness (retry below the ceiling,
user text first, no result past the per-turn limit, broken chaining) each
fail it on the matching assertion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GPT-6 Sol often reads a state's own variable, such as
ca_income_tax_before_refundable_credits, instead of the board's
state_income_tax_before_refundable_credits. classify_miss then files the
miss as wrong_variable_or_period, because the output's engine variable was
never computed, even when the variable read equals the board's output on
that situation and the inputs decide the miss.

resolve_unread_output takes the last successful call whose results hold the
submitted value (else the main call) and computes the board's output on its
situation. If they agree, the verdict becomes wrong_input_mapping and goes
to attribution; if not, it stands with the board's value recorded, which
shows whether the situation itself was right.

attribute_miss now also names the necessary inputs by leave-one-out when
only the full set of differences fixes the output, and labels each mapping
miss as a stated fact left out or entered differently. misses.csv gains
those columns, plus the answering call and the board's value on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mapping_kind called a mapping miss "left_out" whenever the model had not
set the cause's variable. In GPT-6 Sol's scenario_088 the reference sets
home_mortgage_interest, which Sol left unset, but Sol entered the same
$10,777 as mortgage_interest. _entered_elsewhere now counts a cause as
entered when the model set it, or when its amount (within $1, numbers
only) sits on the model side of another difference on the same unit.

The toy outputs in the attribution tests now differ by more than the
board's $1 tolerance, which the first version of the new test did not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
r2 found that a coincidental probe could flip a verdict: any later call
holding a number equal to the answer, such as an employment_income of
$200 on a person, was taken as the answering call, and its situation was
attributed. resolve_unread_output now reclassifies a wrong_variable_or_period
miss only when:
- the holding calls are those with a matching result on the output's own
  entities (the entities of its engine variables);
- the board's output on every holding call's situation equals the answer.
  Calls that disagree leave the verdict standing, marked ambiguous;
- the output is an amount (a 0 or 1 matches too much by chance).
The situation attributed is the holding call that computed the most
outputs, not the last one.

attribute_miss also lists entered_as, the model inputs on the cause's unit
that hold an unset cause's amount, so a re-entry (Sol's interest_income for
taxable_interest_income) can be told from a coincidence. The single-cause
test now uses situation_diff's own diffs, as r2 asked; new tests cover r2's
probe case, disagreeing holding calls, the fullest call and binary outputs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…entries only (review r3)

r3 found three gaps in fae34f5:
1. A summed answer (Opus's $3,720 + $870) has no holding call of its own,
   so a lone same-entity probe holding the sum could flip the verdict even
   though the main situation's board output disagreed. The main call (the
   one that computed the most outputs) must now agree too.
2. When holding calls disagreed, the verdict stood but reported the last
   probe's situation. It now reports the main call's, with every holding
   call's board value in holding_board_values.
3. A numeric coincidence (scenario_018's $50 of interest beside 50 weekly
   hours) could earn entered_differently. Only REENTRY_PAIRS, re-entries
   read in a transcript, now count: taxable_interest_income ->
   interest_income and home_mortgage_interest -> mortgage_interest, on the
   same unit and period. Other equal amounts are listed as
   entered_as_candidates and leave the miss left_out.

A mutant without the main-call agreement fails the new test for case 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dict stands (review r4)

r4 agreed at 297b173 and noted two minor gaps:
1. When no call computed any of the household's outputs, every call ties at
   zero and main_call's tie rule made the last call the main one, so a
   probe could pass the main-call check on its own evidence.
   main_call_and_count now reports the count; at zero there is no main
   situation, and nothing is reclassified.
2. A verdict that stands now says why: the main situation could not be
   computed; or its board output equals an answer no call holds (an answer
   the model built itself); or the evidence is ambiguous.

The minimal state-variable test now computes a second output, as real
answering calls do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
r5 agreed at d4a8852 with one nonblocking note: binary outputs skip the
holding check, so the "no call's results hold it" note could appear even
when a 0 sits in a result. The note is now suppressed for binary outputs.
The binary test's call now computes SNAP, so it is the main call and
reaches the note; with the guard removed, the test fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 15, 2026 20:34
@MaxGhenis
MaxGhenis merged commit 49cba4b into main Oct 15, 2026
5 checks passed
@MaxGhenis
MaxGhenis deleted the tool-pilot-more-models branch October 16, 2026 00:37

This branch was successfully deployed

1 active deployment
Preview — 29cff9ff Deployed Oct 15, 2026 by vercel[bot]
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