Skip to main content
This tutorial walks through building a Recursive Language Model (RLM) — an inference pattern where an LLM writes code to programmatically explore data instead of reading it all at once. We’ll use ts-rlm for the framework and Vers VMs for isolated code execution.

What You’ll Learn

  • What RLMs are and why they outperform raw prompting on large contexts
  • Building an RLM that analyzes documents through iterative code execution
  • Adding custom tools (API calls, databases, file access)
  • Using Vers VMs as sandboxed interpreters for safe code execution

Prerequisites

  • Vers CLI installed and authenticated
  • Bun runtime installed
  • An Anthropic or OpenAI API key

The Problem RLMs Solve

LLMs have context windows — 200K tokens for Claude, 128K for GPT-4o. But context windows have two problems:
  1. Cost: Sending 200K tokens per request is expensive
  2. Accuracy: Models get worse at finding specific information as context grows (Lost in the Middle)
RLMs solve both. Instead of feeding the entire document to the model, you put the data in a variable and give the model a REPL. The model writes code to search, filter, and extract what it needs — touching only the relevant parts.
The model explores the data programmatically, calling sub-LLMs only on the slices it needs.

Step 1: Set Up the Project

Create a Vers VM

Edit vers.toml:

Install Dependencies

Inside the VM:

Step 2: Build a Basic RLM

Create basic.ts:
Run it:
With verbose: true, you’ll see the model’s reasoning and code at each step. A typical trajectory:
  1. Explore: print(document.slice(0, 300)) — sees the structure
  2. Search: print(document.match(/growth|fastest|YoY/gi)) — finds growth indicators
  3. Extract: reads the AI Products line, sees 32%
  4. Verify: calls llmQuery() to confirm AI is the fastest-growing segment
  5. Answer: FINAL("32% — AI Products is the fastest-growing segment at $800M")
Five steps instead of sending the whole document.

Step 3: Add Custom Tools

Tools let the LLM interact with external systems from inside the REPL. Create with-tools.ts:
The model calls readFile("/etc/os-release") and runCommand("df -h"), then synthesizes the answer. It never needs you to paste system info into the prompt.
The runCommand tool above executes arbitrary shell commands. In production, restrict what commands are allowed or use a dedicated Vers VM per execution (see Step 5).

Step 4: Process Unbounded Data

The real power of RLMs shows with data that doesn’t fit in a context window. Create large-dataset.ts:
The model can’t read 10,000 tickets at once. Instead it:
  1. Parses the JSON: const data = JSON.parse(tickets)
  2. Filters: const critical = data.filter(t => t.priority === "critical")
  3. Groups: const byCategory = Object.groupBy(critical, t => t.category)
  4. Computes: averages per category using reduce
  5. Answers with the numbers and methodology
The LLM touched maybe 2,000 tokens of code and output. The full dataset was 500KB+ but never entered the context window directly — it lived in a JavaScript variable.

Step 5: Isolated Execution with Vers VMs

The examples above run code on whatever machine hosts the interpreter. For production use — especially with untrusted data or tools that make network calls — you want isolation. Vers VMs give you that.

The Pattern

Instead of running the BunInterpreter locally, you can implement a custom CodeInterpreter that executes code inside a Vers VM:
Pass it to the RLM:
Now every code execution step runs inside an isolated Vers VM. The model can rm -rf / and it only destroys its own sandbox.

Branching for Parallel Exploration

Vers branching enables a pattern that local interpreters can’t: exploring multiple solution paths simultaneously.
Branch the VM at a decision point. Both branches have identical state (variables, intermediate results, installed packages). Run different analysis strategies in parallel. Take the better result. This is especially useful for:
  • Ambiguous queries where the right analytical approach isn’t obvious upfront
  • Validation — run the same analysis two different ways and compare
  • Exploration — one branch does statistical analysis, another does semantic analysis via llmQuery()

How RLMs Compare

RLMs trade latency for accuracy and cost. Each iteration is an LLM call (~1-3 seconds), and a typical task takes 5-10 iterations. But the total tokens consumed are much lower than sending the full context, and accuracy stays high because the model is systematically searching rather than scanning.

When to Use RLMs

Good fit:
  • Data larger than ~50K tokens where specific information needs to be extracted
  • Structured data (JSON, CSV, logs) where code can filter efficiently
  • Tasks requiring aggregation, counting, or computation over data
  • Multi-step analysis where intermediate results inform next steps
Poor fit:
  • Short texts that fit comfortably in context
  • Creative tasks (writing, brainstorming) where exploration isn’t needed
  • Real-time applications where 10-30 seconds of iteration latency is too slow
  • Unstructured prose where code-based search has no advantage over reading

Next Steps

  • Browse the ts-rlm examples for more patterns (Zendesk chat, OpenRouter multi-model)
  • Read the RLM paper (Zhang, Kraska, Khattab, 2025) for the theoretical foundation
  • Try the agent swarms tutorial to run multiple RLMs in parallel across Vers VMs
  • Explore the API Reference for programmatic VM management