# Recursive Language Models: context as a variable, recursion as a function call

> Satyajit Ghana — Head of Engineering @ Inkers Technology
> canonical: https://ai.thesatyajit.com/articles/recursive-language-models
> date: 2026-08-06
> tags: agents, llm, recursion, context-management, prime-intellect, explainer
The standard agent loop has one data structure at its center: the transcript. The model reads a
growing conversation, emits a tool call that matches a JSON schema, a harness parses it, runs it,
and appends the result back as another message. Everything the model can act on has to live in
that transcript. Everything it does has to fit the schema. [Agent harnesses](/articles/agent-harness)
and [the harness effect](/articles/harness-effect) are both, in different ways, about how much that
loop shape costs — in design effort and in tokens. A **Recursive Language Model** (RLM) doesn't
optimize the loop. It replaces the data structure.

An RLM gives the model a persistent Python REPL instead of a transcript. Two things follow from
that, and they are the spine of this piece:

1. **Prompt-as-a-variable.** The input — a document, a codebase, a 500&nbsp;MB corpus — becomes a
   value bound to a name in the REPL, not text sitting in the context window. The model writes code
   to slice, filter, and search it, and only what it chooses to print ever enters its own context.
2. **Programmatic recursion.** Calling another language model is a function call — `rlm(...)` — that
   returns a value like any other call. It composes with `for` loops, `if` statements, `map`, and
   error handling, because it *is* one of those, not a special harness verb bolted on next to them.

<Callout type="warn">
**Idea, implementation, product — three different things, one name.** Alex Zhang (MIT CSAIL) coined
"Recursive Language Model" for a specific inference-time technique: one long prompt held as a REPL
variable, decomposed by recursive sub-LM calls, terminated by an explicit `FINAL()`/`FINAL_VAR()`.
Prime Intellect built two things that carry the same name and the same two mechanisms but are not
the same system as each other or as Zhang's original design: `RLMEnv`, a research-eval
reproduction inside their `verifiers` library, and Prime Agent, a general coding harness that
generalizes both mechanisms — context-as-data, recursion-as-a-call — to its entire working
environment, not just one input string. [Prime Agent](/articles/prime-agent) covers that second one
in full, including its separate Continual Harness feature. This piece is about the idea and how the
paper measures it; read that one for the shipped product.
</Callout>

## Who actually built this

Prime Intellect's own post is straightforward about credit, and I'll be too: "the Recursive
Language Model (RLM), introduced by Alex Zhang in October 2025 as a blog post, and now available as
a full paper," with an acknowledgment thanking him "for his original work on recursive language
models." Zhang's post frames the mechanism plainly — an RLM is "a thin wrapper around a LM that can
spawn (recursive) LM calls for intermediate computation," with an API meant to be a drop-in
replacement for an ordinary completion call: `rlm.completion(messages)` where you'd otherwise write
`gpt5.completion(messages)`. The motivating problem is what he calls **context rot**: model recall
degrades as context grows, independent of whether the context still technically fits the window.

