Agent Elo¶
Agent Elo is URSA's evolutionary competition environment. Multiple agents work independently on the same task, compete in pairs, and receive Elo ratings based on the quality of their work. Selected losers leave the active population, while successful survivors produce descendants that inherit their research state.
The goal is to develop solutions over several generations. A descendant starts with its parent's files and persistent state, then works independently to address limitations or improve the inherited solution. Surviving founders also continue improving their existing work.
When Elo is the right shape¶
Use Agent Elo when you want to:
- explore several approaches to a task independently;
- compare solutions using explicit evidence and quality criteria;
- preserve successful work while replacing weaker competitors;
- develop and validate scientific or computational methods over repeated rounds;
- inspect how solutions and agent lineages evolve over time.
Use an Agent Team when a PI should delegate specialist subtasks and synthesize an answer. Use an Agent Symposium when you want peer review, revision, and an organizer's final report. Elo returns competition results and standings; it does not have an organizer that synthesizes one final answer.
What happens during a generation¶
Each generation follows this sequence:
- Independent work. Every active member receives the same task, its role, member-specific guidance, and instructions appropriate to its lineage. Members run asynchronously in their own workspaces.
- Pairing. The environment randomly pairs the active members. Each member participates in exactly one match.
- Judging. Completed and timed-out competitors are compared by an LLM judge, which chooses candidate A, candidate B, or a draw using the task’s evaluation criteria. Ordinary execution failures are handled before calling the judge.
- Rating updates. Match results update both competitors' Elo ratings.
- Selection and reproduction. Up to
deaths_per_rounddecisive losers are selected for elimination. The highest-rated survivors each produce one child to replace an eliminated member. All replacement children are prepared before the losers are removed. - Saving. The environment advances its generation counter and writes
environment_state.jsonfor restart.
A population must contain at least two members and have an even size. Names must be unique. The population size stays constant after each successful generation, although its members may change. Children first compete in the next generation.
Try the worked example¶
Follow Evolve numerical methods with Agent Elo for setup instructions, a complete YAML population, the Python runner, and a walkthrough of two generations. Its task asks competitors to implement and validate numerical integration, with evaluation criteria specified in the task.
One invocation runs the configured number of generations on the supplied task.
Invoking the same environment again runs that many additional generations using
its current population and ratings. Load YAML with
AgentEloEnvironment.from_yaml(path, llm=llm) and pass the task to env.invoke(...).
Inside an existing async event loop, use await env.ainvoke(...). Calling
env.invoke(...) from that context raises an error.
Programmatic construction¶
You can also create an environment without a YAML file:
from langchain.chat_models import init_chat_model
from ursa.environments import AgentEloEnvironment
llm = init_chat_model(model="openai:gpt-4o-mini")
env = AgentEloEnvironment(
llm=llm,
name="method_comparison",
workspace="./method-comparison-workspace",
generations=3,
deaths_per_round=1,
seed=42,
members=[
{
"name": "adaptive_method",
"role": "Develops an adaptive numerical method",
"agent": "ExecutionAgent",
},
{
"name": "reference_method",
"role": "Develops a simple, well-validated reference method",
"agent": "ExecutionAgent",
},
],
)
Configuration validation¶
AgentEloConfig is a frozen Pydantic model. YAML loading, direct Python
construction, and dashboard validation use the same Elo configuration rules.
Constructor overrides are merged before validation; an override of None keeps
the configured value, while values such as deaths_per_round=0 take effect.
Invalid settings raise a validation error before members are built. Counts must be integers, ratings and timeouts must be finite, and unknown configuration fields are rejected. Numeric strings are converted to the declared types. A new run requires an even population of at least two members; a restart may omit members because they are restored from the checkpoint.
Use AgentEloConfig.model_validate(mapping) to validate a mapping, or
config.model_dump() to obtain a serializable configuration. Saved configurations
retain resolved member model settings so they can be loaded again.
Competition settings¶
| Setting | Default | Meaning |
|---|---|---|
initial_rating |
1500 |
Rating assigned to founding members. |
k_factor |
32 |
Positive scale controlling rating changes per match. |
deaths_per_round |
1 |
Maximum number of decisive losers replaced per generation; zero disables replacement. |
generations |
1 |
Number of additional generations run by each invocation; must be at least one. |
seed |
null |
Optional seed for pairing and selection tie-breaking. |
member_timeout_seconds |
null |
Positive execution budget for the independent-work phase; no environment-imposed deadline when omitted. |
judge_prompt |
null |
Optional additional judging guidance; put evaluation criteria in the task. |
restart_from_json |
null |
Path to a saved environment_state.json. |
Elo uses the standard expected-score calculation. A win scores 1, a draw 0.5,
and a loss 0. The rating change is k_factor * (score - expected_score).
For two equally rated agents with a K-factor of 32, a decisive match adds 16 to
the winner and subtracts 16 from the loser.
Only decisive losers are eligible for elimination. If there are more eligible losers than available replacement slots, the lowest-rated losers are selected using their updated ratings. Draws do not cause elimination, although a draw between unequally rated agents still changes their ratings.
Parents are selected from the highest-rated survivors. Equal ratings in selection are resolved using the environment's random generator. A fixed seed makes those random choices reproducible given the same population, ordering, and match outcomes; it does not make LLM responses deterministic.
Judging and incomplete runs¶
Specify evaluation criteria in the task so both competitors and judge receive
the same definition of success. The built-in judge prompt provides comparison
and inspection instructions, without a task-specific quality rubric. If the task
omits criteria, the judge evaluates how well each candidate fulfills it.
judge_prompt remains available for optional additional guidance.
The member prompt asks founders to establish an independent approach, surviving founders to favor refinement and validation, and descendants to explore a substantive improvement or alternative. A descendant retains that guidance in later rounds, even if it has itself produced children. The task determines what counts as a useful improvement.
Submissions use the following order:
- The member's final response, when available.
- Its saved progress report when there is no usable final response.
- A request for brief workspace inspection when neither is available.
Members with file-writing tools are asked to maintain a concise report at
_elo_progress/generation_<number>.md inside their own workspace. The report
should describe completed work, verified results, limitations, and supporting
file paths, distinguishing new work from inherited results. There is no fixed
word limit. Members should update it after meaningful milestones, using a
temporary file and rename to avoid partially written reports. The report is
optional for agents without file-writing tools.
The environment reads the current generation's report when handling a timeout, or when a completed run has an empty final response. It passes that captured text directly to the judge; it does not make an extra competitor LLM call or scan the workspace for a submission. An earlier generation's report is not substituted.
The judge evaluates supplied submissions first, with optional file verification.
When no submission is available, it is instructed to inspect obvious relevant
artifacts briefly, avoid extensive searches or lengthy calculations, and note
insufficient evidence if useful results cannot be found. It must not modify
candidate files. These efficiency instructions are not a hard judging timeout.
The judge uses the environment-level llm, even when members have their own models.
After inspection, an Elo-specific judge runs a final structured-output model call
using the accumulated conversation and tool results. It uses URSA's
invoke_structured() helper to validate winner (A, B, or DRAW) and
reasoning. This adds a final model call and uses the provider's default
structured-output method, with function calling as an alternative; it does not
guarantee strict decoder-level enforcement across providers.
If the agentic judge fails to initialize, execute, or return a valid decision, including when structured output is unsupported, URSA tries a direct LLM judgment with JSON prompting and validation. That fallback uses the supplied text without workspace tools. If it also fails, the match is recorded as a draw with an explanation.
A timeout alone is not a loss: timed-out members can win, draw, survive, and reproduce based on the judgment. An execution failure forfeits against a completed or timed-out competitor, even if the timed-out competitor has no saved report. Two failed competitors draw:
| Candidate A | Candidate B | Outcome |
|---|---|---|
| Completed or timed out | Completed or timed out | Ask the judge. |
| Completed | Failed | A wins automatically. |
| Failed | Completed | B wins automatically. |
| Timed out | Failed | A wins automatically. |
| Failed | Timed out | B wins automatically. |
| Failed | Failed | Draw. |
When member_timeout_seconds is set, all members receive the same UTC deadline
for that generation's independent work. This budget does not cover judging or
reproduction. The environment cancels member execution at the deadline, but an
already-running blocking subprocess may continue until it exits or reaches its
own timeout. Neither report capture nor optional workspace inspection guarantees
a filesystem snapshot taken precisely at the deadline.
Workspaces, persistence, and lineage¶
Each member receives a separate directory under the environment workspace:
The child name above is illustrative. Descendant names include the parent name, the child's lineage generation, and a counter; existing names and directories are checked to avoid collisions.
If no top-level workspace is supplied, the environment uses:
Persistent members use identities of the form
<environment-name>_<member-name>, stored under:
Elo manages these paths and identities. Member-level config.workspace and
config.agent_name are rejected, including during restart. Set the environment's
top-level workspace and use each member's name field instead.
With persist_members=True, the default, children inherit copies of the parent's
workspace, checkpoint database, and LangGraph store when present. They also
inherit the parent's updated Elo rating and member configuration, including its
role, prompt, and model settings. Parent and child subsequently use separate
files and databases.
Lineage generation and environment generation are different: a founder remains at lineage generation zero even after surviving several environment generations. A child's lineage generation is its parent's plus one.
Eliminated members leave the active roster, but their workspaces and persistent
directories remain on disk for inspection. With persist_members=False, workspace
copying and rating inheritance still occur, but persistent checkpoint inheritance
is disabled and restart from JSON is unavailable.
Member-specific models¶
Members inherit the default llm unless they specify a model block. For example,
these entries can be used in the YAML members list:
members:
- name: researcher_1
role: Develops and validates numerical methods
agent: ExecutionAgent
model:
model: openai:gpt-4o-mini
- name: researcher_2
role: Investigates an alternative numerical approach
agent: ExecutionAgent
model:
model: ollama:llama3.1
base_url: http://localhost:11434
Model diversity can provide different approaches, but ratings measure performance under this task and judge. They are not a general benchmark of model capability.
Saving configuration and restarting a run¶
Saving configuration and saving evolutionary state serve different purposes.
Use save_elo_config(...) to save the environment definition:
from ursa.environments import load_elo_config, save_elo_config
config = load_elo_config("examples/environments/agent_elo/agent_elo.yaml")
path = save_elo_config(config)
print(path)
Without an explicit destination, the configuration is written to:
After each completed generation, Elo separately writes environment_state.json
in the environment workspace. It contains the active member configurations,
ratings, lineage, completed-generation count, and random-generator state.
To restart, use the same environment name, group, and workspace, and supply the
snapshot path. From the worked example directory, save this as elo-restart.yaml:
name: numerical_integration_elo
group: default
workspace: ./workspace_agent_elo
restart_from_json: ./workspace_agent_elo/environment_state.json
generations: 2
k_factor: 32
deaths_per_round: 1
Load it with AgentEloEnvironment.from_yaml("elo-restart.yaml", llm=llm) and invoke
it with the task again. This runs two additional generations. The task is not
stored in the restart snapshot.
The snapshot replaces the YAML member list, including per-member model settings.
Members without an explicit saved model use the default LLM supplied on restart.
Resolved model settings are saved without requiring the original named-provider
registry; credential references still require the corresponding credentials to be
available. Current environment settings such as generations, k_factor,
deaths_per_round, timeout, and judge instructions come from the restart
configuration, so retain any custom values you want to continue using.
The JSON file is metadata, not a self-contained backup. Keep the member workspaces and persistent databases alongside it in their expected locations. Restart requires member persistence and existing member directories.
If child preparation fails, Elo cleans up prepared children and keeps the original population active. Ratings and evolutionary random state are restored so the failed generation can be retried. This does not undo research or file changes already made by the competing agents.
Understanding the results¶
The returned mapping includes:
task: the normalized task;starting_generation,completed_generations, andending_generation: progress for this invocation;generations: detailed reports for the completed generations;standings: final active members, ratings, ranks, lineage generations, and parent names;population_size: the final active population size;environment_state: the restart snapshot path.
Each generation report includes member outputs, member_runs, pairs,
matches with scores and reasoning, ratings_before, standings_after_matches,
eliminated, reproducing_parents, children, and the resulting standings.
It also lists failed and timed-out members. Each member_runs entry includes
progress_report when captured; outputs remains reserved for final responses.
Timed-out members retain their timed_out status regardless of the judgment.
Inspect outputs and match reasoning alongside ratings. Final standings include
new children that have inherited ratings but have not yet competed themselves.
The generation's outputs belong to the agents that actually worked that round.
Monitoring runs¶
The dashboard's Environment Runs page can validate Elo YAML, launch a run, and display its progress. For Python-driven recording and replay, see Visualizing Environment Runs. Elo emits generation, member, match, elimination, reproduction, and topology events.
Practical guidance¶
- Start with two members and a small number of generations to evaluate whether the task and judge criteria produce useful improvements.
- Give agents a task with room for substantive development, such as stronger numerical validation, a better method, or a tested implementation.
- Ask for concise final outputs that identify evidence, generated files, and remaining weaknesses so the judge can compare the work efficiently.
- Account for every active member running each generation, plus one judgment per completed pair and the cost of copying persistent state for children.
- Preserve the full workspace and persistence directories if you intend to restart or inspect extinct lineages.
- Treat high ratings as comparative evidence within the run, and check the underlying results before relying on them.