Skip to content

Conversation

@BowTiedSwan
Copy link

Summary

This PR adds a new experimental rlm_repl tool that enables true Recursive Language Model (RLM) capabilities in OpenCode.

Fixes #8554

Background

Based on the RLM paper, this addresses two key limitations of typical sub-agent implementations:

  1. Can't write O(N) sub-calls as tool calls - the model can't verbalize 10,000 explicit sub-prompts. Recursion must be symbolic through code.
  2. Long prompts can't fit in context - prompts need to be accessible through pointers for symbolic recursion.

What This PR Adds

A new built-in tool that allows the LLM to write JavaScript code that programmatically invokes sub-LLM calls:

// Instead of 10,000 tool calls, write a 3-line loop
for (const chunk of chunks) {
  results.push(await sub_llm(`Analyze: ${chunk}`))
}

Available Functions

Function Purpose
sub_llm(prompt, agent?) Invoke a sub-LLM call, returns string result
sub_llm_parallel(prompts[], agent?) Parallel sub-LLM calls
context.store(key, data) Store data externally (pointer-based)
context.load(key) Load data by pointer
context.chunk(key, size) Split stored data into chunks
context.keys() List all stored keys

Example: Processing Large Data

// Store large data externally (not in LLM context)
context.store("input", largeDataString)

// Chunk it into manageable pieces
const chunkKeys = context.chunk("input", 5000)

// Process each chunk with a sub-LLM
const results = await sub_llm_parallel(
  chunkKeys.map(k => `Analyze this chunk: ${context.load(k)}`)
)

// Return aggregated result
return results.join("\n")

Security

  • Code runs in a sandboxed environment with limited globals
  • Maximum 50 sub_llm calls per execution
  • 5 minute timeout on total execution
  • Context store limited to 10MB total
  • Sub-agents cannot spawn tasks or use rlm_repl (prevents infinite recursion)

How to Enable

OPENCODE_EXPERIMENTAL_RLM_REPL=true opencode
# or
OPENCODE_EXPERIMENTAL=true opencode

Files Changed

  • packages/opencode/src/tool/rlm-repl.ts - New tool implementation
  • packages/opencode/src/tool/registry.ts - Register the tool
  • packages/opencode/src/flag/flag.ts - Add experimental flag

Testing

Tested that the code compiles without syntax errors. The tool uses the same SessionPrompt.prompt() pattern that TaskTool uses for spawning sub-agents.

Notes

  • The sandbox uses new Function() which is a simplified approach. For production hardening, consider using isolated-vm or vm2 for stronger isolation.
  • This is gated behind an experimental flag to allow iteration before making it generally available.

This adds a new experimental tool that enables true Recursive Language Model
(RLM) capabilities, allowing the LLM to write code that programmatically
invokes sub-LLM calls in loops rather than requiring explicit tool calls.

Based on the RLM paper (https://arxiv.org/html/2512.24601v1), this addresses
two key limitations of typical sub-agent implementations:
1. Can't write O(N) sub-calls as tool calls (model can't verbalize that many)
2. Long prompts can't fit in context (need pointer-based access)

The tool provides:
- sub_llm(prompt, agent?) - invoke a sub-LLM call
- sub_llm_parallel(prompts[], agent?) - parallel sub-LLM calls
- context.store/load/chunk/keys - pointer-based data access

Enabled via OPENCODE_EXPERIMENTAL_RLM_REPL=true or OPENCODE_EXPERIMENTAL=true

Fixes anomalyco#8554

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@github-actions
Copy link
Contributor

The following comment was made by an LLM, it may be inaccurate:

No duplicate PRs found

@androolloyd
Copy link

do we have to invoke anything special to trigger this?

@BowTiedSwan
Copy link
Author

@androolloyd Great question!

To enable the tool, set the environment variable before running OpenCode:

OPENCODE_EXPERIMENTAL_RLM_REPL=true opencode

Or enable all experimental features at once:

OPENCODE_EXPERIMENTAL=true opencode

Once enabled, the LLM can use the rlm_repl tool by writing JavaScript code that calls sub_llm(). For example, when you ask it to analyze a large dataset or process many files, it can write:

// Process chunks in parallel
const results = await sub_llm_parallel(
  chunks.map(c => `Analyze this: ${c}`)
)
return results.join("\n")

The tool becomes available automatically — no special invocation syntax needed. The LLM will choose to use it when the task benefits from programmatic sub-LLM calls rather than individual tool invocations.

Let me know if you have any other questions!

@BowTiedSwan
Copy link
Author

Note on Sandboxing

To clarify how the current sandbox works and its limitations:

Current Implementation (No External Dependencies)

The sandbox uses new Function() with a whitelist of allowed globals:

const sandbox = {
  sub_llm,
  sub_llm_parallel,
  context,
  JSON, Array, Object, String, Number, Boolean, Date, Math, Promise, Map, Set, RegExp, Error,
  parseInt, parseFloat, isNaN, isFinite, encodeURIComponent, decodeURIComponent,
  // Explicitly blocked:
  setTimeout: undefined,
  setInterval: undefined,
  fetch: undefined,
  require: undefined,
  eval: undefined,
  Function: undefined,
}

const asyncFn = new Function(...sandboxKeys, wrappedCode)

Limitation: This is NOT True Sandboxing

A clever payload can escape via the constructor chain:

const F = [].constructor.constructor
const evil = F('return process')()
evil.exit(1)  // Escapes sandbox

The code acknowledges this at line 247-248:

"Note: This is a simplified sandbox. For production, consider using isolated-vm or vm2 for stronger isolation."

Options for Stronger Isolation

Option Dependency Isolation Level
isolated-vm npm + native Strong (V8 isolates)
quickjs-emscripten npm Strong (separate JS engine)
Subprocess None Strong (OS-level)
vm2 npm Medium (deprecated, has CVEs)

Current Assessment

For an experimental feature behind a flag, this is acceptable as a proof-of-concept. The other safeguards (50 call limit, 5min timeout, 10MB context cap, restricted sub-agent permissions) provide additional protection.

However, if this moves toward production, it should be hardened with isolated-vm or similar. Happy to implement stronger sandboxing if the maintainers prefer that before merging.

@androolloyd
Copy link

standardizing around strong isolation is probably a net good

@mwalol
Copy link

mwalol commented Jan 20, 2026

i have tried this pull request using OPENCODE_EXPERIMENTAL=true opencode
but still i hit max token limits at 200k isn't there a smartway to avoid this using the rlm?

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.

feat: Enable programmatic sub-LLM calls for RLM (Recursive Language Model) pattern

3 participants