A team of researchers from Google Cloud AI Research, Washington University in St. Louis and UNC Chapel Hill has released EnvHarness, a programmable layer that turns a static agent benchmark into one that adapts to the policy training on it. LLM agents now learn less from curated text and more from interactive environments, but those environments are hand-built and frozen: they behave identically no matter which agent is acting or how much it has improved. The usual fix is to generate new environments, which pins you to domain-specific pipelines and LLM-written verifiers that have to be over-generated and filtered. EnvHarness inverts the move. It wraps an existing environment in plug-in components that operate strictly through the standard reset() / step() interface, changing where an episode starts, what the agent may do, and what it sees, while the underlying simulator, tasks, and human-built verifier stay untouched. An LLM designer called EnvRigger writes those wrappers automatically against flaws it diagnoses in the policy’s own rollouts. Across five benchmarks in four domains, skills mined this way gain up to 9.0 points on held-out tasks with 9.8% fewer execution steps.
Is it deployable?
Yes, if you already run an agent eval loop. EnvHarness ships as Apache-2.0 Python with reproduction drivers for six environments. A new benchmark joins by implementing one interface (reset / step / observe / evaluate / get_env_state / save_state / from_state); nothing downstream changes. The hard prerequisite is a resettable environment, which rules out live user accounts and physical robots.
Environments that stop teaching
LLM agents now learn less from curated text and more from interactive environments. Those environments are hand-built and static: they behave identically no matter which agent acts or how much it has improved, so they cannot target a policy’s weakness and have nothing left to teach once solved.
The usual answer is generating more environments. The EnvHarness paper names two costs: generation pipelines are domain-specific and do not transfer, and LLM-written verifiers must be over-generated and heavily filtered without ever being fully trustworthy.
Wrapping, not authoring
The research team proposes the opposite move. An agent harness makes a frozen LLM capable through plug-in tools, memory and skills. EnvHarness applies that idea to the other side of the loop, wrapping a frozen environment in plug-in components that operate strictly through the standard reset() / step() interface.
Formally, a component is a transformation E' = w(E) that rewrites the state, action, observation and transition terms. The reward term is deliberately left out. Because no intervention reaches the simulator backend, every reshaped task keeps its original, human-built verifier, and because nothing touches benchmark-specific code, one implementation covers every domain.
Three components ship, and they compose freely:
- Stage replays a fixed action list after
reset(), so the episode starts somewhere else. Hiding the target mug in a closed drawer forces search instead of reach. - Contract installs per-step hooks on the action, transition and observation axes: block an action, rewrite a response, truncate an observation.
- Chain composes a second environment into the same episode under a shared step budget, with the composite verdict being the conjunction of both verifiers.
EnvRigger: the designer loop
Components are policy-agnostic; choosing them is not. EnvRigger treats the policy as a black box and runs four stages: it observes five baseline rollouts, diagnoses a systemic flaw, writes components as real Python, and validates on five fresh rollouts. Unsolvable and trivially solvable candidates are both rejected, with up to five revision rounds per task. Generated hooks compile in an isolated subprocess, so a bad mutation becomes a recorded trace rather than a dead run.
/* slide 2 */
function pulse(el){el.style.borderColor=”#FBBC04″;setTimeout(function(){el.style.borderColor=””;},500);}
document.getElementById(‘ehC1’).onclick=function(){pulse(this);};
document.getElementById(‘ehC2′).onclick=function(){pulse(this);};
/* slide 3 */
var CMP=[
{i:0,w:’Stage — replays a fixed action list on reset’,
c:’delta = [“take mug 1”, “open drawer 1”,n “put mug 1 in drawer 1”, “close drawer 1”]n# applied to s0 after reset(); s0′ = T(…T(s0,a1)…,ak)‘,
m:’The mug now starts hidden in a closed drawer. The agent has to search instead of reaching for it in plain sight.’},
{i:1,w:’Contract — rewrites actions, transitions, observations’,
c:’class _Contract(Contract):n def filter_observation(self, obs, env_state):n return truncate(obs, sentences=2) # f_On def filter_action(self, action, env_state): # f_An if is_teleport(action): return Blocked(“walk there”)n return action’,
m:’Room descriptions are cut to two sentences and teleport navigation is blocked, so spatial memory has to be built step by step.’},
{i:2,w:’Chain — composes another environment into the episode’,
c:’link = Chain(env_a=mug_task, env_b=“heat a potato andn put it on the countertop”)n# R′ = R_A AND R_B, each scored by its own verifier‘,
m:’One episode, two tasks, one shared step budget. The agent has to carry its goal past the point where it would have stopped.’}
];
function drawCmp(n){
var c=CMP[n];
document.getElementById(‘ehWtxt’).innerHTML=c.w;
document.getElementById(‘ehCode’).innerHTML=c.c;
document.getElementById(‘ehMug’).innerHTML=c.m;
for(var k=0;k<3;k++){document.getElementById(‘ehI’+k).setAttribute(‘fill’,k===c.i?’#4285F4′:’#5c636f’);}
var wr=document.getElementById(‘ehWrap’);
wr.setAttribute(‘stroke’,[‘#4285F4′,’#EA4335′,’#34A853’][n]);
document.getElementById(‘ehWtxt’).setAttribute(‘fill’,[‘#4285F4′,’#EA4335′,’#34A853’][n]);
var tabs=root.querySelectorAll(‘[data-cmp]’);
for(var j=0;j
‘rollout 3/5 · submit → blocked: “run the test suite…”nrollout 3/5 · $ pytest tests/ … 1 failednrollout 3/5 · fix → pytest ✓ → submit ✓nverdict: teaches verification → ACCEPT‘
];
document.getElementById(‘ehGo’).onclick=function(){
var st=root.querySelectorAll(‘.eh-step’),n=0;
for(var k=0;k<4;k++)st[k].className=”eh-step”;
document.getElementById(‘ehLog’).innerHTML=’…’;
var id=setInterval(function(){
if(n>0)st[n-1].className=”eh-step done”;
if(n>3){clearInterval(id);setTimeout(size,60);return;}
st[n].className=”eh-step live”;
document.getElementById(‘ehLog’).innerHTML=LOG[n];
n++;setTimeout(size,40);
},1250);
};
/* slide 5 */
var BM=[
{l:’ALFWorld · average success rate (%)’,mx:80,
d:[[‘No skills’,61.7],[‘Original envs’,62.4],[‘GenEnv’,62.6],[‘EnvHarness envs’,68.3]],
n:’Held-out task types gain the most: 61.4 → 70.4, the paper’s headline +9.0 points.’},
{l:’WebArena · average success rate (%)’,mx:55,
d:[[‘No skills’,38.7],[‘Original envs’,38.5],[‘VeriEnv’,39.6],[‘EnvHarness envs’,41.6]],
n:’Skills from the unmodified sites land below the no-skill baseline. Reshaping is what makes them worth mining.’},
{l:’SWE-bench Verified · resolved rate (%)’,mx:65,
d:[[‘No skills’,47.67],[‘Original envs’,49.88],[‘SWE-smith’,50.12],[‘EnvHarness envs’,52.58]],
n:’And shorter: 49.61 average steps against 55.01 for original-env skills, a 9.8% cut.’},
{l:’OfficeQA · exact match (%)’,mx:70,
d:[[‘No skills’,54.23],[‘Original envs’,54.40],[‘EnvHarness envs’,56.20]],
n:’No generation baseline exists for the office domain. EnvHarness reaches it through the same interface.’},
{l:’SpreadsheetBench · Pass@1 (%)’,mx:62,
d:[[‘No skills’,46.44],[‘Original envs’,45.88],[‘EnvHarness envs’,49.15]],
n:’Here original-env skills actively hurt, falling below the frozen agent by 0.56 points.’}
];
function drawBM(n){
var b=BM[n],h=””;
for(var k=0;k
‘+b.d[k][0]+’
‘+b.d[k][1].toFixed(1)+’


