Our first agent stored every turn as rows in Postgres and replayed the whole history to the model on the next turn. Message rows go back word for word. Nothing sits between what a tool returned and what the model reads.
That design has a price, and the price compounds. Fetch 200 emails on turn one and those 200 emails re-enter the context window on turn two, turn three, and every turn after, at full cost. Any task with real data in it gets punished for having real data in it. "Go through my last 200 emails and tell me which ones need a reply" is either two hundred round trips or nothing at all.
The pattern
Recursive Language Models are the fix: give the model a REPL and let the data live there. The model writes code. The code holds the result in a variable. Only what the code prints comes back. The model sets its own context budget, one print at a time.
emails = run_action("gmail", "list_messages", max_results=200) # stays in the box
urgent = [e for e in emails if "IMPORTANT" in e["labels"]]
print(len(urgent), urgent[0]["subject"]) # this line enters context
Three lines of context for two hundred emails. The messages themselves never touch the window. Turn two can query the same variable, because the process is still running.
What we built
The whole tier is about eighty lines of Python in the box, agent_box/repl.py, and it does two things.
It runs code in a namespace that outlives the call. Each conversation gets a dictionary. exec runs against it with stdout and stderr captured. A variable set in one call is there in the next one, next week. Exceptions are caught and the traceback goes back as output.
Output is capped at twenty thousand characters, keeping the first thousand and the last nineteen thousand with a note about what was dropped in between. The cap matters more than it looks. A REPL is only a context saving device while the agent is disciplined about printing, and print(emails) is one keystroke away from undoing the entire point. We want the agent to have just enough information to complete the task given a prompt directive.
It seeds the namespace with the connector helpers. run_action, search_actions, and get_prop_options are ordinary Python functions inside the REPL. They post to the same Rails proxy that the model-facing tools use, carrying the same scoped run token, so credentials stay in Rails exactly as before.
def run_action(connector_key, action_key, **inputs):
return _proxy("run_action", {...})
Without this, a connector's data arrives as a tool result, which means it arrives in the context window, which is the thing we were avoiding. With seeding, the agent chooses. Small answer: call the tool. Large answer: call it from code and keep it in a variable.
Persistence comes free
E2B snapshots RAM as well as disk when a sandbox pauses. The daemon holding those namespaces is an ordinary long-lived process, so it survives the pause with its variables intact and comes back in about 280 milliseconds. We did not build the persistence. We picked a sandbox that had it and left the process running.
What it did not fix
Then we tested it, and the result was a flat null.
We wrote a working directive for our benchmark harness telling the agent to keep its requirements and its gathered data in the REPL, compute the answer there rather than reasoning informally, and verify against it before finishing. On the task we most wanted to move, an exclusion task where the agent reliably over-includes, it called run_python 368 times and scored 0.500, exactly what it scored without the directive. It enrolled the right six of six. It caught five of ten it should have excluded, the same five as always.
A separate directive telling it to gather more thoroughly came back null too. It read the linked account records it had been skipping, and then over-included anyway.
The reading is clean enough to be useful. The REPL removes a cost ceiling. It does not raise a reasoning ceiling. Cheap context makes hard tasks affordable to attempt, and gives the agent no more skill at attempting them. Those are separate problems, and it saves a lot of prompt iteration to know which one you are looking at.
Worth building anyway. The 200 email task went from impossible to routine, and that is what the tier was for.