The idea was formalized two months later in [*Recursive Language
Models*](https://arxiv.org/abs/2512.24601) (arXiv 2512.24601, submitted 2025-12-31), authored by
Alex L. Zhang, Tim Kraska, and Omar Khattab, all MIT CSAIL. Their own framing in the abstract: RLMs are "a general
inference paradigm that treats long prompts as part of an external environment and allows the LLM
to programmatically examine, decompose, and recursively call itself over snippets of the prompt."
The paper is explicit about what it's reacting against, and credits the right ancestors rather than
claiming recursion or code-as-tool-use as new:

- **CodeAct** established writing code as the tool-call format, but in a standard coding agent that
  code still executes inside the same context-constrained loop — sooner or later the harness has to
  compact. RLMs offload the entire prompt as an external variable instead, so the REPL's addressable
  state isn't bounded by the model's window at all.
- **MemGPT** manages context explicitly, paging things in and out of a single model's working
  memory. RLMs don't build a memory hierarchy; they let the model itself decide what to look at,
  programmatically, each time.
- **ReAct**-style sub-calls are verbalized autoregressively — described in natural-language turns
  inside one transcript. RLM sub-calls are constructed programmatically and their results are stored
  as REPL variables, which is what lets a `for` loop over sub-calls do real accumulated work instead
  of restating each result back into the same window.

So the general idea — treat a long input as an external, programmatically addressable environment,
and let recursive delegation happen through control flow instead of prose — has a real, credited
origin, and it is not Prime Intellect. What Prime Intellect has done is build two different things
on top of it. Their research post says plainly: "we at Prime Intellect have implemented our version
of the RLM in [verifiers](https://github.com/PrimeIntellect-ai/verifiers/) so that it is ready to be
used in any environment," landing as the experimental `RLMEnv` — a reasonably faithful reproduction
of Zhang's design, built for running controlled evaluations. Separately, `prime-agent-runtime`'s
`rlm` package — the one this site's [Prime Agent](/articles/prime-agent) piece covers — takes the
same two mechanisms and applies them to an entire general-purpose coding agent: files, shell
commands, skills, and subagents all go through the same persistent kernel, not just one oversized
input prompt. Zhang's design restricts the root model to *metadata* about the prompt (its length, a
prefix) until it explicitly decides to look closer; Prime Agent's root model just has an ordinary
working context plus a kernel, because it's built to be a general agent, not a single-prompt
inference technique. Related, useful, and worth keeping straight — not the same artifact.

## The prompt becomes a variable

<Figure
  src="/articles/recursive-language-models/fig1.png"
  alt="Diagram of a Recursive Language Model: a root LM writes code into a Python REPL environment where the input prompt is loaded as a variable; it prints slices of the prompt, calls llm_query() to spawn depth-1 sub-RLMs on chapter fragments, and combines their sub-responses into a final answer."
  caption="A Recursive Language Model treats the prompt as part of the environment: loaded as a variable in a REPL, sliced and inspected with code, decomposed into recursive sub-calls whose responses feed the final answer (Zhang, Kraska, Khattab, arXiv 2512.24601, Figure 2 — flattened onto white; layout otherwise unmodified)."
/>

In the paper's own algorithm, the root model never receives the prompt as tokens in its context. It
receives metadata — length, a prefix, how to access it — and a REPL where that prompt already sits
as a variable. The loop is: the model writes code, the REPL executes it, truncated stdout comes
back, and this repeats until the model calls `FINAL(answer)` to return a string directly or
`FINAL_VAR(name)` to return whatever a REPL variable currently holds. Nothing about the prompt's
actual content is ever force-fed into the root model's window; the model decides what to look at, a
slice at a time.

Prime Agent's version of this same bet is less specialized but the mechanism is identical in spirit:
a persistent IPython kernel that survives across turns, with `rlm` preloaded in the namespace. Here
is the actual shim that puts it there, from `prime-agent-runtime/src/rlm/__init__.py`:

```python
class _RLMCallable:
    async def run(self, prompt: str, **kwargs: Any) -> RLMSpawnHandle:
        return await run(prompt, **kwargs)

    async def __call__(self, prompt: str, **kwargs: Any) -> RLMSpawnHandle:
        return await run(prompt, **kwargs)

rlm = _RLMCallable()
```

`rlm` is not a tool the model selects from a menu. It is a plain Python object with a `__call__`
method, sitting in the kernel's global namespace the same way any import would. Calling it is
calling a function, full stop — which is the whole point: nothing about `await rlm(...)` needs a
harness to specially recognize the string `"rlm"` and route it through a different code path than
any other line of Python.

Here's the same underlying claim made concrete with a task. Say the question is "which of these log
files mentions an out-of-memory kill, and which one is worst." A schema-based harness does this as a
sequence of round trips — each one a full model turn, a parsed tool call, and an appended result:

```text
# illustrative — the general shape of a schema-based harness, not quoted from a specific product
assistant: tool_call grep(pattern="Out of memory", path="logs/")
tool_result: {"matches": ["logs/worker-014.log:88231", "logs/worker-014.log:88245", ...340 more]}
assistant: tool_call read_file(path="logs/worker-014.log", offset=88200, limit=100)
tool_result: "<100 lines of log text>"
# …and one more round trip per file the model wants to actually look inside
```

The RLM version, written in the same idiom as the docs' own `config_files` example
(`packages/coding-agent/docs/rlm.md`):

```python
from pathlib import Path

hits = [p for p in Path("logs").rglob("*.log") if "Out of memory" in p.read_text(errors="ignore")]
worst = max(hits, key=lambda p: p.stat().st_size)
print(f"{len(hits)} files mention OOM; worst by size: {worst}")
```

`hits` is a real Python list, still there next turn if the model wants to `map` something else over
it. The grep, the read, and the size comparison happen inside one cell. The model's context grows by
one printed line, not by one message per file.

<ContextInWindowVsVariable />

That's the mechanism at data-scale, not code-scale: the top bar is what has to happen when the only
way to look at something is to read it into the window — the window caps out at 272K tokens for
GPT-5 regardless of how the corpus is chunked, so a 500&nbsp;MB corpus needs on the order of
hundreds of read-and-compact rounds just to scan once, and any single round can only ever see a
272K-token slice. The bottom bar is the REPL: the corpus is bounded by machine memory, not context
budget, and only a found, printed slice ever reaches the model. This is the same shape as the
paper's own S-NIAH and OOLONG scaling runs — hold the task fixed and grow the input, and one line
stays flat while the other falls off past the window boundary.

## Recursion is just a call

The second inversion is about delegation. In a schema-based harness, spawning a subagent is a
distinct, specially-recognized action — usually literally called `Task` or `subagent` in the tool
list, with its own parsing path in the harness. In an RLM, `rlm(...)` is not a different *kind* of
call from anything else in the REPL. It's an `async` function that happens to start another agent
instead of, say, reading a file. Here's the actual implementation, trimmed from the same file:

```python
async def run(prompt: str, **kwargs: Any) -> RLMSpawnHandle:
    """Spawn a recursive Prime Agent child and return once its task is admitted."""
    if not isinstance(prompt, str):
        raise TypeError(f"prompt must be str, got {type(prompt).__name__}")
    payload = await host_request("rlm.run", {"prompt": prompt, "kwargs": kwargs})
    return _spawn_handle_from_payload(payload)
```

`host_request` opens a Jupyter comm to the TypeScript host, which creates a real child
`AgentSession` and returns as soon as the task is *admitted* — not when it's *done*. That admission-
not-completion design is a deliberate choice recorded in Prime Agent's own changelog (covered in
more depth in [Prime Agent](/articles/prime-agent)), and it's what makes fan-out compose with
ordinary control flow instead of blocking on it:

