The project's README from github.com/Gen-Verse/Recuris. As one markdown file: README.md.
National University of Singapore · Stanford University · University of Oxford · Princeton University
Recuris is a recursive self-improvement framework that improves a
long-horizon agent by evolving its memory instead of its weights or its
prompt. A frozen agent is paired with a Skill Memory M = (E, W, ρ, C).
A meta-agent reads structured execution traces, locates each failure in one
component of that memory, and patches only that component. A deterministic
validation gate then decides, on paired held-out evidence, whether the patch
survives. Recuris has the following key features:
(w_t, E_t, a_t, o_t)
locate a failure in a specific component, instead of nudging a monolithic
prompt from outcomes alone.Overall, Recuris achieves higher task success, larger gains on longer horizons, and substantially fewer long-horizon failures, on both frontier and open-source agents.
Task success (avg@4, %), each model run with the benchmark's own reference
agent alone and with that same agent plus Recuris. Bold marks the better of
each pair, the subscript is Δ, † marks a paired task-clustered bootstrap 95% CI
excluding zero, and n/a means the model was not run on that benchmark.
| Model | Cross-task evolution | Within-task adaptation | ||
|---|---|---|---|---|
| τ²-Retail | τ²-Airline | SkillFlow | Terminal-Bench 2.1 | |
| Open-source models | ||||
| Granite-4.1-3B | 9.7 | 34.3 | 0.3 | 0.6 |
| + Recuris | 23.0 +13.4† | 39.8 +5.5 | 0.0 −0.3 | 3.1 +2.5 |
| Qwen3.5-4B | 68.0 | 75.3 | 6.0 | 10.1 |
| + Recuris | 68.3 +0.3 | 79.0 +3.8 | 7.1 +1.1 | 13.0 +2.9 |
| Qwen3.5-9B | 77.6 | 75.5 | 15.1 | 17.4 |
| + Recuris | 79.6 +2.0 | 78.4 +2.9 | 18.4 +3.4 | 20.5 +3.1 |
| GPT-OSS-20B | 50.6 | 54.8 | 7.8 | 3.9 |
| + Recuris | 60.8 +10.2† | 59.3 +4.5† | 10.4 +2.6† | 6.7 +2.8 |
| Qwen3.6-27B | 62.8 | 79.0 | 42.2 | 38.8 |
| + Recuris | 71.2 +8.3† | 80.0 +1.0 | 58.7 +16.6† | 42.1 +3.3 |
| Qwen3.6-35B | 78.2 | 80.3 | 35.3 | 33.1 |
| + Recuris | 78.5 +0.3 | 81.5 +1.3 | 48.8 +13.5† | 36.4 +3.3 |
| Frontier models | ||||
| Gemini 3.7 Flash | 73.5 | 86.5 | n/a | 79.8 |
| + Recuris | 78.3 +4.8 | 85.0 −1.5 | n/a | 82.4 +2.6 |
| GPT-5.6 Sol | 58.3 | 79.0 | n/a | 83.2 |
| + Recuris | 76.1 +17.8† | 86.0 +7.0† | n/a | 86.4 +3.2 |
| Claude Opus 5 | 72.4 | 89.5 | n/a | 84.6 |
| + Recuris | 87.9 +15.6† | 90.5 +1.0 | n/a | 88.4 +3.8 |
| Doubao-2.0-Pro (deployment) | 58.1 | 75.5 | 34.6 | 46.1 |
| + Recuris | 81.4 +23.3† | 80.5 +5.0 | 51.4 +16.8† | 48.9 +2.9 |
Recuris improves task success in 35 of the 37 completed model and benchmark pairs, from a 3B open-source agent up to the strongest frontier models. The largest gains reach +23.3 on τ²-Retail and +16.8 on SkillFlow. Gains grow with the interaction horizon, reaching +32.2 on the longest tasks, and common long-horizon failure modes drop by up to 80%.
This repository provides the code for running Recuris on τ²-Bench, SkillFlow and Terminal-Bench 2.1, the Skill Memory packages produced by the evolution loop, and the frozen evaluation splits.
Python 3.12 and git. SkillFlow and Terminal-Bench 2.1 also need Docker
with the Compose V2 plugin (docker compose version must work; harbor
shells out to it for every task).
git clone https://github.com/Gen-Verse/Recuris.git recuris
cd recuris
uv sync --extra all # or: pip install -e ".[all]"
Put your endpoint in a .env file at the repository root, or export it:
OPENAI_API_KEY=...
OPENAI_BASE_URL=...
Any OpenAI-compatible endpoint works. This is needed even when the agent itself is an open-source model, because τ²-Bench scores every episode with an LLM user simulator and an LLM assertion judge, and both stay pinned to a reference model.
Each benchmark is run twice, once with a Skill Memory loaded and once without. The two runs differ only in the flags shown below. Both are needed, because the number that matters is the difference between them.
Set up the benchmark:
bash third_party/tau2/setup.sh
uv pip install -e external/tau2-bench
recuris check-data --benchmark tau2
Install tau2 after uv sync, not before: uv sync resolves the environment to
exactly what pyproject.toml declares, so running it again removes anything
added with uv pip install.
Serve an open-source model locally. τ²-Bench drives the agent through tool calls, so the two tool-calling flags are required, not optional: without them vLLM rejects every request and every episode ends ungraded.
vllm serve <model-id> --port 8000 --served-model-name qwen3.6-27b \
--enable-auto-tool-choice --tool-call-parser hermes
hermes is the parser for Qwen; other families need their own (see vLLM's
tool-calling docs). A frontier model served by a provider needs none of this.
Now point the agent at it:
export TAU2_GATE_TERM=1 TAU2_GATE_TERM_WM=1 TAU2_STATUS_BOARD=1
# open-source example
export MODEL=openai/qwen3.6-27b
export ARGS='{"api_base":"http://127.0.0.1:8000/v1","api_key":"dummy","temperature":0.0,"timeout":360,"num_retries":2}'
# frontier example
# export MODEL=openai/<provider-model>
# export ARGS='{"api_base":"'"$OPENAI_BASE_URL"'","api_key":"'"$OPENAI_API_KEY"'","temperature":0.0,"timeout":360,"num_retries":2,"reasoning_effort":"high","allowed_openai_params":["reasoning_effort"]}'
Run both configurations and compare them:
# with Skill Memory
recuris tau2 --domain retail --agent recuris_agent --skill-memory tau2_retail \
--open-downstream --agent-llm "$MODEL" --agent-llm-args "$ARGS" \
--num-trials 4 --max-concurrency 4 --save-to retail_skill
# without
recuris tau2 --domain retail --agent llm_agent \
--open-downstream --agent-llm "$MODEL" --agent-llm-args "$ARGS" \
--num-trials 4 --max-concurrency 4 --save-to retail_bare
recuris compare --a retail_skill --b retail_bare
--domain is retail or airline. For airline, use
--skill-memory tau2_airline.--agent-llm-args must be identical in both runs. It is validated rather
than merged, so an unknown key raises an error instead of being dropped
silently.$MODEL and $ARGS. Nothing else changes.
Some servers need extras, for example
"extra_body":{"chat_template_kwargs":{"enable_thinking":false}} for Qwen.Set up the benchmark and build the task images once:
pip install huggingface_hub
bash third_party/skillflow/setup.sh
./external/SkillFlow/docker/harbor-cli-base/build.sh
python external/SkillFlow/utils/prebuild_task_images.py \
--tasks-root external/SkillFlow/test_tasks
Generate the configs for both runs, then execute them:
export MODEL=openai/qwen3.6-27b
export BASE=http://127.0.0.1:8000/v1
recuris skillflow render-configs --arm bare \
--model "$MODEL" --base-url "$BASE" --out configs/skillflow/generated
recuris skillflow render-configs --arm skill --routing default \
--model "$MODEL" --base-url "$BASE" \
--skill-memory skillflow --out configs/skillflow/generated
for cfg in configs/skillflow/generated/bare_*.yaml; do harbor run -c "$cfg" --yes; done
for cfg in configs/skillflow/generated/skill_*.yaml; do harbor run -c "$cfg" --yes; done
recuris skillflow score --bare jobs/bare --skill jobs/skill
--routing default is model-agnostic and is the right choice for new
work. --routing frozen_insample reproduces our reported numbers and applies
six per-family overrides that were chosen in-sample.bash third_party/tb21/setup.sh
recuris check-data --benchmark tb21
On this benchmark a task may be attempted several times in a row, and it stops
as soon as one attempt succeeds. --rounds sets how many attempts each task
gets. There are three configurations:
| Configuration | What the agent carries | After a failed attempt |
|---|---|---|
bare |
nothing, the stock agent | nothing changes, the next attempt starts over |
m0 |
a fixed Skill Memory, the seed package | nothing changes, the next attempt gets the same package |
tta |
the same package, as a per-task copy | the meta-agent reads the failed trajectory and writes a new card into that copy, which the next attempt carries |
# smoke test: one task, one attempt
recuris tta run --taskset splits/tb21/tta_taskset_v3.json \
--run-id smoke --arm m0 --limit 1 --rounds 1
# all three configurations, four attempts each
for cfg in bare m0 tta; do
recuris tta run --taskset splits/tb21/tta_taskset_v3.json \
--run-id demo --arm "$cfg" --rounds 4 --concurrency 3
done
--rounds. Comparing tta at four
attempts against bare at one mostly measures the extra attempts rather than
adaptation.m0 against bare isolates the value of having a Skill Memory at all. tta
against m0 isolates the value of updating it between attempts, since both
carry a package and both get the same number of attempts.This is the recursive loop. A meta-agent, the upstream model, reads failed trajectories from the agent being improved, the downstream model. It patches one component of the memory, and a gate admits the patch only on paired held-out evidence.
npm install -g @anthropic-ai/claude-code
RECURIS_META_MODEL=... # the upstream meta-agent's model
RECURIS_META_BASE_URL=...
RECURIS_META_API_KEY=...
# one scoped session, zero simulations: checks the plumbing first
recuris metaagent qualify --run-id qsmoke --proxy-port 4047
recuris metaagent run --domain retail --run-id retail_v1 \
--splits splits/tau2/retail_from0_v1_k4.json \
--rounds 4 --k 4 --arm autonomous --base neutral \
--round-gate progressive --power-gate warn --reg-cap 1 \
--meta-workflow hierarchical --diagnosis-workers 3 \
--max-concurrency 6 --max-sims 1400 --proxy-port 4047
To evolve a memory for an open-source downstream model, unfreeze the worker only. The user simulator stays pinned, so rounds remain comparable:
recuris metaagent run --domain retail --run-id retail_gptoss_v1 \
--splits splits/tau2/retail_from0_v1_k4.json \
--rounds 4 --k 4 --arm autonomous --base neutral \
--open-worker --worker-model openai/gpt-oss-20b \
--worker-llm-args '{"api_base":"http://127.0.0.1:8000/v1","api_key":"dummy","temperature":0.0,"timeout":360,"num_retries":2,"stop_token_ids":[200002,200012]}' \
--round-gate progressive --power-gate warn --reg-cap 1 \
--meta-workflow hierarchical --max-concurrency 6 --proxy-port 4047
--meta-model is the upstream meta-agent and --worker-model is the
downstream agent being improved. Both default to Doubao.--base neutral starts from a deterministic seed package, so no
hand-written domain profile enters the loop.qualify and then a single round. Each round writes a full record:
the evidence the session was given, the plan it produced, the gate arithmetic,
and the ledger entry. A round that admits nothing is a valid outcome.@article{yu2026recuris,
title = {Recursive Experiential--Working Memory Evolution for Long-Horizon Agent Harnesses},
author = {Yu, Zhaochen and Wu, Yingcheng and Yin, Zhenfei and Chen, Kaiyuan and Zhao, Zhe and Wang, Mengdi and Yan, Shuicheng and Yang, Ling},
journal = {arXiv preprint arXiv:2608.24876},
year = {2026}
}