Skip to content

Commit d429cc2

Browse files
rewrite the Introduction, and split Quick Start into inference and RSI
The Introduction described the repository layout rather than the work. Replaced with what Recuris is, four key features, and one line on what it delivers. Quick Start was a single tau2 pair on the reference model, which is not what most readers arrive wanting. Now two parts, because they are different jobs: Part 1 inference with a memory we already evolved -- Qwen3.6-27B and a 4B model on tau2-Bench, Qwen3.6-27B on SkillFlow, GPT/Claude on tau2-Bench Part 2 evolving a new memory -- Doubao upstream and downstream, then the same upstream driving an open-weight GPT-OSS-20B downstream Every command was checked against the shipped parsers rather than written from memory. The 4B section says plainly that a ~3-4B agent hits a capability wall on SkillFlow while still gaining on tau2-Retail, so a reader does not read a floored bare arm as a result. Explicit anchors for the Quick Start sections: GitHub's slugifier and mine disagree about headings containing the tau2 glyph, and a silently dead link is worse than a visible one.
1 parent e11dfb7 commit d429cc2

1 file changed

Lines changed: 252 additions & 34 deletions

File tree

README.md

Lines changed: 252 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -33,34 +33,34 @@ Recursive Experiential–Working Memory Evolution for Long-Horizon Agent Harness
3333

3434
## 💡 Introduction
3535

36-
An agent that fails a task the same way twice has a **memory** problem, not a
37-
capability problem. **Recuris** separates the two: a **frozen machine** that
38-
never changes between arms, and an **evolved memory** `M = (E, W, ρ, C)` that
39-
does. A meta-agent proposes changes to the memory; a deterministic gate decides
40-
whether they are kept, using paired held-out evidence and nothing else.
41-
42-
That split is physical in this repository. `src/recuris/` is the machine.
43-
`skill_memories/` is the memory, and it is **the only thing that differs**
44-
between a bare arm and a skill arm.
36+
**Recuris** is a recursive self-improvement framework that **improves a
37+
long-horizon agent by evolving its memory rather than its weights or its
38+
prompt**. A frozen agent is paired with a **Skill Memory** `M = (E, W, ρ, C)`;
39+
a meta-agent reads structured execution traces, localises each failure to one
40+
component of that memory, and patches only that component — and a deterministic
41+
validation gate decides on paired held-out evidence whether the patch survives.
42+
Recuris has the following key features:
43+
44+
- **State-grounded memory use** — working memory drives skill invocation, so
45+
retrieval is conditioned on verified task state instead of a chat history that
46+
grows until the state is buried.
47+
- **Targeted memory evolution** — structured trajectories `(w_t, E_t, a_t, o_t)`
48+
attribute a failure to a specific component, instead of nudging a monolithic
49+
prompt from outcomes alone.
50+
- **Bounded by a validation gate** — candidates are admitted by paired held-out
51+
arithmetic and nothing else. No model votes on its own patch, so a round that
52+
accepts nothing is a valid outcome.
53+
- **Training-free and model-agnostic** — the downstream agent stays frozen, and
54+
a memory evolved on one model transfers to others unchanged.
55+
56+
Overall, Recuris delivers **higher task success**, **larger gains as the horizon
57+
grows**, and **substantially fewer long-horizon failures**, across both frontier
58+
and open-weight agents.
4559

4660
<p align="center">
4761
<img src="assets/motivation.png" width="1000">
4862
</p>
4963

50-
Prior memory-based harnesses use memory **statically** — retrieval competes with
51-
a chat history that grows until the task state is buried — and evolve it from
52-
**outcomes only**, which says a trajectory failed but not *which part of memory*
53-
was responsible. Recuris changes both ends:
54-
55-
- **Working Memory drives invocation.** `W` tracks what the task still needs and
56-
selects from Experiential Memory on that basis, so skill retrieval is grounded
57-
in verified current state rather than in a growing transcript.
58-
- **Evolution reads structured trajectories.** Each episode leaves
59-
`(w_t, E_t, a_t, o_t)`, which lets a failure be attributed to one component and
60-
patched *there* — instead of nudging a monolithic prompt and hoping.
61-
- **A gate, not a judge, decides.** Candidates are admitted only on paired
62-
held-out evidence. Nothing in `gates.py` consults a model.
63-
6464
> Across four long-horizon benchmarks and ten models, Recuris improves task
6565
> success in **35 of 37** completed model–benchmark pairs, adding **+17.8** to
6666
> GPT-5.6 Sol and **+15.6** to Claude Opus 5 on τ²-Bench — taking Opus 5 to
@@ -187,29 +187,241 @@ error.
187187