```python
# real — packages/coding-agent/docs/rlm.md
api_review = await rlm("Review the public API", name="api-reviewer")
test_review = await rlm("Review the test coverage", name="test-reviewer")
integration_audit = await rlm("Run the slow integration audit", name="integration-audit")
```

Three lines, three independent children, one turn. In a schema-based harness the equivalent is three
separate structured messages, each requiring the model to emit a full tool call and the harness to
parse and dispatch it — and, if the harness's subagent tool is synchronous, a wait on each before the
next line can even be written. Here it's `for child in reviewers: await rlm(child)` if you want a
loop, or three independent statements if you don't. Recursion composes with the rest of the language
because it's written in the rest of the language.

<RecursionTree />

The tree above is the concrete version of "a task over 200 files is a `for` loop with 200 model
calls made by the program, not 200 round trips through the model's own context." Each spawned
session's own context holds exactly one task — the child at `RLM_DEPTH=1` never sees its siblings,
never sees the root's other work. What holds the shape of the whole job is the root session's Python
namespace: the list of handles, the loop that produced them, the code that will eventually read their
replies. That's a different place for "the state of the whole task" to live than any single model's
context window, and it's why the unit of work stops being "how much fits in 200K tokens" and starts
being "how many child sessions can the host actually run."

## What's actually measured, and by whom

Two different evaluations exist, and they're worth keeping apart because they measure different
things with different rigor.

**The paper's own numbers** (arXiv 2512.24601) are the more citable evidence, because they're in a
reviewable artifact with a stated protocol: GPT-5 and Qwen3-Coder-480B-A35B-Instruct, compared
against RLM wrappers of themselves, across S-NIAH, OOLONG, OOLONG-Pairs, BrowseComp-Plus, and CodeQA.

The cleanest figures to quote are the ones in the abstract, because they are stated exactly rather
than read off a chart, and because they are relative to the right baselines. Wrapping GPT-5 in an
RLM beats — by a median across the evaluated benchmarks — **26% against compaction, 130% against
CodeAct with sub-calls, and 13% against Claude Code**, "while having comparable cost." That middle
number is the one that matters most for the argument here: CodeAct *also* gives the model code
execution and sub-calls. The gap between it and an RLM isn't code-versus-schema, it's whether the
input lives outside the model's context or inside it. The paper also claims inputs "up to two orders
of magnitude beyond model context windows."

