fix(rlm): preserve input mutations across iterations - #67
Draft
isaacbmiller wants to merge 1 commit into
Draft
Conversation
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.
1. Issue / repro
RLM tells the model that interpreter state persists between iterations, but ordinary signature inputs are re-injected before every generated snippet.
On
main, this returns6, not10. Before iteration 2 runs, RLM silently restoresnumbersto[1, 2, 3].2. Why this is the root cause
Every generated snippet currently crosses this boundary:
variables=means “inject these values into the interpreter namespace before this execution.” Repeating it overwrites mutations from earlier iterations. That conflicts with both contracts already visible to implementers and models:CodeInterpreterdocuments that state persists acrossexecute()calls.SandboxSerializableinputs already follow the correct lifecycle: setup and assignment happen before the loop. Only ordinary inputs were coupled to per-iteration code execution.3. How we know the fix addresses the root cause
The regression test uses the real Deno/Pyodide interpreter, not a mock:
numbers=[1, 2, 3].4in one model iteration.numbersin the next iteration.The exact test fails on untouched
mainat24ec85de4:It passes here with
10. Unit tests also assert that ordinary inputs are injected exactly once.4. Why this is the concise fix
This moves input injection to the existing session-initialization boundary and removes
input_argsfrom the per-iteration call chain:The patch does not add state flags, first-iteration branches, retry paths, or a second namespace abstraction. It uses the persistence guarantee the interpreter already provides.
The existing
SandboxSerializabletransport is intentionally unchanged: each serializable still runs the same setup/assignment call with the same payload variables. Ordinary inputs get one additional no-op initialization call:5. Context needed to validate the change
There are two distinct objects named “variables” in this module:
REPLVariablemetadata is formatted into the model prompt.execute(..., variables=...)mutates the live interpreter namespace.This PR only changes the second. Prompt metadata, tools, output coercion, extraction after
max_iters, and LM behavior are untouched.6. What the fix does in the code
_initialize_inputsbecause it now initializes both input forms.SandboxSerializablesetup path.input_argsthrough sync and async iteration helpers.Compatibility boundaries and downsides
execute("pass", variables=...)call before model-generated code.CodeInterpreter.executealready requires support for code plus variable injection and persistent state. Test doubles that script one response per call must include this initialization call.CodeInterpreterprotocol are unchanged.Validation
uv run --frozen pytest -q tests/predict/test_rlm.py --deno—110 passed, 2 skippeduv run --frozen pytest -q tests/primitives/test_python_interpreter.py tests/primitives/test_sandbox_serializable.py --deno—66 passedmain— fails with6; passes here with10