Agent0 — Tool-integrated curriculum co-evolution¶
Curriculum/executor co-evolution. A Curriculum agent writes tasks at the Executor's frontier and rewards tool use; the Executor answers them stop-and-go through a sandboxed calculator. Runs through the shared
MethodPolicyrunner. Example:examples/agent0/agent0_tool_curriculum.py.
| Paper | Agent0: Unleashing Self-Evolving Agents from Zero Data via Tool-Integrated Reasoning — 2025 (arXiv:2511.16043) |
| Upstream code | aiming-lab/Agent0@f775b510 |
| Example | examples/agent0/agent0_tool_curriculum.py |
| Domain | self-generated cart arithmetic with a sandboxed calculator — 16 self-play slots + 16/16 frozen evaluation carts |
| Layer | L1 (blast_radius=0.6, set by the shared runner) |
| Fidelity | inference_analogue — what the classes mean |
This port is measured in the runtime matrix: the mechanism is preserved and measured under AgentDescent's runtimes; it is not a paper-benchmark reproduction.
The mechanism¶
A Curriculum agent and an Executor agent, both from the same base model,
co-evolve in alternating iterations. The curriculum reward combines
uncertainty 1−2|p̂−0.5| (executor self-consistency near 50%), a
tool-use bonus min(N_tool, C), a BLEU repetition penalty, and a format
gate. The executor trains with ADPO (ambiguity-scaled advantages) on
majority-vote pseudo-labels, rolling out multi-turn with a sandboxed Python
interpreter in stop-and-go fashion.
Where each piece lives¶
| Upstream mechanism | Where it lives here |
|---|---|
| Stop-and-go tool rollouts | request → AST-gated calculator → continue, on both training and frozen evaluation paths |
| Uncertainty + tool-use reward | both components surfaced in the curriculum update prompt |
| Frontier curriculum | DifficultyWeighted — the same curve as 1−2|p̂−0.5| |
Boundaries¶
- Verbal policy memory replaces ADPO post-training.
- One calculator tool replaces the Python interpreter; no repetition penalty.
Measured results — self-play carts¶
Three seeds, async_pipeline, 80 rollouts each, 8 workers, --staleness full,
reflective merge on (this method's own declaration), four executor samples per
generated task, deepseek-v4-flash at temperature 0.7. Recorded in
bench/results/agent0-tool-curriculum.json.
| seed | test quality | validation | accepted | invalid | calls |
|---|---|---|---|---|---|
| 0 | 0.000 → 0.500 | 0.000 → 0.438 | 2/80 | 0 | 1616 |
| 1 | 0.125 → 0.625 | 0.000 → 0.500 | 2/80 | 0 | 1584 |
| 2 | 0.000 → 0.500 | 0.000 → 0.688 | 2/80 | 0 | 1678 |
All three seeds moved, by exactly +0.500 each — the largest gain of the three inference analogues (Absolute Zero +0.313, R-Zero +0.230) — and the most expensive, at ~1600 calls per seed against their ~620 and ~940. Four executor samples times two tool turns is eight model calls per training rollout.
Read the gain: the carts are generated, so the baseline is not 0.000 and the ceiling is not 1.000. See the caveat on PromptBreeder on one run per seed.
Both reward components are numbers in the prompt, not adjectives
curriculum_reward.py is
(min(p, 1−p) if question else −1) − penalty + calculate_tool_reward(...).
Uncertainty is over the executor's self-consistency — max_count /
len(results), the same computation R-Zero uses — so the
executor is sampled four times per generated task and the majority share
is what the curriculum sees. A single rollout's grounded reward would be 0 or
1, where 1 − 2|p − 0.5| is zero at both ends and the signal does not exist.
Upstream writes min(p, 1−p); 1 − 2|p − 0.5| is exactly twice it, and
DifficultyWeighted's 4p(1−p) shares its peak and zeros with either.
The tool bonus is min(tool_call_count, 4) × 0.05, and the update prompt
reports R_tool with the call count that produced it — a prompt that says
"with a tool-use bonus" and carries no value is not surfacing the component.
Run it¶
python -m examples.agent0.agent0_tool_curriculum --dry-run
# one seed of the three above
python -m examples.agent0.agent0_tool_curriculum --yes --seed 0 \
--provider openai --model deepseek-v4-flash \
--async --async-ratio 1 --workers 8 --budget-rollouts 80 --staleness full \
--temperature 0.7 --max-seconds 3600
--async-ratio 1 is what this row ran at: the flag was dropped before it
reached the runtime, so the run took the runner's default whatever the
command line said. It is passed explicitly here because the value matters and
a default can move.
Flags: the MethodPolicy command line.
Offline tests: tests/test_selfplay_upstream.py,
tests/test_candidate_methods.py.