And one contribution the write-ups mostly skip: they didn't only wrap existing models, they
**post-trained one for the paradigm**. RLM-Qwen3-8B beats plain Qwen3-8B by 28.3% on average and,
per the abstract, "approaches the quality of vanilla GPT-5 on three long-context tasks." An 8B model
approaching a frontier model on long-context work by being trained to drive a REPL rather than to
read further is the most interesting claim in the paper, and the one I'd most want replicated.
The headline figure scales input length from roughly 8K to over 1M tokens on S-NIAH, OOLONG, and
OOLONG-Pairs: GPT-5 degrades sharply as input grows, especially past its own 272K-token window where
it structurally cannot see the rest of the input at all, while RLM(GPT-5, depth=1) stays roughly flat
across the same range. In the paper's tables — read from its figures, so treat the exact decimal as
approximate rather than a number I recomputed myself — GPT-5 alone scores around 44% on OOLONG versus
roughly 56&ndash;58% for the RLM wrapper at recursion depths 1 and 3; on BrowseComp-Plus (1,000
documents, 6&ndash;11M tokens total) GPT-5 alone scores 0% because the input doesn't fit at all,
versus roughly 91&ndash;92% for RLM(GPT-5); on CodeQA, GPT-5 scores around 24% versus roughly
62&ndash;66% for the RLM wrapper. These are real, single-paper, not-yet-independently-replicated
numbers — but they come with a stated model, a stated task, and a stated context length, which is
more than most of what gets cited as evidence for an agent architecture.

**Prime Intellect's own post** runs a separate, smaller evaluation: GPT-5-mini through their
`RLMEnv` implementation, across four `verifiers` environments — DeepDive (web research), Math-python,
Oolong, and Verbatim-copy — at 50 rollouts each. This is where the honesty has to cut both ways. RLM
helps on DeepDive (with explicit strategy tips pushing sub-LLM calls further), helps on Oolong (the
plain LLM gets close to zero reward on the longest real-data contexts; RLM keeps working out to
roughly 1.5M characters), and helps on Verbatim-copy across most content types. On Math-python it
does not: the post reports RLM performing *worse* than the plain LLM, and ablating the REPL's timeout
up to 600 seconds doesn't close the gap. That's a genuine negative result from the people building
the thing, reported plainly rather than left out — a useful data point on where "run more code" isn't
automatically the right move for a task that's mostly reasoning, not search. Charts, not tables: the
post shows relative comparisons rather than a numeric results table, and says so itself — "this is
not a measurement of any model's absolute performance on any benchmark."

<Callout type="note">
A day before this piece, Prime Intellect published a separate launch post for Prime Agent reporting
Opus&nbsp;5 scoring 95.5% Best@1 on ARC-AGI-3 (183 test levels), against a self-reported 30.2%
baseline for Opus&nbsp;5 on its own harness, and a table comparing Prime Agent across models against
Pi-mono, Claude Code, and Codex on OOLONG, OBLIQ-Bench, and LongBenchv2. That's Prime Intellect's own
first-party number, for the product, not in the open-source repository — consistent with what
[Prime Agent](/articles/prime-agent) already found reading the repo itself: no benchmark numbers
ship in the code, only in the marketing post. It belongs in a product-level discussion, not this one;
I mention it only so this piece doesn't read as unaware of it.
</Callout>

## What this costs

None of the above is free, and the paper and the Prime Agent docs are both honest about the price.

**Arbitrary code execution is the primary interface, not a fallback.** A fixed tool schema gives you
an enumerable, individually-auditable set of actions. A REPL's action space is "anything Python (and
a shell cell) can do." Prime Agent's own trust-model documentation says this plainly: the kernel "is
a durable control environment, not a security sandbox." Zhang's design narrows the blast radius
somewhat by keeping the root model restricted to prompt metadata until it asks for more — but the
sub-LM calls and any tool access still execute inside the same interpreter.

**A stateful interpreter can wedge.** Persistent state across turns is the entire point of the
design, but it means a hung cell doesn't just time out cleanly the way a stuck tool call does — Prime
Agent's own busy-kernel handling frames the choice as "wait" (preserve state, keep waiting
indefinitely) or "kill" (lose every in-memory variable and restart clean). There's no option that
gets you both a responsive kernel and the state back.

**Recursion needs an enforced budget, not a polite one.** The recursion tree above has a real cap
behind it: `RLM_MAX_DEPTH` defaults to 1, and the check —

