Repository navigation
Keep the qed_math_env answer-mode reference server-side and stop scoring after done in reasoning_gym_env and finqa_env - #1552
Merged
burtenshaw merged 4 commits intoNov 5, 2026
Conversation
…gent
In answer mode, reset() put reference_solution (already wrapped as
\boxed{gold}) in the ProblemObservation, and the get_problem MCP tool added
it to its payload. Rows without a rubric are routed to answer mode
automatically. On HuggingFaceH4/MATH-500, submitting that reference
verbatim scored 1.0 on 391 of the 500 rows.
Grading already reads the reference from the server-side problem and the
gold cache, so both outputs now leave it out, as proof mode already did.
QED-Nano formats only the task into the actor prompt and passes the answer
to the verifier, so this does not change parity with it.
Signed-off-by: jimmy-holdout <346065422+jimmy-holdout@users.noreply.github.com>
step() returns done=True with correct_answer, but it kept the current entry, so a second step() on the same question was scored again. Answering anything, then answering the revealed correct_answer, scored 1.0. The harness forwards correct_answer to the agent in the tool result, and StepEnvSessionAdapter and SessionMCPBridge pass further calls through. The observation of the first scored answer is now kept, and later step() calls return it again until reset(), without scoring or counting a step. The episode reward stays the scored answer's reward, both for callers that read the last step (the session's default verify()) and for _resolve_env_reward. Signed-off-by: jimmy-holdout <346065422+jimmy-holdout@users.noreply.github.com>
submit_answer returned done=True with ground_truth in the metadata, but nothing recorded that the episode had ended. A second submit_answer was scored again, so submitting anything and then the returned ground_truth scored 1.0. The submit branch also runs before the max_steps check, so a submission after the episode had ended at max_steps was still scored. step() and step_async() now keep the terminal observation when an answer is submitted or max_steps is reached. Any later step returns it again until reset(): no tool runs, nothing is scored, and step_count does not change. A submission on the last allowed step is still scored. Signed-off-by: jimmy-holdout <346065422+jimmy-holdout@users.noreply.github.com>
burtenshaw
reviewed
Nov 4, 2026
burtenshaw
left a comment
Collaborator
There was a problem hiding this comment.
No blocking issues on 9920bc0e.
What I ran:
- The three env suites (
tests/envs/test_qed_math_environment.py,test_reasoning_gym_environment.pywithreasoning-gyminstalled, andtest_finqa_environment.py): 262 passed, 16 skipped, 5 deselected. With each env's server file restored to its parent commit, the new fix tests fail (qed 2, reasoning_gym 2, finqa 6) and the controls pass. - The harness and collect tests that resolve rewards (
tests/core/test_harness_collect.py,test_harness_rollout_backcompat.py,test_harness_runtime.py,tests/envs/test_openspiel_collect.py,tests/test_cli/test_collect.py): 164 passed. reasoning_gym_envunder uvicorn, withReasoningGymSessionFactorybehindSessionMCPBridgeand seed 1. A wrong answer followed by the revealed answer gives step rewards[0.0, 0.0],verify()0.0 and_resolve_env_reward0.0. The parent commit gives[0.0, 1.0], 1.0 and 1.0. A correct answer followed by one extra call gives[1.0, 1.0], 1.0 and 1.0. The parent gives[1.0, 0.0], 0.0 and 0.0.finqa_envunder uvicorn withFINQA_MAX_STEPS=3, driven over the WebSocket session. Resubmitting the returnedground_truthscores 0.0, andstep_countstays at 1. A submission after the max-steps end also scores 0.0. The parent commit scores both 1.0.call_tool("submit_answer")over/mcpreturns only the confirmation string, so that route never scored.- A
qed_math_envanswer-mode row served withcreate_app, with its gold string searched for in 16 outputs: the WS reset,/mcpget_problem,get_grading_guidelinesandsubmit_proof, WSstepforget_problemandsubmit_proof,state, HTTP/reset,/state,/schema,/metadata,/web/reset,/web/step,/web/state,/web/metadataand the Task API. None contain it. On the parent commit, 6 of them do. - Consumers:
QEDMathEnv._as_problem_observation(envs/qed_math_env/client.py:79) buildsProblemObservation(**payload), and the field defaults to"", so the missing key is safe. Nothing in examples, docs or integration code readsreference_solution. Every caller ofreasoning_gym_envorfinqa_envin the repo stops atdone.
Follow-ups, not blocking:
qed_math_envkeeps scoring afterdone, which is the bug this PR fixes in the other two envs.submit_proof_payload(envs/qed_math_env/server/qed_math_environment.py:1348-1376) grades every call and does not check whether the episode has ended. On this branch, with an answer-mode row whose gold is73441,step_async(submit_proof("\boxed{1}"))returns reward 0.0 withdone=True. A secondstep_async(submit_proof("\boxed{73441}"))then returns reward 1.0 withdone=True. With the reference hidden, answer mode allows only blind re-guessing. In proof mode the grader prompt contains the reference (envs/qed_math_env/server/rubric.py:438and:454-455) and the grader feedback goes back to the agent, so a second submission can use it. A fix must letmulti_stepproblems keep scoring until their owndone. A follow-up issue is enough for this PR.- Optional:
envs/thinkingbox_env/server/thinkingbox_environment.py:211already keeps the terminal observation as_terminal_observation. Using that name instead of_final_observationin both envs keeps the repeated pattern aligned, as CLAUDE.md asks.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1551
Summary
There are three commits, one per environment in the issue.
qed_math_envno longer sends the answer-mode reference solution to the agent.reasoning_gym_envandfinqa_envstop scoring once the episode has ended. Laterstep()calls return the episode's final observation again, with the same reward, untilreset().qed_math_env:reset()leavesProblemObservation.reference_solutionempty andget_problemleaves it out in both modes. Proof mode already worked this way. Grading still reads the reference from the server-side problem and the gold cache. The docstrings (the class,reset(),mcp_server.py), thereference_solutionfield description and the README now say the reference is not sent.reasoning_gym_env: the observation of the first scored answer is kept, and laterstep()calls return a copy of it untilreset(), without scoring or counting a step. The episode reward therefore stays the scored answer's reward. That holds for the session's defaultverify(), which reads the last step, for_resolve_env_reward, which reads the last non-Nonetool reward, and for any loop that reads the last step. The existing "no current entry" branch is unchanged.finqa_env:step()andstep_async()keep the terminal observation when an answer is submitted ormax_stepsis reached, andreset()clears it. Any later step, includingListToolsAction, returns a copy of it: no tool runs, nothing is scored, andstep_countdoes not change. A submission on the last allowed step is still scored.ground_truthstays in the submit observation, which now comes only at the end of the episode.Behaviour changes for callers:
reference_solutionfrom theqed_math_envreset observation or theget_problempayload in answer mode now gets""or no key.reasoning_gym_envandfinqa_env, astep()afterdonenow gets the final observation again instead of a new score or a tool result. This applies to thestep()/ WebSocket path. The production/mcproute calls tools directly and never issued a step reward.Type of Change
Alignment Checklist
Before submitting, verify:
PRINCIPLES.mdand this PR aligns with our principlesINVARIANTS.mdand no invariants are violatedlint.sh) and the tests, and addressed all issueslint.shstops at itsruff format --check src/ tests/ envs/step onmaintoo. It lists the same 47 files onmainand on this branch: 46 underenvs/andsrc/openenv/core/harness/README.md. Three of them are READMEs edited here (qed_math, reasoning_gym, finqa). In those, ruff flags code blocks that this PR does not change.usort checkreports only the two known files.ruff format --checkon the changed Python files, andruff check src/ tests/, pass.scripts/sync_env_docs.py --checkpasses. The three doc stubs were regenerated with--fix.RFC Status
Test Plan
New tests. The ones that check the fix fail on the parent commit and pass on their own commit (qed 2, reasoning_gym 2, finqa 6). The honest-path controls pass on both.
tests/envs/test_qed_math_environment.py:test_answer_mode_hides_reference_solution(reset observation andget_problem) andtest_auto_detected_answer_mode_hides_reference_solution(a row without a rubric). Control:test_answer_mode_correct_answer_still_scores(\boxed{4}still gets reward 1.0).test_answer_mode_includes_reference_solutionis replaced.tests/envs/test_reasoning_gym_environment.py: wrong, then the revealed answer keeps reward 0.0; correct, then an extra step keeps 1.0. Control:reset()scores the next question normally. This fileimportorskipsreasoning_gym, which the CI job does not install, so these tests run only wherereasoning-gymis installed.tests/envs/test_finqa_environment.py: newTestEpisodeEnd, with synthetic data likeTestResetSeed, so it runs in CI. Fix tests cover a submit after a submit, a submit aftermax_steps, and aListToolsActionafter a correct submit (reward stays 1.0 andstep_countis unchanged), each throughstep()andstep_async(). Controls: a submit on the last allowed step (1.0) andreset().Runs:
test.yml, run on a fresh clone with the CI job's extra packages plusreasoning-gym(Python 3.13): 4344 passed, 110 skipped on this branch, and 4331 passed, 110 skipped onmain. The touched test files: 153 passed for qed (5 integration tests deselected), 30 passed for reasoning_gym, and 79 passed, 16 skipped for finqa. With the downloaded FinQA data linked atenvs/finqa_env/data, all 95 finqa tests pass, including the existing data-dependentTestEnvironment.reasoning_gym_envandfinqa_envimages were also built from their Dockerfiles and gave the same rewards. The finqa image was driven with raw/wsmessages.mainget_problem""/ absent\boxed{66}/ wrong\boxed{-12345}step("x"), then the revealed answerverify()/ resolved reward)verify()/ resolved reward)submit_answer("0"), then the returnedground_truthFINQA_MAX_STEPS=3ended the episodeNote
Medium Risk
Changes evaluation/training reward semantics and removes answer-mode reference exposure; callers depending on post-done rescoring or client-side gold answers need to adapt.
Overview
Fixes reward leakage and double-scoring across three environments.
qed_math_envno longer sends the ground-truth reference to the agent.reset()/ProblemObservationalways exposereference_solutionas empty, andget_problemdrops it for proof and answer modes alike. Server-side grading and the gold cache are unchanged; docs and field descriptions now state the reference stays server-side only.reasoning_gym_envandfinqa_envtreat the first terminal step as final: they cache the ending observation and return a deep copy on laterstep()calls (includingfinqa_envstep_async/ WebSocket) without re-scoring, running MCP tools, or bumpingfinqa_envstep_count.reset()clears the cache. Submit on the last allowed FinQA step is still scored once.Callers that read answer-mode references from QED or submit again after
done=True(e.g. using revealedground_truthorcorrect_answer) will see different rewards than before. READMEs and synced env docs describe the new semantics; tests cover hiding references, post-done steps, and async FinQA paths.Reviewed by Cursor Bugbot for commit 3bdfdba. Bugbot is set up for automated code reviews on this repo. Configure here.