A benchmark that places non-stationarity in the harness — tools, skills, and specialist agents — rather than in the task stream. 17 streams, 802 tasks, two evaluation modes: deployment and self-evolving adaptation.
Modern LLM-based agents operate through a harness of tools, reusable skills, and specialist agents. In practice this harness continually evolves as new capabilities are added. Existing continual-learning benchmarks typically place non-stationarity in the task stream while keeping the harness fixed. EvoHarnessBench inverts that: the externally supplied harness itself expands, stage by stage.
The benchmark contains 17 multi-stage streams constructed deterministically from verifier-based benchmarks (EnterpriseOps-Gym and Agents’ Last Exam), comprising 802 tasks instantiated as 1,510 axis-specific evaluation examples. Two complementary settings isolate the central challenges: deployment evaluation (retention as the harness expands, with no experience carried forward) and self-evolving adaptation evaluation (whether accumulated persistent state remains useful as new capabilities arrive).
Current agents do not yet keep pace when the harness grows.
Frontier agents can lose competence on previously solved tasks simply as the externally supplied harness expands — harness-induced forgetting. Model parameters are unchanged; only the exposed capability set grows. The largest case is −34.7% BWT on the agents axis (ALE, deployment).
Improvements vary substantially across harness stages, capability axes, and environments. MemToolAgent lifts tools on EOG to 38.6%, while on ALE most methods stay close to or below the deployment baseline.
Methods that best preserve previously accessible competence often impair adaptation to newly introduced capabilities, and vice versa. Positive BWT with negative FWT is a recurring pattern.
Negative deployment BWT appears even when neither model parameters nor persistent state are updated. The mechanism differs across axes: tools enlarge the executable action space, skills must be retrieved and engaged, and agents add delegation and coordination demands. Retention must be evaluated as the harness itself evolves.
Task-relevant exposure helps for tools and skills, whereas agent adaptation benefits more from a stable broader pool. Inner adaptation cannot be considered independently of outer harness evolution.
FWT and BWT reveal behavior hidden by aggregate performance. A system can improve overall while becoming worse either at exploiting newly introduced capabilities or at preserving earlier competence, so the two should be evaluated separately along the harness trajectory.
These results motivate bi-level adaptation: inner updates from current experience, plus an outer objective that checks whether those updates still hold once the harness moves again — including detecting when persistent artifacts have gone stale.
@article{evoharnessbench2026,
title={EvoHarnessBench: Can Your Agents Keep Pace with an Evolving Harness?},
author={Ke, Zixuan and Patil, Vaidehi and Shi, Haizhou and Li, Yang
and Liu, Ye and Shekkizhar, Sarath and Koul, Anurag
and Wang, Jiayu and Nguyen, Xuan Phi and Yavuz, Semih
and Bansal, Mohit and Joty, Shafiq},
year={2026}
}
The benchmark separates outer harness evolution (tools, skills, and agents accumulating across stages) from optional inner self-evolving adaptation (persistent state updated inside a stage). Then: how streams are built, and how expansion looks on each axis.
Capabilities are released from core to long tail. Earlier ones stay as affordances or distractors. This study isolates monotonic growth — nothing is retired.
Sits outside every stage: the inner loop may write it, and it outlives the harness version that produced it. Deployment evaluation leaves it empty; self-evolving evaluation carries it forward.
Does harness expansion itself degrade previously solved tasks?
Can self-evolving adaptation keep pace as capabilities arrive?
Do retention and adaptation pull in different directions?
How do the three axes — tools, skills, agents — differ?
17 streams from two seed benchmarks, constructed deterministically from EnterpriseOps-Gym and Agents’ Last Exam. No LLM-generated tasks. Capabilities are released from core to long tail.
Figure 2 · harness growth
Each stage adds adaptation and held-out test tasks. The curves track the cumulative tools, skills, or agents the system must navigate.
Every task the benchmark evaluates on, as the agent receives it: a single request against a live environment, feasible at the stage that introduced its capabilities and graded only on the state it leaves behind. Open one to read its prompt, the harness it was offered, and the checks it is scored against — then open any tool, skill, specialist or piece of software it names to read what that is. The same request can appear under more than one axis, since what the agent is handed differs even when the request does not.
1,061 evaluation tasks read from the benchmark corpus — the test split of every
domain the paper reports, across all harness stages. Prompts, oracle harness, staged files and
verifiers are reproduced verbatim; nothing here is recomputed. Figures are equally literal:
an EnterpriseOps figure is a screenshot of that task's own environment, seeded from the exact
SQL the harness feeds the gym and captured before the agent touches anything, and an ALE
figure is the artifact staged in the task directory — the drawing, the slide, the clip
— scaled for the web and otherwise untouched. 839 of the 1,061 tasks carry one.
A note on the EnterpriseOps pictures. The 503 tasks draw on 51 distinct seeded databases, so tasks in one domain generally show different environments — but those 51 render as 28 distinct pictures, because each app view shows a curated slice of its gym and a number of seeds differ only in timestamps, in rows below the fold, or in tables the view never displays. Where two environments are pixel-identical they share one file. Only this app view is shown; the relational view that explains how a task is graded is a different question and is not reproduced here.
Start with the evaluation protocol, then switch axes to compare deployment and self-evolving methods on EOG and ALE. Pass rates are verifier success on the held-out cumulative evaluation split.
A harness stream is a nested sequence H1 ⊆ H2 ⊆ ⋯ ⊆ HT. Each task is feasible at its assigned stage and requires at least one newly introduced capability. At test time the system receives the full cumulative harness — earlier capabilities remain as affordances or distractors.
No persistent state across stages. A fresh instance is evaluated on the cumulative eval set under Ht. Isolates the direct effect of harness expansion: can the agent exploit new capabilities while withstanding distraction from the growing pool?
Persistent state zt (memory, learned skills, prompts, or code) may be updated from an adaptation split and carried forward. Measures whether accumulated experience supports new capabilities — or goes stale and misdirects execution.
We report pass rate and cost, plus transfer metrics that separate new-task adaptation from retention:
| Tools | Skills | Agents | |
|---|---|---|---|
| Deployment expansion | Accuracy ↑, cost ↑ | Little effect | Environment-dependent |
| Largest harness-induced forgetting | −5.3% | −4.0% | −34.7% |
| Best adaptation gain | +27.8% MemToolAgent | +27.5% GEPA | +110.2% Meta-Harness |
| Best adaptation context | Task-specific | Task-specific | Final full pool from the beginning |
| Observed bottleneck | Extracting useful tool experience | Engaging the right skills | Balancing delegation coverage and selectivity |
Adaptation gains are relative to the cumulative deployment baseline on EOG. Forgetting is worst-case deployment BWT.
Broader catalogs can raise pass rate (26.0% → 30.2% on EOG) while inflating tokens (27.2M → 75.8M). MemToolAgent is the strongest adapter (38.6%), but ALE gains do not follow.
Pool expansion is nearly free at deployment (Codex stays at 18.9% on EOG). The hard part is engagement: default GPT-5 systems invoke almost none of the offered skills; GEPA is the clearest adapter (24.1%).
The hardest axis. Task-specific Codex reaches only 6.5% on EOG and cumulative deployment 8.8%. Meta-Harness lifts that to 18.5% (+110.2% relative), while ALE shows the most severe forgetting in the paper (−34.7% BWT).
Broader tool exposure improves accuracy but raises cost. The larger catalog is not only a distractor — agents can use extra affordances, at the price of more search.
MemToolAgent 38.6%, ReasoningBank 36.9%, Meta-Harness 35.2% vs. 30.2% deployment on EOG. On ALE most methods stay at or below the baseline.
Deployment BWT is negative on both environments. On ALE, GEPA and Meta-Harness improve retention but FWT drops to −28.5% and −11.1% — inner adaptation can over-specialize.
Task-specific tools during MemToolAgent adaptation cut time 23.9h → 17.9h with similar pass rate. Structured memories beat raw replay; schemas and overly shared memories can hurt.
Grey = task-specific reference. Green = best overall method. ▣ = multi-agent. Values are pass %.
Broader skill exposure has little aggregate effect. Codex stays at 18.9% on EOG; tokens barely move. Skills must be retrieved rather than remaining as always-on actions.
GEPA is the clear gain on EOG (18.9% → 24.1%). Memory methods are modest or mixed; Codex native memory slightly drops pass rate.
Mild deployment forgetting. GEPA reaches +14.9% BWT on ALE with −4.0% FWT — preserving old procedures does not imply learning new ones.
Default GPT-5 invokes almost no offered skills; task-specific GPT-5.5 invokes 82%, Claude Code 15%, task-specific GEPA raises GPT-5 to 31%. Skill learning needs grounded refinement, coverage, and control over sequential accumulation.
Environment-dependent. Codex improves on EOG (6.5% → 8.8%) and degrades on ALE (5.3% → 4.2%). Coordination is hard even with a task-specific pool.
Largest relative gains of any axis on EOG: Meta-Harness 18.5% from 8.8%. ALE does not follow — only GEPA clearly improves (6.3%).
EOG is comparatively favorable (GEPA +14.3% FWT). ALE deployment Codex hits −34.7% BWT, the strongest forgetting in the paper.
Adaptation helps required-agent recall (79% → 89%), not precision (~90%). Forgetting is lost coverage of previously successful specialists, not distractor takeover. Agent adaptation prefers a stable full pool over a changing sequence.
“—” = not completed within a comparable compute budget.
Aggregate pass rates say that an expanding harness hurts. A case says how. Every case in the benchmark is here — a single task at a single (adapt stage, test stage) pair, with every system’s attempt beside it: the prompt as the model received it, what the graders checked, what the harness offered, and what each system actually reached for. The list opens on the cases where the systems disagree most, since a task they all solve or all fail shows nothing; clear that filter to browse the rest.
Loading the case table…
3,395 cases and 17,992 system attempts, read from the benchmark’s per-task record — one row per (task, system, adapt stage, test stage) cell. Nothing on this page is recomputed from the trials.
Evaluate your agent on EOG and ALE through the hosted API service, which provides the environments and graders. For local use, download the published Hugging Face datasets to inspect tasks and harness resources.
run_benchmark does all four once per task. EOG tasks use MCP tools and ALE tasks
use artifact files; run_leaderboard combines results from the hosted benchmarks.
The agent acts by calling MCP tools bound to a fresh per-session database, and hidden
SQL verifiers grade the state it leaves behind. You get task.mcp_servers
and task.mcp_session(s). Eight published streams — calendar, CSM, drive,
email, HR, hybrid, ITSM, teams — plus enterprise_tri_hybrid, a cross-gym
stress domain no published table counts.
The task stages input files; the agent works however it likes and submits a
deliverable, scored by the task’s own evaluate(). You get
task.inputs(), task.fetch_input(p) and
task.submit_text(p, s). ALE is a flat layout, so pass domain=None,
and its tasks are deliberately hard — a low score is the norm.
If you just want a fast, interactive evaluation. Inspect the
evolve-eval skill.
This optional skill uses the service’s simple_agentic_evals SDK to guide
your coding agent through hosted evaluation. Set your service key and install the
authenticated package with the command below, then ask your coding agent to
“use the evolve-eval skill to evaluate yourself on the hosted EvoHarnessBench tasks.”
It checks the hosted target, reviews the agent configuration and evaluation budget with you,
and reports the results. You can also ask it focused API questions such as
“How do I get per-verifier results?”; it will give the matching SDK or HTTP access
path without starting a run. Credentials stay in environment variables.
: "${EVAL_SERVICE_API_KEY:?Required — sign in and open MyAuthtoken}" &&
curl -fsSL -H "Authorization: Bearer $EVAL_SERVICE_API_KEY" \
https://educator-marrow-cultural.ngrok-free.dev/resources/evolve-eval/install.sh | bash
# Then tell your coding agent: Use evolve-eval to evaluate yourself on the hosted EvoHarnessBench tasks.
For repeatable experiments and leaderboard runs, follow the SDK path from authentication through agent integration, scoring, and continual evaluation.
The hosted service is authenticated, so the first step happens in a browser rather than in code. Log in to the demo, click MyAuthtoken, and copy the service API key it shows you. The same key covers both halves of a run: the scoring calls, and the MCP tool calls your agent makes while it acts.
Sign in at the demo and open MyAuthtoken from the signed-in view. That page issues the key; it is yours, so keep it out of shared notebooks and out of version control.
Two different keys are in play, and confusing them is the most common setup failure.
EVAL_SERVICE_API_KEY opens this service and comes from MyAuthtoken.
OPENAI_API_KEY is whatever your method thinks with, and it comes from
your own account: the service hosts the environment and the grader, but it does not pay for
your inference. Export them once and nothing downstream has to mention either again —
EvalClient() reads the first and sends it as a bearer token.
export EVAL_SERVICE_API_KEY="<paste the key from MyAuthtoken>" # this service
export OPENAI_API_KEY="sk-..." # what YOUR method thinks with
Prompting works too, and is the easier route in a notebook where you would rather not leave either key in a cell that might get committed.
import getpass, os
os.environ["EVAL_SERVICE_API_KEY"] = getpass.getpass("Eval service key (MyAuthtoken): ")
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API key: ")
Your OpenAI key is only ever seen by the agent you hand it to. The one deliberate exception
is the two provided harnesses, "react" and "codex" — they
execute on the service, so the key travels with that request, and they require it.
Omit it and the SDK raises MissingAPIKey before anything is sent, rather than
letting a forgotten key score a clean-looking 0.0 across a whole sweep.
The client SDK is served by the service, so there is no PyPI account, no repo checkout,
and no service URL anywhere in your code afterwards. EvalClient() resolves its
endpoint on its own: the base_url argument, then $EVAL_SERVICE_URL, then
the URL baked into the served wheel.
SDK_BASE="https://educator-marrow-cultural.ngrok-free.dev"
: "${EVAL_SERVICE_API_KEY:?Required — get it from MyAuthtoken first}" &&
WHEEL=$(curl -sSL -H "Authorization: Bearer $EVAL_SERVICE_API_KEY" "$SDK_BASE/sdk" \
| python -c 'import json,sys; d=json.load(sys.stdin); print(d["path"]) if "path" in d else sys.exit("SDK download denied: " + d.get("detail", "unknown error"))') &&
curl -fsSL -H "Authorization: Bearer $EVAL_SERVICE_API_KEY" \
"$SDK_BASE$WHEEL" -o "/tmp/${WHEEL##*/}" &&
python -m pip install "/tmp/${WHEEL##*/}"
Install the wheel the deployment is currently serving, not merely a wheel: a
stale pre-installed copy shadows it and then drifts from the API silently. The notebooks check
simple_agentic_evals.__version__ against GET /sdk and reinstall when
the two disagree.
from simple_agentic_evals import (CommandAgent, EvalClient, ServiceError,
catalog_targets, resolve_target,
run_benchmark, run_leaderboard,
react_agent, acp_codex_agent, to_openai_tools)
client = EvalClient() # endpoint and auth resolved for you -- no URL to paste
LLM_MODEL = "gpt-4o-mini" # whatever YOUR method thinks with
# Every /v1/* route needs the key, health included, so this one call both connects
# and proves the key -- a bad one fails HERE rather than three sections later.
print("connected ->", client.health())
Never infer support from a benchmark name. Resolution checks the live catalog’s adapter health, track, mode, harness and action surface. If any requested capability is missing, the target is unavailable through the hosted service. Infrastructure and grader failures remain errors rather than being converted to zero.
print(client.health())
for target in catalog_targets(client):
print(target["dataset"], target["benchmark"],
"runnable=", target.get("runnable"),
"health=", target.get("health"),
"actions=", target.get("action_types"))
# Hosted is selected only when every requested capability is healthy.
choice = resolve_target("eog", dataset="evovling_tools",
mode="deployment", harness="react", action_type="mcp",
client=client)
assert choice["location"] == "hosted", choice
print(choice["reason"])
Continue only when the catalog confirms the requested hosted configuration is runnable. If it is unavailable, select a supported configuration or inspect the published datasets.
First wrap your method as the callable below, then use Connect any agent to connect it through the EOG tool
interface or ALE input/output files. Once both pieces are in place,
run_benchmark can evaluate fresh, isolated instances across the full task scope.
run_benchmark takes anything callable that accepts a task. There is no
base class to subclass and nothing to register, so if your method already exists you are
writing an adapter, not a port. You never write with task: either — a
fresh environment is provisioned before each call and torn down after it.
The grader never reads your return value; it reads the environment you acted on. What you leave in the gym database or in the sandbox filesystem is your answer.
mcp.close() before returning, from a finally: — a crash
mid-task should still release the session.
Your model calls never touch the service, so it cannot count them.
return {"total_tokens": n} fills the cost column; omit it and the row reads
not measured rather than a misleading 0.
def solve_eog(task, max_steps=6):
"""EOG: act on the gym through MCP tools. Replace the loop with your method."""
used = 0
mcp = task.mcp_session(task.mcp_servers[0])
try:
tools = to_openai_tools(mcp.list_tools()) # MCP schemas -> OpenAI specs
msgs = [{"role": "system", "content": task.system_prompt or ""},
{"role": "user", "content": task.user_prompt or ""}]
for _ in range(max_steps):
r = oai.chat.completions.create(model=LLM_MODEL, messages=msgs, tools=tools)
used += r.usage.total_tokens
m = r.choices[0].message
msgs.append(m.model_dump(exclude_none=True))
if not m.tool_calls: # no call -> the agent is done
break
for tc in m.tool_calls:
out = mcp.call_tool(tc.function.name,
json.loads(tc.function.arguments or "{}"))
msgs.append({"role": "tool", "tool_call_id": tc.id,
"content": json.dumps(out)[:4000]})
finally:
mcp.close() # rule 2 -- even if we raised
return {"total_tokens": used}
def solve_ale(task):
"""ALE: read the staged inputs, write the deliverable. One shot, no tools."""
files = task.inputs()
head = task.fetch_input(files[0]["path"])[:4000] if files else ""
r = oai.chat.completions.create(model=LLM_MODEL, messages=[
{"role": "system", "content": task.system_prompt or ""},
{"role": "user", "content": f"{task.user_prompt}\n\n{head}"}])
task.submit_text(task.output_path, r.choices[0].message.content or "")
return {"total_tokens": r.usage.total_tokens}
def my_agent(task):
"""One entry point for callable MCP and artifact adapters."""
if task.action_type == "mcp":
return solve_eog(task)
if task.action_type == "sandbox":
return solve_ale(task)
raise RuntimeError(f"Unsupported hosted action type: {task.action_type}")
Branch on task.action_type, not a benchmark name. Swap the bodies for LangGraph,
your planner or another provider while using the hosted EOG tools or ALE file interface.
Whatever you already have — a prompt optimizer, a memory, a multi-agent scaffold, a container, a CLI — connects through one wrapper and one of two interfaces. Your policy runs in your process and calls the service, so its language, framework and runtime stay yours; anything reachable from inside a Python function can be scored.
EOG tool-use interface — MCP. task.mcp_url(server) with a bearer header is a
standard MCP endpoint over streamable HTTP, so any conformant client connects to it,
the official MCP SDK included. Underneath, the contract every agent framework consumes is
just a list of JSON-Schema tool declarations plus a call(name, args)
function — which is why a policy we have never seen still plugs in.
server = task.mcp_servers[0]
url = task.mcp_url(server) # hand these two to your own MCP client
headers = {"Authorization": f"Bearer {EVAL_SERVICE_API_KEY}", **server.headers}
# Or use the bundled minimal client. These two calls are the entire seam --
# everything else in the SDK is a convenience on top of them.
mcp = task.mcp_session(server)
tools = mcp.list_tools() # 1. WHAT YOUR POLICY CAN DO (plain JSON Schema)
mcp.call_tool(tools[0]["name"], {}) # 2. HOW YOUR POLICY ACTS (a plain function)
mcp.close()
ALE artifact-delivery interface — input/output files.
fetch_inputs_to(dir) stages the task’s files
on your disk and submit_dir(dir) ships back whatever your policy wrote. The
contract is a filesystem, so a container, a CLI, a remote job or another language all sit
behind it equally well. Submitted paths are normalized under the task’s
output/, and non-UTF-8 files are base64-encoded automatically so binaries survive.
import subprocess, tempfile
from pathlib import Path
def containerized_agent(task):
"""An existing container, unchanged, scored on an ALE task.
It never talks to the service: it reads one directory and writes another,
exactly as it does on your laptop. We fill the first, we collect the second.
"""
with tempfile.TemporaryDirectory() as tmp:
work = Path(tmp)
indir = work / "input" # we fill this one
outdir = work / "output" # your system fills this one
outdir.mkdir()
task.fetch_inputs_to(indir) # 1. service -> your disk
subprocess.run([ # 2. your system runs, however it likes
"docker", "run", "--rm", "-v", f"{work}:/work", "my-system:latest",
"--input", "/work/input", "--output", "/work/output",
], check=True, timeout=1800)
task.submit_dir(outdir) # 3. your disk -> service, then grading
# Returning 0 would be worse than saying nothing: it reports a measurement of zero.
return None
# Nothing to do with Docker, really. For a plain CLI, steps 1 and 3 are identical and
# step 2 becomes: subprocess.run(["my-system", "--in", indir, "--out", outdir])
That is the whole evaluation. run_benchmark walks the scope, calls your agent
once per task, grades what it left behind, and hands back a report.
.success_rate is Pass % — strict, every verifier on the task
passed — and .accuracy is Score, the mean verifier pass rate. The
distance between them is partial credit, and reporting only one of them hides which kind of
method you built.
report = run_benchmark(my_agent, mode="deployment_eval", client=client,
benchmark="eog", limit=2, progress=True)
print(report) # the paper-style block
print(report.success_rate, report.accuracy) # Pass % (strict), Score (partial credit)
print(report.agent_s, report.total_tokens) # agent time, not wall clock; None if unmeasured
print(report.n_errors, report.complete) # a non-zero n_errors did not measure what you think
def floor_agent(task):
"""Acts on nothing. Whatever this scores, the environment was giving away."""
return {"total_tokens": 0}
floor = run_benchmark(floor_agent, "deployment_eval", client=client,
benchmark="eog", limit=2, progress=False)
print(f"floor {floor.accuracy:.3f} mine {report.accuracy:.3f}")
# ^ if these two match, your agent is not acting -- check that it reaches mcp.call_tool
limit= is for rehearsal only. It proves the adapter runs and the grader sees what
you left behind, and the report stamps the run SUBSAMPLED so a debug number can
never be mistaken for a result. Drop it for anything comparable.
A leaderboard row is both benchmarks, repeated so it carries a spread. Three details make the
numbers comparable rather than merely plausible. Hours are summed agent duration, not wall
clock — provisioning and grading are ours, so they do not count against you, which
is why the row reads agent_s and not latency_s. ± is the
population standard deviation over repeated runs, not a standard error and not a
confidence interval; the published rows use three. And Overall pools tasks, not
percentages: total solved over total attempted, so EOG’s 454 tasks outweigh
ALE’s 63, and a macro-average of the two rates would be a different, larger number.
# The SDK runs both benchmarks, aggregates repeats, and returns leaderboard JSON.
# A limited rehearsal is stamped `partial`; token telemetry stays None if unreported.
row = run_leaderboard(my_agent, "My Method", client=client,
benchmarks=["eog", "ale"], cat="memory", seeds=1, limit=2)
print(json.dumps(row, indent=2))
print(row["benchmark_results"]) # results keyed by hosted benchmark id
# Pass % mean(r.success_rate) strict -- every verifier on the task passed
# Score mean(r.accuracy) partial credit -- mean verifier pass rate
# h mean(r.agent_s) / 3600 agent time; r.latency_s is wall clock, not this
# Tok M mean(r.total_tokens) / 1e6 None rather than 0 when nothing reported usage
# Overall sum(n_success) / sum(n_tasks) pooled across both, over tasks not percentages
# +/- pstdev over the `seeds` repeats
# A publishable no-limit run is expensive and requires explicit acknowledgement:
# row = run_leaderboard(my_agent, "My Method", client=client, seeds=3,
# benchmarks=["eog", "ale"],
# confirm_full_cost=True)
# Two switches worth knowing before you commit:
# dataset= the evolving axis -- "evovling_tools" | "evovling_skills" | "evovling_agents",
# each a separate leaderboard.
# mode= the question. "deployment_eval" is a fixed method under a growing harness and
# is the right default; a method that LEARNS between stages belongs in
# "self_evolving_adapt_eval" -- the next tab.
version was never an arbitrary shard. Every domain arrives in stages, and
each stage adds tools, SKILL.md bundles or roster specialists and brings
its own cohort of tasks. Two populations move at once, and crossing them gives the object the
whole evolving study is built on: a lower-triangular performance matrix, cohort
τ evaluated under harness stage t, defined only for τ ≤ t
because a cohort does not exist until its stage is reached. One flag fills it.
report = run_benchmark(my_agent, "deployment_eval", client=client,
domain="calendar", matrix=True) # costs roughly T x the last row
# cohort v1 cohort v2 cohort v3
# under H_v1 0.31 . . <- adaptation: v1 tasks, v1 tools
# under H_v2 0.28 0.40 . <- retention: v1 tasks, v2 tools exist
# under H_v3 0.25 0.36 0.22 <- the last row is what ACC pools
#
# Down a column is one cohort's story as the world grows around it. 0.31 -> 0.28 -> 0.25 is
# FORGETTING: nothing about those tasks changed, only the harness did. That column delta is
# what BWT summarizes.
print(report.bwt, report.fwt) # cohort-size-weighted means, exactly as the appendix defines
print(report.per_stage_acc) # the diagonal, by stage
deployment_eval isolates the harness’s effect by holding the method fixed.
self_evolving_adapt_eval asks the other question — whether learning keeps
up with growth — by handing your method each stage’s training split before
that stage is evaluated. BWT is meaningful in both; FWT only exists in self-evolving,
because it measures adaptation, and omitting adapt there is an error rather than
a silent fallback.
class EvolvingAgent:
"""A policy with state: `adapt` writes it, `__call__` reads it.
run_benchmark picks up `.adapt` automatically, so this object is the whole
integration -- no registration, no adapt= argument needed.
"""
def __init__(self):
self.notes = []
def adapt(self, stage, tasks): # once per stage, with that stage's TRAIN split
for t in tasks:
with t: # prompts exist only once a task is provisioned
self.notes.append(f"[v{stage}] {(t.user_prompt or '')[:140]}")
def __call__(self, task):
# `act` is the loop from "Wrap your method", with room in the system prompt
# for whatever the policy has learned so far.
return act(task, notes="\n".join(self.notes[-8:]))
evolve = run_benchmark(EvolvingAgent(), "self_evolving_adapt_eval", client=client,
domain="calendar", matrix=True)
print(evolve.bwt, evolve.fwt) # FWT is None in deployment mode; here it is real
All three notebooks use the hosted service’s client and session API. The environments and graders run on the service. The notebooks share setup and health checks, then each answers one question — pick by how far you intend to go.
f(task), connect it through either port, smoke-test it, check it against the
do-nothing floor, then produce the eight columns the leaderboard stores.
8 code cells · …_quick_start.ipynb
2
Continual learning
What that single number cannot say. The lower-triangular performance matrix behind it:
adaptation on the diagonal, retention below it, BWT and FWT weighted as the appendix
defines them, and the adapt(stage, tasks) hook for methods that learn.
9 code cells · …_quick_start_detail.ipynb
3
Supported harnesses & modes
The one you come back to. Standard non-evolving evaluation, the react and
codex harnesses with their real costs and failure modes, all three modes, ALE
in depth, persistent memory, raw HTTP, and the complete API dictionary.
10 code cells · …_full.ipynb
The Hugging Face release provides datasets for local inspection and analysis. To run and score your agent, please use the hosted API service.
ZixuanKe/evovling_tools
4,632 rows · 49 configs
Evolving skills
ZixuanKe/evovling_skills
3,357 rows · 31 configs
Evolving agents
ZixuanKe/evovling_agents
3,327 rows · 32 configs
All three sit in the EvoHarnessBench collection on Hugging Face.
A dataset is the evolving axis, a config is
{domain}_v{stage}, and the split is train or
test. The example below reads a published task and its metadata.
from datasets import load_dataset
# One axis of harness evolution; one stage of one domain; the evaluation split.
ds = load_dataset("ZixuanKe/evovling_tools", "hr_v1", split="test")
row = ds[0]
row["user_prompt"] # the request, exactly as the agent receives it
row["oracle_tools"] # the annotated required tools
row["cummulative_tools"] # tools available up to this stage
row["verifiers"] # verifier metadata
row["gym_servers_config"] # gym and snapshot metadata
Those columns are the EOG shape. The ale_* configs carry
task_prompt, input_files, agent_must_do and the task
taxonomy instead, because an ALE task is graded on a submitted artifact rather than on
database state.
The dataset downloads provide task records and harness resources. The hosted service supplies the running EOG and ALE environments and graders needed to evaluate an agent. Follow the hosted workflow for authentication, agent integration, and scoring.
Every reported system, one axis of harness evolution at a time. Systems run the same tasks under the same accumulated harness, so the columns are comparable down an axis — but not across them, since each axis has its own task set. Each row states its backbone model and the harness it ran in, because the field is not uniform: the controlled systems hold GPT-5 fixed, SkillOpt runs GPT-5.5, and Claude Code is Sonnet-4.6 inside its own harness. Filter to one model and one harness to compare shared backbones and scaffolds. Click a system name for its configuration, search budget, and available results by stream and stage.
Transcribed from the paper's three main results tables, plus the Claude Code deployment table and the skill-learning ablations from the appendix. Pass (%) is strict: every verifier on a task has to pass. Score is the mean verifier pass rate, so the distance between them is partial credit. ± is the population standard deviation over 3 runs — not a standard error, and not a confidence interval. Hours are summed agent duration rather than wall clock, and tokens count input plus output. The record carries no dollar cost, so none is shown. The paper reports Claude Code and the skill-learning ablations separately from its main tables; here they are ranked in the same field, with the model and harness on every row in place of that separation. Two caveats survive the merge. Claude Code changes both model and harness, so it is a controlled comparison only against itself. The skill-learning rows vary only skill provenance and were run on EOG alone, so their ALE columns and overall rate are empty; the paper does not state their task count, and the EOG count in the header is the evolving-skills task set rather than a figure quoted for those rows.
EvoHarnessBench ships 17 streams constructed from two seed benchmarks, EnterpriseOps-Gym and Agents’ Last Exam. The construction recipe itself is general: point it at a seed of your own and it gives you an evolving benchmark on the same terms.
If you just want a fast, interactive build. Inspect the
evolve-benchmark skill,
then install the authenticated package with the command below and ask your
coding agent to “use the evolve-benchmark skill to convert my benchmark.” It will inspect
any verifier-backed seed and guide you through four short checkpoints: suitability, annotations,
staging, and final promotion. It builds each supported track only after you confirm its choices.
: "${EVAL_SERVICE_API_KEY:?Required — sign in at https://ian-sections-none-closest.trycloudflare.com/ and open MyAuthtoken}" &&
curl -fsSL -H "Authorization: Bearer $EVAL_SERVICE_API_KEY" \
https://educator-marrow-cultural.ngrok-free.dev/resources/evolve-benchmark/install.sh | bash
# Then ask your coding agent: Use evolve-benchmark to convert my seed benchmark.
A seed dataset with tool annotations, rich explanations, and procedural prompts can be converted into evolving tools, skills, and agents. Each signal supplies one capability view to the same four-step construction pipeline.
Per-task oracle tool labels provide the hidden capability sets for the tools track. Their frequencies determine when tools enter the cumulative harness.
Tool descriptions, interfaces, and software metadata reveal what each capability operates on. The agents track uses that structure to form disjoint owner-based specialists.
Reusable operating procedures in system or task prompts become hidden reference skills. General role, behavior, and output rules remain visible in the skills track.
For every retained task, the four-step pipeline assigns the task to the earliest cumulative harness that contains all of its annotated required capabilities. This gives the two formal properties defined in the paper.
All capabilities required by a retained task are available at its assigned stage:
Cᵢ ⊆ Hₜᵢ. Here, feasible means covered by the annotated capability set;
it does not claim that every task has been execution-tested successfully.
At least one required capability is introduced at the task’s assigned stage:
Cᵢ ∩ (Hₜᵢ ∖ Hₜᵢ₋₁) ≠ ∅. Because this is the earliest covering stage, the
previous harness cannot already cover the whole task.
Choose Terminal-Bench 2, APEX-Agents, or Hyper-τ-Bench as a worked example. Each tab downloads a fixed seed snapshot, constructs all three tracks, and shows the expected output.
This creates a fresh work directory, downloads the public repository, checks out the pinned 89-task snapshot, and verifies its commit. It does not download a private builder.
The tools track treats task-facing software as the capability universe. Terminal-Bench 2 does not provide that annotation directly, so Step 1 accepts only software explicitly required by the task or invoked by its oracle and records the matching evidence.
Download the versioned software catalog, validate its aliases, then discover tasks and
produce oracle_tools with source-path, line, and matching-rule evidence.
The shared implementation begins here and is extended once in the next two tabs.
Rank canonical tools by task frequency, release the frequent core first, retain the long tail, and merge only adjacent stages that cannot satisfy the split floors.
Form the cumulative allowlist, assign each task to the first stage covering all of its tools, write disjoint train/test JSONL, and validate the resulting trees. This final shared chunk is reused by the skills and agents tracks rather than printed again.
The skills track deterministically mines procedural atoms from task instructions, descriptions, keywords, tags, and software anchors. Universal execution and output rules remain visible as fixed house rules rather than becoming staged skills.
Download the fixed 26-skill catalog, validate its procedural schema, and associate skills with tasks using deterministic term and software matches.
Rank matched skills by frequency with the established skill sequencer. Singleton skills stay in the long tail, and the skill curriculum gets its own stage boundaries, assignments, and deterministic train/test split.
Build cumulative skill libraries, assign tasks to their earliest covered stage, and
write held-out SKILL.md files, indexes, evidence, and train/test rows.
The exact row and library functions used by the generated builder are shown below.
The agents track maps each accepted software capability to exactly one owner family. Each owner becomes a specialist with its own software bundle; task annotations are the owners of that task’s oracle tools.
Validate that ownership is a complete disjoint partition, then derive each task’s
oracle_agents from the owners of its oracle tools.
Rank owner families by task frequency. The exact call below reuses the shared frequency scheduler, preserves the tools-track split, and builds an independent cumulative agent curriculum.
Materialize owner skills, portable agent TOMLs, routing descriptions, manifests, and each stage’s cumulative software union. Then write task rows containing both the available specialist roster and its allowed software.
Each stacked bar is the set of tasks introduced at that stage: adaptation tasks are available for development and test tasks stay held out. The curve shows how many tools, skills, or agents the evolving harness has accumulated by then.
Tools and agents retain 86 tasks; skills retain all 89. The three software-unannotated
exclusions are extract-moves-from-video,
log-summary-date-ranges, and password-recovery.
All five CPU-only Harbor smoke tasks must return reward 1.0.
This last block installs the pinned runtime and Harbor adapter, runs two fresh deterministic builds, validates all three tracks, and executes one Harbor oracle smoke task per tools stage. Promotion occurs only when every check passes. Reproducibility is checked automatically, so there is nothing to copy or compare by hand.
Accept the dataset terms first. This block securely asks for your Hugging Face token, downloads and verifies the four small metadata files at the pinned snapshot, and checks out the matching Archipelago tool definitions. It does not save your token or download the roughly 9 GB world archives.
APEX-Agents names which applications a world makes available, but does not give each task a minimal oracle operation list. Step 1 therefore derives exact Archipelago MCP operations from task-facing seed evidence. Application names only guard availability; they are not tools.
Download the closed 33-operation catalog, verify every namespace owner, then match only the prompt, world description, expected-output type, and referenced input-file types. Every accepted operation records evidence, and its owner must exist in the task world.
Rank canonical MCP operations by task frequency, release the frequent core first, retain the evidenced long tail, and merge adjacent stages until every emitted stage satisfies the adaptation and test floors.
Build the cumulative operation allowlist, date every task to its earliest covered stage, write fresh train/test JSONL, and validate source membership, evidence, schemas, cumulative coverage, and newly introduced capability pressure.
The skills track follows the paper’s fixed procedural-atom route. It matches a closed catalog against prompts and world descriptions, expected-output types, and normalized tool anchors. Rubrics and gold responses are never used to annotate tools or skills.
Download the fixed 20-skill catalog and associate its canonical procedures with tasks using deterministic lexical, output-type, and software-anchor matches. Ten tasks with no supported match are excluded rather than assigned a guessed skill.
Rank the 19 active procedural atoms by task frequency with the established skill sequencer. The skill curriculum is independent of the tools curriculum and keeps its own stage boundaries, assignments, and deterministic split.
Accumulate the skill library, assign each retained task to its earliest covered stage, and
materialize held-out SKILL.md files, the stripped house-rules prompt, indexes,
manifests, and deterministic train/test rows.
Applications appear here, as owners—not in the tools track. Every MCP operation maps to exactly one application owner; each owner becomes a specialist, and a task needs the owners of its derived oracle operations.
Validate that the 33-operation catalog is partitioned across 11 owners with no missing or
duplicate ownership, then derive each task’s oracle_agents from its operation owners.
Rank the 10 active application owners by task frequency using the shared release scheduler. The cumulative specialist roster evolves independently from both the operation and skill pools.
Materialize owner SKILL.md files, portable Codex agent TOMLs, routing descriptions,
owned-operation lists, stage manifests, and the cumulative software union for every task row.
The three tracks are independently staged, so they do not need the same number of versions. Bars show the tasks introduced at a stage; the curve shows the cumulative operation, skill, or specialist pool available at that stage. Hover or focus a stage for its exact counts.
The tools and agents tracks retain 392 tasks; 88 tasks without supported operation evidence are excluded. The skills track retains 470 tasks and excludes 10 without a deterministic match. The final check runs one source/gold integrity preflight per tools stage—four checks total. It does not execute an agent or judge and does not report rewards.
This block reconstructs the pinned CLI, builds twice in fresh directories, requires identical
content, validates all three tracks, and runs the four metadata/gold preflights. Only successful
output is promoted to the three apex_agents_validated destinations.
This creates a fresh work directory, checks out the public 53-task snapshot at its pinned commit, and installs the shared construction engine. The source benchmark is left unchanged and the new evolving datasets are written separately.
Hyper-τ-Bench does not ship ordinary per-task oracle_tools. The approved
deterministic adapter treats the exact customer-service API operations that the constructed
agent must implement as tools. This is recorded as a capability-definition deviation; the
ranking, release, accumulation, and task-assignment pipeline remains unchanged.
Parse public @is_tool and @is_discoverable_tool operation
schemas, combine them with each task’s public domain or journey scope, and retain
evidence for every label. Framework wrappers, generic helpers, customer-only controls,
and verifier-only operations are excluded. The frozen adapter below also constructs the
skill catalog and ownership partition used by the other two tracks.
Inspect the annotations, then rank the 91 active operations by task frequency. Release the frequent core first, keep the long tail, and merge thin adjacent stages without weakening the two-adaptation and five-test floors.
Accumulate the operation allowlist and assign every task to the earliest stage covering all of its oracle operations. Write fresh canonical JSONL and manifests, then build twice and require identical files. The candidate is not called validated until runtime smoke passes.
The skills track mines reusable procedures from the full public construction prompt and description. Nine fixed procedural atoms are frozen before labeling; the adapter then matches all tasks deterministically without reading hidden inner-task gold actions, rubrics, or verifier state.
Render the task description together with developer-visible kit requirements, then match frozen prompt signatures for evidence synthesis, API implementation, greenfield or brownfield construction, hardening, elicitation, live-pilot use, response phrasing, and credit budgeting. Every one of the 253 task-skill labels is grounded in that public rendered prompt.
Rank the nine procedures by task frequency and run the established skill sequencer. This curriculum is independent of the tools curriculum, so it receives its own five stage boundaries and deterministic train/test membership.
Assign tasks to their earliest covered skill stage, retain universal execution rules as
visible house rules, and materialize each hidden reference procedure as a held-out
SKILL.md with its index and provenance.
The agents track is derived from tools, not from free-form personas. Every active operation
belongs to exactly one of 17 owner families, and each task’s oracle_agents
is the unique set of owners of its oracle operations.
Verify that all 91 operations have one and only one owner, derive task owners from the tool
annotation, and propagate each task’s approved oracle_skills into the agent row.
The lead remains tool-less; specialists receive owned operations and the reusable procedures.
Rank owner families by the frequency with which their operations are needed. Build an independent cumulative owner curriculum while preserving the tools-track train/test assignment.
Materialize owner capability skills, shared procedures, portable Codex agent TOMLs, routing descriptions, owned-operation lists, and per-stage manifests. No approved skill procedure is dropped.
All three tracks retain all 53 tasks. Bars show the adaptation and held-out test tasks arriving at each stage; the curve shows the cumulative operation, skill, or specialist pool. Hover or focus a stage to inspect every count.
Static schema, coverage, ownership, feasibility, new-capability pressure, and two-build reproducibility checks pass. The current local result remains preflight-only: its three official Hyper-τ smoke runs require both OpenAI and OpenRouter credentials and have not completed, so the page does not claim runtime validation or rewards.
This block installs the pinned environment and runs one official sealed outer-task verifier per tools stage. It performs real agent construction and evaluation, so it is intentionally expensive. Review the report before separately approving promotion.
News & community
Join the conversation—let us know what you think about EvoHarnessBench!
“We do this in everyday agent use: add useful tools, skills, or agents and move on, but rarely rethink what already worked: did this addition break anything? 👀
That is exactly the assumption we wanted to test in 🚀 EvoHarnessBench 🚀”
“Harness gains are easiest to overstate when search and final evaluation share one benchmark. Read RRSI, Rethinking the Evaluation of Harness Evolution, and EvoHarnessBench (in benchmarks) before trusting a leaderboard jump.”
“We do this in everyday agent use: add useful tools, skills, or agents and move on, but rarely rethink what already worked: did this addition break anything? 👀”
“Current self-evolving agents do not reliably recover from it. Work co-led with Zixuan Ke and Haizhou Shi, together with our amazing collaborators Yang Li, Ye Liu, Anurag Koul, Sarath Shekkizhar, Jiayu (Mila) Wang, Xuan Phi Nguyen, Semih Yavuz, Mohit Bansal, and Shafiq Joty at Salesforce Research and UNC Department of Computer Science!”
“What happens when an agent’s environment keeps changing—not just because the tasks change, but because its capabilities do? We study this overlooked form of continual adaptation, where tools 🔧, skills 📚, and specialist agents 🤖 are progressively added to the harness while previously solved tasks remain in evaluation. We find that expanding the harness itself can induce forgetting, and that current self-evolving agents do not reliably recover from it.”
“Check out our recent benchmark EvoHarnessBench with evolving tools, skills, and agents!”
“Each addition feels like an upgrade. But what if an evolving harness quietly makes the agent worse at tasks it could already solve? Our results show that it can. More surprisingly, even today’s self-evolving methods cannot reliably adapt to new capabilities while retaining earlier competence.”
“We keep expanding agentic systems with new tools, skills, and specialist agents. Each addition feels like an upgrade. But our new results show a surprising failure mode: as the harness evolves, agents can silently lose competence on tasks they could already solve. Salesforce Research introduces 🎉 EvoHarnessBench 🎉, a benchmark for studying this setting.”
“The part of an agent system that actually changes week to week is not the model. It is the harness: the MCP servers you connected, the skill files you wrote, the subagents you defined, the tool catalog that grew from twelve entries to two hundred because every integration request added one more. The model gets swapped maybe twice a year. The harness gets edited on a Tuesday afternoon.
Almost nobody re-runs the old tasks after that Tuesday afternoon. The new capability gets tested against the thing it was added for, it works, the PR merges. The tasks the agent was already solving are assumed to be fine, because nothing about them changed. Their tools are still there. Their prompts are still there. The model is the same model.”