```typescript
// packages/coding-agent/src/core/agent-session.ts
if (this._rlmDepth >= this._rlmMaxDepth) {
  throw new Error(
    `RLM recursion depth limit reached (RLM_DEPTH=${this._rlmDepth}, RLM_MAX_DEPTH=${this._rlmMaxDepth})`,
  );
}
```

— runs in the host before a comm channel even opens, not as a prompt instruction the model could
argue its way past. That matters because the cost of recursion is exponential in depth if fan-out
is uncapped: `fanout^depth` sessions, each billed and each capable of spawning more, versus a `for`
loop's cost growing linearly in the number of iterations. A depth cap enforced in code is the
difference between "a program that does 200 things" and "a program that can, in principle, spawn
without bound."

**Harder to sandbox and audit than a fixed schema.** Every action a schema-based agent can take is
one of N defined functions — individually reviewable, individually denyable. "Any code the model
writes" is a much larger surface for a malicious skill, a compromised MCP integration, or a bad
instruction to abuse, and it's a correspondingly harder surface to review after the fact.

**Debugging shifts from reading a transcript to debugging a program.** A schema-based agent's failure
mode is usually legible from the transcript alone: read the tool calls and results in order. An RLM's
failure mode can be a bug in generated code, a REPL state that's subtly wrong three cells after the
mistake that caused it, or a child session that never replies because nothing in the parent's code
checks for it. That's a different, and for most engineers a more familiar, debugging discipline — but
it is a different one, and treating it like transcript-reading will miss real bugs.

## Where this sits

RLM is an inversion of the model [Lilian Weng's harness framing](/articles/agent-harness) and [the
harness effect](/articles/harness-effect) both describe: a fixed loop around a fixed tool schema,
where the transcript is the only place state can live. RLM doesn't optimize that loop's token
economics — it removes the transcript as the place state lives at all, replacing it with an
interpreter.

It's also a different axis from two other pieces published alongside it. [Recursive Harness
Self-Improvement](/articles/recursive-harness-self-improvement) treats the harness as a single text
prompt and improves it by comparing it against its own immediately-previous version — harness as a
string being optimized. RLM treats the harness as a program the model writes fresh each turn —
harness (or at least the working state) as code being executed. And the Continual Harness
[paper](https://arxiv.org/abs/2605.09998) — *Continual Harness: Online Adaptation for Self-Improving
Foundation Agents*, by Seth Karten, Joel Zhang, Tersoo Upaa Jr, Ruirong Feng, Wenzhe Li, Chengshuai
Shi, Chi Jin and Kiran Vodrahalli — is a third axis again: an online loop that alternates acting with
refining the agent's *own* prompts, skills, memory, and subagent specs *during* a run, without
resetting. (Its Zhang is Joel Zhang, not RLM's Alex L. Zhang — same surname, different author.)
Prime Agent's own Continual Harness feature, covered in [Prime Agent](/articles/prime-agent), draws
on this and composes with its RLM runtime. The connection is more than citational: the paper's lead
author, Seth Karten, is an active committer to the prime-agent repository, with 39 commits in its
history as of 2026-08-06. So the two ideas arrive in the same product from the same people — but they
answer different questions. RLM is about how one task executes; Continual Harness is about how the
harness's own configuration evolves across tasks.

[MemHarness](/articles/memharness) is a useful contrast in the opposite direction from RLM's whole
bet. MemHarness's argument is that a retrieved memory should be reconstructed — critiqued and
rewritten — against the current state every time it's used, because stale verbatim replay can hurt
more than no memory at all. RLM doesn't reconstruct anything: the corpus sits in a variable exactly
as it was written, and the model's job is to write code that finds the right slice of it, not to
have that slice handed to it pre-digested. Reconstruction spends compute making memory trustworthy
before use; RLM spends compute letting the model decide what's worth looking at, each time, from an
unmodified source. Different failure modes follow from each: a MemHarness-style system can
misreconstruct; an RLM-style system can simply fail to look at the part that mattered.

The idea itself is Zhang, Kraska, and Khattab's, credited honestly by the company building on it.
What Prime Intellect has actually built is two separate systems that take the same two mechanisms —
context as a variable, recursion as a call — and apply them at different scopes: one faithful to the
original single-prompt design, one generalized into an entire agent. Both are real, checkable
architecture decisions. The paper's numbers are the most rigorous evidence either has; Prime
Intellect's own eval is smaller, honestly mixed, and shown as charts rather than a table; and the
product-level ARC-AGI-3 number belongs to a different piece than this one.
