Skip to content

fix(rlm): reject sandbox name collisions - #70

Draft
isaacbmiller wants to merge 1 commit into
review-base/stanford-main-10014from
isaac/rlm-namespace-collisions
Draft

fix(rlm): reject sandbox name collisions#70
isaacbmiller wants to merge 1 commit into
review-base/stanford-main-10014from
isaac/rlm-namespace-collisions

Conversation

@isaacbmiller

Copy link
Copy Markdown

1. Issue / repro

RLM exposes signature inputs, user tools, and built-in helpers in one Python namespace, but currently accepts configurations where those names cannot coexist.

rlm = RLM(
    "lookup -> answer",
    tools=[Tool(first, name="lookup"), Tool(second, name="lookup")],
)

There are two independent collisions here:

  • the tool list silently drops first because normalization uses last-write-wins dictionary construction;
  • at execution time, the lookup input overwrites the remaining lookup tool.

Inputs can likewise shadow print, SUBMIT, llm_query, or llm_query_batched. A tool named for passes str.isidentifier() and fails only later when the sandbox tries to generate Python for it.

2. Why this is the root cause

RLM currently validates each source of names in isolation:

return {tool.name: tool for tool in tool_list}

That loses duplicate information before validation. _validate_tools() then checks only tool identifiers and a few reserved tool names; it never compares tools with signature inputs, and isidentifier() alone accepts Python keywords.

The runtime is behaving consistently: Python has one global name for lookup. The invalid configuration should be rejected before RLM builds prompts that advertise both meanings.

3. How we know the fix addresses the root cause

On untouched main at 24ec85de4, the repro prints:

second
RLM
RLM

That proves duplicate tools silently keep the second function, while print as an input and lookup as both input and tool are accepted.

This branch has constructor-level regressions for:

  • duplicate tool names;
  • Python-keyword tool names;
  • inputs shadowing each built-in RLM sandbox helper;
  • inputs shadowing user tools.

All fail at the boundary where the conflicting namespace is assembled.

4. Why this is the concise fix

Tool normalization now builds the dictionary explicitly so it can reject a duplicate before information is lost:

for value in tools:
    tool = to_tool(value)
    if tool.name in normalized:
        raise ValueError(...)
    normalized[tool.name] = tool

The existing tool validator becomes _validate_namespace() and adds only the missing comparisons. There is no runtime renaming, precedence rule, alias map, or fallback lookup.

5. Context needed to validate the change

The public RLM sandbox namespace contains:

  • signature input variables;
  • user-provided tools;
  • llm_query and llm_query_batched;
  • SUBMIT and print.

RLM's prompt tells the model those names are available simultaneously. If two sources use the same name, choosing a winner would make that prompt false. Renaming one side is the only configuration that preserves both capabilities.

6. What the fix does in the code

  • Rejects duplicate normalized tool names instead of silently keeping the last.
  • Rejects tool names that are Python keywords.
  • Rejects signature inputs that shadow built-in sandbox helpers.
  • Rejects signature inputs that shadow a user tool.
  • Renames the private validator to reflect that it validates the shared namespace, not only tools.

Compatibility boundaries and downsides

  • Intentional constructor rejection: configurations that relied on duplicate last-write-wins behavior now raise ValueError.
  • Intentional collision rejection: signatures/tools that reused one name must rename either the input or tool. Both meanings could not be available simultaneously before this change.
  • Earlier failure: a Python-keyword tool now fails during RLM construction instead of later during generated sandbox setup.
  • Valid configurations are unchanged: tool invocation, input transport, prompts, providers, interpreter lifecycle, and extraction are untouched.
  • No broad reservation policy: this PR validates only the public names RLM itself assembles; it does not reserve arbitrary standard-library or implementation-internal names.
  • No overlap with generic Signature validation: upstream PR #10011 concerns DSPy attributes, while this boundary is specific to RLM's interpreter namespace.

Validation

  • uv run --frozen pytest -q tests/predict/test_rlm.py::TestRLMInitialization --deno23 passed
  • uv run --frozen pytest -q tests/predict/test_rlm.py --deno116 passed, 2 skipped
  • repository pre-commit hooks on both changed files — passed
  • keyword-tool assertion against untouched main — fails with DID NOT RAISE ValueError; passes here
  • direct main repro — duplicate resolves to second; input/helper and input/tool collisions construct successfully

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