Repository navigation
Add invocation-scoped code interpreter bindings #10137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ | |
| CodeExecutionError, | ||
| CodeInterpreter, | ||
| FinalOutput, | ||
| _bind_interpreter, | ||
| _create_interpreter, | ||
| _validate_interpreter, | ||
| _validate_interpreter_factory, | ||
|
|
@@ -510,12 +511,7 @@ def _inject_execution_context(self, interpreter: CodeInterpreter, execution_tool | |
| even for user-provided interpreters. Each forward() call gets fresh tools with a | ||
| fresh call counter, so we must inject on every execution. | ||
| """ | ||
| interpreter.tools.update(execution_tools) | ||
| if hasattr(interpreter, "output_fields"): | ||
| interpreter.output_fields = self._get_output_fields_info() | ||
| # Reset registration flag to force re-registration with fresh tools | ||
| if hasattr(interpreter, "_tools_registered"): | ||
| interpreter._tools_registered = False | ||
| _bind_interpreter(interpreter, {**interpreter.tools, **execution_tools}, self._get_output_fields_info()) | ||
|
|
||
| @contextmanager | ||
| def _interpreter_context( | ||
|
|
@@ -526,8 +522,12 @@ def _interpreter_context( | |
| """Yield a caller-owned interpreter or manage a factory-created one.""" | ||
| if interpreter is not None: | ||
| _validate_interpreter(interpreter) | ||
| self._inject_execution_context(interpreter, execution_tools) | ||
| yield interpreter | ||
| original = (dict(interpreter.tools), getattr(interpreter, "output_fields", None)) | ||
| try: | ||
| self._inject_execution_context(interpreter, execution_tools) | ||
| yield interpreter | ||
| finally: | ||
| _bind_interpreter(interpreter, *original) | ||
|
Comment on lines
+525
to
+530
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Two overlapping RLM calls can mutate the same caller-owned interpreter without exclusive ownership of its bindings. Each call independently snapshots tools, injects invocation-specific tools, executes, and restores its snapshot. Concurrent execution causes one invocation to observe the other invocation's ArtifactsFocused caller-owned lifecycle pytest source
Focused caller-owned lifecycle validation output
Comment on lines
+525
to
+530
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The caller-owned path snapshots mutable bindings, installs invocation tools, yields for execution, and restores its snapshot without exclusive ownership of the interpreter. Two overlapping calls can therefore observe each other's tools, restore over each other's active bindings, and leave invocation tools behind after both calls finish. The deterministic concurrent run showed one invocation receiving the other invocation's tool, the other losing its own tool after restoration, and a stale invocation tool remaining afterward. Serialize the complete snapshot/bind/execute/restore lifecycle per caller-owned interpreter, or reject overlapping use. ArtifactsDeterministic shared-interpreter race reproducer source
Before capture of legacy snapshot update restore race
After capture of current RLM binding race
Focused upstream test collection attempt
Comment on lines
524
to
+530
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Overlapping calls using the same caller-owned interpreter independently snapshot, bind, execute, and restore mutable tool bindings. One invocation can therefore observe another invocation's tools, lose its own binding while still active, and leave stale invocation tools after cleanup. This breaks request-scoped capability isolation and can expose tenant-specific or privileged tools across requests. Serialize the full lifecycle per caller-owned interpreter, or explicitly reject concurrent use. ArtifactsDeterministic caller-owned interpreter lifecycle reproduction source
Sequential caller-owned interpreter lifecycle output
Overlapping caller-owned interpreter lifecycle output
Focused RLM pytest collection attempt
Worktree integrity check after validation
Comment on lines
+525
to
+530
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Caller-owned interpreters are snapshotted, rebound, executed, and restored without exclusive ownership of their mutable bindings. Overlapping RLM calls can therefore execute with another invocation's tools, lose their own active tools during restoration, and retain stale invocation tools afterward. Serialize the complete snapshot/bind/execute/restore lifecycle per interpreter, or explicitly reject concurrent use. ArtifactsDeterministic RLM caller-owned interpreter lifecycle reproduction source
Runtime output for caller-owned interpreter sequential and concurrent lifecycle reproduction
Focused RLM interpreter lifecycle test output
|
||
| return | ||
|
Comment on lines
523
to
531
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Caller-owned interpreters are snapshotted, rebound, executed, and restored without exclusive ownership. Overlapping calls can therefore execute with another invocation's tools and leave invocation-scoped tools installed after both calls finish. The reproduced overlap made invocation A receive B's tool result and retained A's ArtifactsDeterministic shared-interpreter RLM concurrency reproduction source
Captured deterministic reproduction source with checksum
Sequential caller-owned interpreter control output
Overlapping caller-owned interpreter reproduction output
Current RLM interpreter lifecycle lines
Focused RLM regression suite output
|
||
|
|
||
| interpreter = _create_interpreter(self._interpreter_factory) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -177,3 +177,14 @@ def _validate_interpreter(interpreter: Any) -> None: | |
| """Validate a caller-owned interpreter.""" | ||
| if not isinstance(interpreter, CodeInterpreter): | ||
| raise TypeError(f"interpreter must implement CodeInterpreter, not {type(interpreter).__name__}.") | ||
|
|
||
|
|
||
| def _bind_interpreter(interpreter, tools, output_fields=None) -> None: | ||
| if callable(bind := getattr(interpreter, "bind", None)): | ||
| bind(tools=tools, output_fields=output_fields) | ||
|
Comment on lines
+183
to
+184
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
ArtifactsFocused reproduction source for a protocol-valid interpreter with bind(connection)
Runtime output showing bind(connection) rejects RLM keyword arguments
Comment on lines
+183
to
+184
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
A caller-owned object can satisfy ArtifactsFocused caller-owned lifecycle pytest source
Focused caller-owned lifecycle validation output
Comment on lines
+183
to
+184
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
ArtifactsFocused RLM caller-owned bind collision reproducer source
Baseline run for structurally compliant caller-owned interpreter
RLM setup failure with unrelated caller-owned bind method
Existing interpreter bind test selection result
Comment on lines
+183
to
+184
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Artifacts
Current binding source excerpt
Comment on lines
+182
to
+184
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
ArtifactsFocused reproduction source with an unrelated bind(connection) method
RLM setup failure with unrelated bind(connection)
Matched control source with DSPy-compatible keyword bind method
Matched RLM control succeeds with keyword-compatible bind
Focused existing RLM interpreter lifecycle tests passing
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
ArtifactsFocused RLM caller-owned interpreter binding reproducer source
Control interpreter reaches execute during RLM caller-owned setup
Unrelated bind method raises before RLM executes code
Current RLM regression suite passes
Comment on lines
+182
to
+184
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
ArtifactsReproduction source for caller-owned interpreter with unrelated bind method
RLM output before safe binding behavior
In-process safe-binding control source
RLM output with in-process safe binding control
|
||
| else: | ||
| interpreter.tools.clear() | ||
| interpreter.tools.update(tools) | ||
| for name, value in (("output_fields", output_fields), ("_tools_registered", False)): | ||
| if hasattr(interpreter, name): | ||
| setattr(interpreter, name, value) | ||
Uh oh!
There was an error while loading. Please reload this page.