188188
## 🚀 Quick Start
189189

190-
Fetch τ²-Bench, then run a paired comparison:
190+
There are two things to do with this repository, and they are different jobs:
191+
192+
| | |
193+
|---|---|
194+
| **[Part 1 — Inference](#part1)** | take a Skill Memory we already evolved, load it into a frozen agent, and measure what it buys |
195+
| **[Part 2 — Recursive Self-Improvement](#part2)** | evolve a *new* Skill Memory for your own model, one gated round at a time |
196+
197+
Every example below is a **pair**: a skill arm and a bare control that differ in
198+
the flags shown and in nothing else. Run both, or the number means nothing.
199+
[Running Experiments](#-running-experiments) has the full flag reference, the
200+
costs, and the caveats.
201+
202+
---
203+
204+
<a name="part1"></a>
205+
206+
### Part 1 — Inference with a provided Skill Memory
207+
208+
<br/>
209+
210+
#### 1a. Open-weight on τ²-Bench — Qwen3.6-27B
211+
212+
Serve the model on any OpenAI-compatible endpoint, then unfreeze **only** the
213+
downstream agent — the user simulator and the assertion judge stay pinned, which
214+
is what keeps the comparison single-variable:
191215

192216
```bash
193-
bash third_party/tau2/setup.sh
194-
uv pip install -e external/tau2-bench
195-
recuris check-data --benchmark tau2
217+
bash third_party/tau2/setup.sh && uv pip install -e external/tau2-bench
218+
219+
vllm serve Qwen/Qwen3.6-27B --port 8000 --served-model-name qwen3.6-27b
196220
```
197221

198222
```bash
223+
export TAU2_GATE_TERM=1 TAU2_GATE_TERM_WM=1 TAU2_STATUS_BOARD=1
224+
225+
export SERVED=openai/qwen3.6-27b
226+
export ARGS='{"api_base":"http://127.0.0.1:8000/v1","api_key":"dummy","temperature":0.0,"timeout":360,"num_retries":2,"extra_body":{"chat_template_kwargs":{"enable_thinking":false}}}'
227+
199228
# skill arm
200229
recuris tau2 --domain retail --agent recuris_agent --skill-memory tau2_retail \
201-
--num-trials 4 --max-concurrency 4 --save-to retail_skill
230+
--open-downstream --agent-llm "$SERVED" --agent-llm-args "$ARGS" \
231+
--num-trials 4 --max-concurrency 4 --save-to retail_qwen27b_skill
202232

203-
# bare control — the same command, two flags different
233+
# bare control — same endpoint, same decoding, no memory
204234
recuris tau2 --domain retail --agent llm_agent \
205-
--num-trials 4 --max-concurrency 4 --save-to retail_bare
235+
--open-downstream --agent-llm "$SERVED" --agent-llm-args "$ARGS" \
236+
--num-trials 4 --max-concurrency 4 --save-to retail_qwen27b_bare
206237

207-
recuris compare --a retail_skill --b retail_bare
238+
recuris compare --a retail_qwen27b_skill --b retail_qwen27b_bare
239+
```
240+
241+
Airline is the same with `--domain airline --skill-memory tau2_airline`.
242+
243+
`--agent-llm-args` must be **byte-identical between the two arms**. It is
244+
validated rather than merged, so a misspelled key is an error instead of a
245+
silent drop — setting it once in a shell variable is the reliable way. The
246+
`enable_thinking: false` in `extra_body` is Qwen-specific; drop it for a server
247+
that does not take it.
248+
249+
#### 1b. A small open-weight model — Qwen3.6-4B
250+
251+
Identical, with a different served model:
252+
253+
```bash
254+
vllm serve Qwen/Qwen3.6-4B --port 8000 --served-model-name qwen3.6-4b
255+
256+
export SERVED=openai/qwen3.6-4b
257+
# ... same $ARGS, same two commands, --save-to retail_qwen4b_{skill,bare}
258+
```
259+
260+
Worth calibrating expectations at this size. In our measurements a ~3–4B agent
261+
still gains on τ²-Retail — Granite-4.1-3B goes 9.7 → 23.0 (**+13.4**) — but on
262+
SkillFlow the same class of model scores near zero in **both** arms, which is a
263+
capability wall, not a memory effect. A Skill Memory can only recover failures
264+
the model is otherwise capable of avoiding. Always read the bare arm first: if
265+
it is at the floor, the pair has nothing to say.
266+
267+
#### 1c. Open-weight on SkillFlow — Qwen3.6-27B
268+
269+
```bash
270+
uv sync --extra skillflow && pip install huggingface_hub
271+
bash third_party/skillflow/setup.sh
272+
./external/SkillFlow/docker/harbor-cli-base/build.sh
273+
python external/SkillFlow/utils/prebuild_task_images.py \
274+
--tasks-root external/SkillFlow/test_tasks
275+
```
276+
277+
```bash
278+
export SERVED=openai/qwen3.6-27b
279+
export SERVED_BASE=http://127.0.0.1:8000/v1
280+
281+
recuris skillflow render-configs --arm bare \
282+
--model "$SERVED" --base-url "$SERVED_BASE" \
283+
--out configs/skillflow/generated
284+
285+
recuris skillflow render-configs --arm skill --routing default \
286+
--model "$SERVED" --base-url "$SERVED_BASE" \
287+
--skill-memory skillflow --out configs/skillflow/generated
288+
289+
# one job at a time -- concurrent harbor jobs exhaust the Docker IPv4 pool
290+
for cfg in configs/skillflow/generated/bare_*.yaml; do harbor run -c "$cfg" --yes; done
291+
for cfg in configs/skillflow/generated/skill_*.yaml; do harbor run -c "$cfg" --yes; done
292+
293+
recuris skillflow score --bare jobs/bare --skill jobs/skill
208294
```
209295

210-
The two commands differ in `--agent` and `--skill-memory`, and **in nothing
211-
else**. That is the design: the machine is identical, the memory is the
212-
variable.
296+
Use `--routing default` for any model. `--routing frozen_insample` additionally
297+
applies six per-family overrides that were chosen by reading those families' own
298+
scores; it reproduces our reported arm, and it is in-sample.
299+
300+
#### 1d. Frontier models on τ²-Bench — GPT / Claude
301+
302+
Same shape, pointed at a provider endpoint. Frontier arms in the paper ran with
303+
the three treatment switches **off**:
304+
305+
```bash
306+
unset TAU2_GATE_TERM TAU2_GATE_TERM_WM TAU2_STATUS_BOARD
307+
308+
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"]}'
309+
310+
export MODEL=openai/<provider-model>
311+
312+
recuris tau2 --domain retail --agent recuris_agent --skill-memory tau2_retail \
313+
--open-downstream --agent-llm "$MODEL" --agent-llm-args "$ARGS" \
314+
--num-trials 4 --max-concurrency 4 --save-to retail_frontier_skill
315+
316+
recuris tau2 --domain retail --agent llm_agent \
317+
--open-downstream --agent-llm "$MODEL" --agent-llm-args "$ARGS" \
318+
--num-trials 4 --max-concurrency 4 --save-to retail_frontier_bare
319+
320+
recuris compare --a retail_frontier_skill --b retail_frontier_bare
321+
```
322+
323+
Replace `<provider-model>` with the provider's id for GPT or Claude as your
324+
gateway spells it. Anything the provider requires goes in `$ARGS` and must be
325+
named — unknown keys are rejected, not dropped.
326+
327+
---
328+
329+
<a name="part2"></a>
330+
331+
### Part 2 — Recursive Self-Improvement: evolve a Skill Memory
332+
333+
This is the loop the paper is about. A meta-agent (**upstream**) reads failed
334+
trajectories from the agent being improved (**downstream**), patches one memory
335+
component, and a gate admits the patch only on paired held-out evidence.
336+
337+
```bash
338+
uv sync --extra metaagent
339+
npm install -g @anthropic-ai/claude-code
340+
```
341+
342+
```bash
343+
RECURIS_META_MODEL=... # the upstream meta-agent's model
344+
RECURIS_META_BASE_URL=...
345+
RECURIS_META_API_KEY=...
346+
```
347+
348+
```bash
349+
# fails on the plumbing before you spend benchmark budget: one scoped
350+
# session, zero simulations
351+
recuris metaagent qualify --run-id qsmoke --proxy-port 4047
352+
```
353+
354+
<br/>
355+
356+
#### 2a. τ²-Bench with Doubao upstream and downstream
357+
358+
The reported configuration. `--worker-model` is the downstream agent being
359+
improved, `--meta-model` is the upstream meta-agent, and `--simulator-model` is
360+
the τ² user simulator, which stays frozen in every arm:
361+
362+
```bash
363+
recuris metaagent run --domain retail --run-id retail_doubao_v1 \
364+
--splits splits/tau2/retail_from0_v1_k4.json \
365+
--rounds 4 --k 4 --arm autonomous --base neutral \
366+
--meta-model doubao-seed-2-1-pro-260628 \
367+
--worker-model doubao-seed-2-0-pro-260215 \
368+
--simulator-model doubao-seed-2-0-pro-260215 \
369+
--round-gate progressive --power-gate warn --reg-cap 1 \
370+
--meta-workflow hierarchical --diagnosis-workers 3 \
371+
--max-concurrency 6 --max-sims 1400 --proxy-port 4047
372+
```
373+
374+
Those three model flags are the defaults, so they can be omitted; they are
375+
spelled out here because which model plays which role is the thing readers most
376+
often get backwards. `--base neutral` starts from a deterministic seed package,
377+
so no hand-written domain profile enters the loop.
378+
379+
<a name="part2b"></a>
380+
381+
#### 2b. τ²-Retail with an open-weight downstream — GPT-OSS-20B
382+
383+
Same upstream, different agent being improved. `--open-worker` unfreezes the
384+
downstream only; the simulator stays on the reference model, so rounds stay
385+
comparable to each other and to every arm in Part 1:
386+
387+
```bash
388+
vllm serve openai/gpt-oss-20b --port 8000 --served-model-name gpt-oss-20b
389+
```
390+
391+
```bash
392+
recuris metaagent run --domain retail --run-id retail_gptoss_v1 \
393+
--splits splits/tau2/retail_from0_v1_k4.json \
394+
--rounds 4 --k 4 --arm autonomous --base neutral \
395+
--meta-model doubao-seed-2-1-pro-260628 \
396+
--open-worker --worker-model openai/gpt-oss-20b \
397+
--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]}' \
398+
--simulator-model doubao-seed-2-0-pro-260215 \
399+
--round-gate progressive --power-gate warn --reg-cap 1 \
400+
--meta-workflow hierarchical --diagnosis-workers 3 \
401+
--max-concurrency 6 --max-sims 1400 --proxy-port 4047
402+
```
403+
404+
`--worker-llm-args` takes `api_base`, `temperature` (0.0), `timeout` (360) and
405+
`num_retries` (2) as required keys, plus optional `api_key`, `extra_body`,
406+
`stop_token_ids`, `max_tokens`, `reasoning_effort` and `allowed_openai_params`.
407+
It is checked by the same validator the standalone `--open-downstream` arm uses,
408+
so a campaign arm and a hand-launched arm are admitted on identical terms. The
409+
`stop_token_ids` above are GPT-OSS's harmony stop tokens: without them vLLM does
410+
not end the turn on a tool call, and the agent looks incapable when it is only
411+
mis-served.
412+
413+
Evolving *for* a specific downstream is worth doing rather than reusing a
414+
package evolved elsewhere. On GPT-OSS-20B a rebuilt package gained **+10.2**
415+
where the general-purpose package transferred *negatively*.
416+
417+
> 💸 **Cost.** A campaign is days of wall-clock and thousands of model calls —
418+
> our retail campaign at k=4 spent roughly eleven hours on the first two rounds
419+
> alone. Start with `qualify`, then a 1-round run, before committing budget.
420+
421+
Each round leaves a complete record: the evidence the session was given, the
422+
plan it produced, the lint and probe verdicts, the gate arithmetic, and the
423+
ledger entry. **A round that accepts nothing is a result** — the gate rejecting
424+
everything is the gate working.
213425

214426
## 🧪 Running Experiments
215427

@@ -415,6 +627,12 @@ domain profile enters the loop. The split file states its own selection rule,
415627
including which tasks were excluded and why, which is the only credible evidence
416628
the held-out set was fixed in advance.
417629

630+
To evolve a memory *for* an open-weight downstream, add `--open-worker` with
631+
`--worker-model openai/<served-name>` and `--worker-llm-args`; see
632+
[Part 2b](#part2b). The
633+
upstream meta-agent and the user simulator stay where they are — only the agent
634+
being improved moves.
635+
418636
Each round leaves a complete record under the run directory: the evidence the
419637
session was given, the plan it produced, the lint and probe verdicts, the gate
420638
arithmetic, and the ledger entry. **A round that accepts nothing is a result**

0 commit comments

Comments
 (0)