Skip to content

feat(rlm): split tools into sandbox_tools and native_tools - #95

Merged
YoanSallami merged 1 commit into
mainfrom
feat/rlm-native-and-sandbox-tools
Aug 16, 2026
Merged

feat(rlm): split tools into sandbox_tools and native_tools#95
YoanSallami merged 1 commit into
mainfrom
feat/rlm-native-and-sandbox-tools

Conversation

@YoanSallami

Copy link
Copy Markdown
Contributor

Why

RecursiveLanguageModelAgent exposes every user tool inside the sandbox: bound into the snippet namespace and advertised in the prompt's tools catalog, while the provider's own tool schema carries only run_python_code.

That is the right placement for a tool a snippet composes with — one whose result feeds the next line of Python. It is the wrong one for a tool the LM only wants to consult: a lookup whose answer shapes the snippet it is about to write. Forcing that through the sandbox costs a round-trip through generated code just to read an answer.

It also has a concrete failure mode. The catalog presents sandbox tools as a tools list with names, descriptions and JSON-schema parameters — which looks exactly like a tool-call spec — so models emit native tool calls for them and get:

Unknown tool 'search_functions'. Callable tools are 'run_python_code';
everything else is a sandbox function reachable from your snippet.

Observed 24 times in a single agent task on Qwen3.8-27B, and in every task of a 19-task run.

What

  • native_tools= — tools the LM calls directly, alongside run_python_code.
  • sandbox_tools= — an explicit spelling of the existing behaviour, for agents that have some of each and where tools= would read ambiguously.

Native tools reuse the dispatch path the subagent tools already take (extra_native_tools), so they land in the provider schema, in _dispatch_tool_calls, and in the Unknown tool message's list of what is callable — while staying out of the sandbox namespace and out of the catalog.

Compatibility

tools= is unchanged: it still means sandbox tools, so existing agents keep their behaviour. It is concatenated with sandbox_tools=. A name may not appear in both halves, and native tools get the same reserved-name and public-name checks as sandbox tools.

Tests

Added to rlm_agent_test.py:

  • test_tools_still_means_sandbox_tools — back-compat pin: tools= lands in the sandbox half, and an agent that never mentions native_tools has none.
  • test_tools_and_sandbox_tools_are_concatenated
  • test_sandbox_tools_is_a_spelling_of_tools
  • test_native_tools_are_kept_out_of_the_sandbox_set
  • test_native_and_sandbox_tools_coexist
  • test_same_name_in_both_halves_rejected
  • test_reserved_names_rejected_for_native_tools_too
  • test_native_tool_is_dispatched_not_rejected — end-to-end: the LM emits a native tool call and gets the tool's result back.
  • test_sandbox_tool_called_natively_is_still_rejected — the other half of the contract.

synalinks/src/modules/agents/: 195 passed before, 197 after (plus the constructor tests).

🤖 Generated with Claude Code

RLM exposed every user tool inside the sandbox: bound into the snippet
namespace and advertised in the prompt's tools catalog, while the provider's
own tool schema carried only `run_python_code`. That is the right placement
for a tool a snippet *composes* with — one whose result feeds the next line of
Python — and the wrong one for a tool the LM only wants to *consult*, whose
answer shapes the snippet it is about to write. Forcing the latter through the
sandbox costs a round-trip through generated code, and the catalog advertises
names that are not natively callable, which models try anyway and get
`Unknown tool` back.

Add `native_tools=` for tools the LM calls directly, alongside
`run_python_code`, and `sandbox_tools=` as an explicit spelling of the
existing behaviour. Native tools reuse the dispatch path the subagent tools
already take, so they land in the provider schema, in `_dispatch_tool_calls`,
and in the `Unknown tool` message's list of what is callable — while staying
out of the sandbox namespace and the catalog.

`tools=` is unchanged and still means sandbox tools, so existing agents keep
their behaviour; it is concatenated with `sandbox_tools=`. A name may not be
in both halves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@YoanSallami
YoanSallami merged commit 96a0857 into main Aug 16, 2026
6 checks passed
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