Continual Harness: Online Adaptation for Self-Improving Foundation Agents
Paper: arXiv:2605.09998 Code: sethkarten/continual-harness Code reference:
main@bbab97ad(2026-05-12)
1. Motivation (研究动机)
当前 Coding Agent 已经有 Claude Code、OpenHands、OpenClaw 这类 harness:模型不只是一次性回答,而是被包在工具、记忆、计划、文件系统和长期上下文里,能在多轮环境中持续行动。但在 embodied agent,尤其是 Pokémon 这类长时程、部分可观测、需要导航、对话、战斗、背包管理和目标依赖的环境里,类似的“可持续自我改造 harness”还不成熟。PokeAgent Challenge 的结论是:如果只给 VLM 截图和按钮接口,哪怕是 frontier model 也几乎不能推进 RPG 游戏;真正的瓶颈不是单步视觉识别,而是长期 scaffolding、技能复用和错误积累后的纠偏。
这篇论文先从 Gemini Plays Pokémon (GPP) 的经验出发:人工观察直播轨迹、不断改 prompt、sub-agent、技能和记忆后,GPP 先后完成 Pokémon Blue、Yellow Legacy hard mode 和 Crystal,并且 Crystal 没有输掉 end-game battle。更关键的是,在 Yellow/Crystal 的后期,模型开始通过 long-context memory 自己迭代策略,例如创建 battle strategy、truth table、press sequence 等。这说明“harness 不是固定外壳,而是可以在运行过程中被改写的状态”。
论文要解决的具体问题是:给定一个最小环境接口 和按钮动作空间 ,能否让 agent 在不 reset 环境的单次长期运行中,在线改写自己的 prompt、sub-agents、skills 和 memory,并进一步把这些轨迹用于 open-source model 的在线训练?这个问题值得研究,因为很多真实 agent 场景——线上运维、代码库维护、长时程 embodied 任务——都没有便宜 reset;如果每次 prompt optimization 都要完整 episode + reset,就无法触达 late-game failure,也不能让经验在同一条轨迹中复利。
Figure 1 解读:这张图把论文的三层递进放在同一个 topology 中:环境、agent、harness、refiner。第一层是 GPP 中的人类 refiner;第二层是 Continual Harness,用自动 Refiner 代替人,在 episode 内改写 harness;第三层是 model + harness co-learning,把 open-source model 的参数 也放进循环里更新。核心问题因此从“模型会不会玩游戏”变成“模型能不能维护并进化自己的行动基础设施”。
2. Idea (核心思想)
核心洞察是:对长时程 agent 来说,真正应该在线学习的不只是 model weights,也不只是 system prompt,而是整个 harness state :prompt、sub-agents、skills、memory 都是 agent 策略的一部分。Continual Harness 把这些组件显式化为可 CRUD 的状态,并让 Refiner 周期性读取最近 trajectory window,针对失败模式写入局部修复。
与 GEPA 等 reset-based prompt optimization 的根本区别是:GEPA 通常在完整 episode 结束后改写 prompt 并 reset 到初始状态;Continual Harness 在同一条持续轨迹中每隔一段步数就更新 ,并把更新后的 harness 立即放回下一步上下文。因此它能处理只有 late game 才暴露的错误,例如复杂 battle、dialogue chain、多阶段 puzzle,而不是永远围绕 early game 失败做优化。
论文进一步把同一思想扩展到 model-harness co-learning:open-source policy 在会自我 refinement 的 harness 中 rollout,pairwise PRM 给低质量窗口打分,Gemini teacher relabel,再通过 soft SFT 得到 。这样 trajectory 同时驱动 harness 的 in-context 更新和模型参数的跨 iteration 更新。
3. Method (方法)
3.1 问题设定与 harness 状态
环境每一步给 agent 一个 rendered frame 和一个文本地图 ,agent 从固定按钮集合 中选择动作 。论文强调 是从人类可从屏幕读出的状态派生出来的可见区域文本图,不包含 walkthrough、全局目标列表或 pathfinding 答案,因此仍然是部分可观测问题。
Harness 是 foundation model 和环境之间的可变 scaffolding,包含四类状态:
- system prompt :每步给模型的策略、约束和行为规范;
- sub-agents :可被 orchestrator 调用的专用模块,例如 battle、puzzle、reflection;
- skills :可复用文本策略或可执行 Python 程序,例如 pathfinder、button macro、tool wrapper;
- memory :跨轨迹持续积累的事实、地点、策略和观察。
最小基线 只有 frame、text map、buttons 和 generic prompt;expert harness 则手工填入 A* pathfinding、type chart、damage calculator、curated objectives 等 domain scaffold;Continual Harness 从 开始,但允许自动 Refiner 在线改写上述四类组件。
3.2 两层循环:episode 内改 harness,iteration 间改模型
Figure 2 解读:左半部分是 single-episode harness refinement:Agent 读取 输出动作 ,每隔 步 Refiner 读取最近窗口 ,输出 ,然后执行 。右半部分是 co-learning:policy 在不断改写的 中走 步,PRM 评分,Gemini-3.1-pro relabel 低分窗口,soft SFT 得到 ;环境 state 不 reset,而是从 iteration 的结尾继续到 。
形式化地,inner loop 是普通 agent step: outer loop 是 harness refinement:warm-up 后每隔 步,Refiner 从最近窗口识别 failure signatures,产生 per-component edits: 直觉上,它像把“人类看日志后修 agent”的过程变成一组在线工具调用:导航循环说明需要 pathfinding skill,工具异常说明 skill code 要修,重复战斗推理说明需要 battle sub-agent,过期地点信息说明 memory importance 要降。由于旧 failure 仍保留在 trajectory/history 中,episode 越长,Refiner 可利用的错误证据越多;这正是 reset-free 相对 reset-based prompt optimizer 的优势。
3.3 Refiner 的四个 pass
Refiner 每轮读取最近轨迹,目标不是生成一个大 prompt,而是分别维护四个 store:
- Prompt pass:改写 orchestrator base prompt ,把最近观察到的失败、已学到的约束和行动策略写入下一轮上下文。
- Sub-agent pass:发现重复多步模式,创建 handler;检查已有 sub-agent 是否超出 safety cap、失败或需要精简;删除低效 sub-agent。
- Skill pass:从成功 action sequence 或反复
run_code中抽取可复用 skill;修复 exception、字段错误、API 使用错误;更新 effectiveness。 - Memory pass:补足最近地点/事件中没有记录的知识,更新 stale entry,降低已经离开的区域的重要度。
released code 中这四个 pass 对应 agents/utils/harness_evolver.py 的 HarnessEvolver.evolve():它先用 PromptOptimizer.get_recent_trajectories(num_trajectory_steps) 取窗口,然后独立调用 _evolve_prompt、_evolve_subagents、_evolve_skills、_evolve_memory;任一 pass 失败不会阻断其他 pass,最后 _save_evolution_log 记录 generation 结果。
论文公式与 released code 实现差异:论文用抽象的 warm-up 和 frequency 描述 refinement;released code 的 HarnessEvolver.should_evolve() 固定 MIN_WARMUP_STEPS=25,前 200 步每 25 步 evolve,一旦超过 200 步改为每 100 步 evolve,并且忽略调用方传入的 frequency 参数。README 示例仍使用 --optimization-window-length 100,这在代码里主要控制读入多少最近 trajectory,而不是触发频率本身。
3.4 Co-learning:PRM + teacher relabel + soft SFT
在线训练循环从 warm-up 后的 Gemma-4 模型开始。每个 iteration 让 在 full Continual Harness 中 rollout 步,pairwise PRM 对 transition/window 评分: 附录给出的 reward 组合是 trajectory progress 、action correctness 、reasoning quality 、format compliance 。低 reward 窗口由 Gemini-3.1-pro 作为 teacher relabel,然后对 relabeled shard 做 soft SFT: 关键不是 reward 单调上升,而是 reset-free state propagation:iteration 结束时保存的 emulator state 被作为 起点,所以 milestone progress 可以跨训练 iteration 累积。论文把这称为 model-harness co-learning,因为 通过 SFT 跨 iteration 变化, 则在每个 rollout 内由 Refiner 变化;二者共同决定下一批 trajectory 分布 。
3.5 代码级伪代码:基于 released implementation
class HarnessEvolver:
def should_evolve(self, current_step: int, frequency: int) -> bool:
if current_step < 25 or current_step <= 0:
return False
effective_freq = 25 if current_step <= 200 else 100
return current_step % effective_freq == 0
def evolve(self, current_step: int, num_trajectory_steps: int = 50) -> dict:
trajectories = self.prompt_optimizer.get_recent_trajectories(num_trajectory_steps)
if not trajectories:
return {"skipped": True, "reason": "no_trajectories"}
results = {}
for name, fn in [
("prompt", lambda: self._evolve_prompt(current_step, num_trajectory_steps)),
("subagents", lambda: self._evolve_subagents(trajectories, current_step)),
("skills", lambda: self._evolve_skills(trajectories, current_step)),
("memory", lambda: self._evolve_memory(trajectories, current_step)),
]:
try:
results[name] = fn()
except Exception as exc:
results[name] = {"error": str(exc)}
self.generation += 1
self._save_evolution_log(current_step, results)
return resultsdef evolve_subagents(text_vlm, store, trajectories, always_available_tools):
registry = store.get_tree_overview()
summary = format_trajectories_for_analysis(trajectories)
tool_failures = extract_tool_failures(trajectories)
prompt = build_subagent_refiner_prompt(registry, summary, tool_failures)
recs = parse_json(text_vlm.get_text_query(prompt, "HarnessEvolver_Subagents"))
created, updated, retired = [], [], []
for spec in recs.get("create", []):
tools = [t for t in spec.get("available_tools", []) if t in always_available_tools]
entry = store.add(
path=f"evolved/{spec['name'].lower().replace(' ', '_')}",
name=spec["name"],
description=spec.get("description", ""),
handler_type=spec.get("handler_type", "looping"),
max_turns=min(spec.get("max_turns", 25), 50),
available_tools=tools or ["press_buttons"],
system_instructions=spec.get("system_instructions", "")[:12000],
directive=spec.get("directive", "")[:12000],
return_condition=spec.get("return_condition", "Task completed"),
importance=3,
source="evolved",
)
created.append(entry.id)
for upd in recs.get("update", []):
store.update(upd["id"], **{k: v for k, v in upd.items() if k != "id"})
updated.append(upd["id"])
for sid in recs.get("retire", []):
store.delete(sid)
retired.append(sid)
return {"created": created, "updated": updated, "retired": retired}def evolve_skills(text_vlm, skill_store, trajectories):
summary = format_trajectories_for_analysis(trajectories)
run_code_count = count_tool_calls(trajectories, "run_code")
run_skill_count = count_tool_calls(trajectories, "run_skill")
antipattern = run_code_count >= 3 and run_skill_count == 0
prompt = build_skill_refiner_prompt(
skill_store.get_tree_overview(), summary, antipattern_warning=antipattern
)
recs = parse_json(text_vlm.get_text_query(prompt, "HarnessEvolver_Skills"))
result = {"created": [], "updated": []}
for spec in recs.get("add", []):
entry = skill_store.add(
path=spec.get("path", "general"),
name=spec["name"],
description=spec.get("description", ""),
code=spec.get("code"),
effectiveness=spec.get("effectiveness", "medium"),
importance=spec.get("importance", 3),
source="evolved",
)
result["created"].append(entry.id)
for upd in recs.get("update", []):
skill_store.update(upd["id"], **{k: v for k, v in upd.items() if k != "id"})
result["updated"].append(upd["id"])
return resultdef evolve_memory(text_vlm, memory_store, trajectories):
locations = sorted({
traj.get("pre_state", {}).get("location") or traj.get("location")
for traj in trajectories
if traj.get("pre_state", {}).get("location") or traj.get("location")
})
prompt = build_memory_curator_prompt(
memory_store.get_tree_overview(), locations, format_trajectories_for_analysis(trajectories)
)
recs = parse_json(text_vlm.get_text_query(prompt, "HarnessEvolver_Memory"))
result = {"created": [], "updated": []}
for spec in recs.get("add", []):
entry = memory_store.add(
path=spec.get("path", "general"),
title=spec.get("title", "Untitled"),
content=spec.get("content", ""),
importance=spec.get("importance", 3),
source="evolved",
)
result["created"].append(entry.id)
for upd in recs.get("update", []):
memory_store.update(upd["id"], **{k: v for k, v in upd.items() if k != "id"})
result["updated"].append(upd["id"])
return result3.6 Code-to-paper mapping
Code reference:
main@bbab97ad(2026-05-12) — pseudocode and mapping based on this commit
| Paper Concept | Source File | Key Class/Function |
|---|---|---|
| scaffold | README.md; scripts/test_scaffolds_red.sh | --scaffold continualharness; --enable-prompt-optimization |
| Agent runtime and tool dispatch | agents/PokeAgent.py | PokeAgent.__init__, _execute_function_call_by_name, scaffold/tool setup |
| Harness refinement scheduler | agents/utils/harness_evolver.py | HarnessEvolver.should_evolve, HarnessEvolver.evolve |
| Prompt evolution | agents/utils/harness_evolver.py; agents/utils/prompt_optimizer.py | _evolve_prompt, PromptOptimizer |
| Sub-agent CRUD | agents/utils/harness_evolver.py; utils/stores/subagents.py | _evolve_subagents, process_subagent store API |
| Skill CRUD and executable skill repair | agents/utils/harness_evolver.py; utils/stores/skills.py | _evolve_skills, process_skill, run_skill |
| Memory curation | agents/utils/harness_evolver.py; utils/stores/memory.py | _evolve_memory, process_memory |
| Minimal environment actions/tools | agents/tools/registry.py; server/game_tools.py | press_buttons, get_game_state, get_map_data |
| Continual harness prompts | agents/prompts/pokeagent-directives/continual-harness/SYSTEM_PROMPT.md; agents/prompts/pokeagent-directives/continual-harness/SYSTEM_PROMPT_RED.md; agents/prompts/pokeagent-directives/continual-harness/BASE_ORCHESTRATOR_POLICY.md | system prompt and base orchestrator policy |
released code 只包含 harness/runtime 与 benchmark infrastructure;我没有在 main@bbab97ad 中找到 SFT/GRPO/PRM 的训练 launch scripts(grpo/prm/unsloth/soft sft 相关搜索基本为空),因此训练超参数在本笔记中标注为来自论文附录 appendices/training.tex 和表格源码,而不是来自 released repo 的训练配置文件。
4. Experimental Setup (实验设置)
4.1 环境、数据与评价对象
实验环境是 Pokémon Red 和 Pokémon Emerald,外加 GPP 叙述中的 Blue、Yellow Legacy hard mode 和 Crystal。Red/Crystal 原生分辨率 ,Emerald 为 ,给 VLM 时上采样 ;每个 observation step 推进 emulator 120 frames,以让菜单、战斗文字和行走动画稳定下来。动作空间是 8 个按钮;主指标是 standardized milestone evaluation 中的 cumulative button presses to milestone。Emerald 完整 milestone set 有 31 个 canonical milestones;主图中的 early Emerald sequence 展示到第 2 个 gym Knuckle Badge 的 9 个 milestone,Red 主图展示到 Thunder Badge 的 11-milestone subset。
训练/评测数据包括:Gemini-3.1-pro Continual Harness gameplay 轨迹(用于 open-source SFT)、teacher-visited states(用于 offline GRPO,每个 state 生成 个候选)、20 个 held-out transitions(Gemma-4 warm-up eval matrix),以及 reset-free online DAgger+PRM rollout 中每 iteration 的 步轨迹。论文没有给出 SFT teacher-trajectory set 的总样本数;表格明确说明 held-out eval 为 20 samples/transitions。
4.2 Baselines / harness conditions
- :frames、local text map、buttons、generic prompt;无 sub-agent、memory、skills。
- :PokeAgent/GPP 手工 harness,含 built sub-agents、A* pathfinding、type chart、damage calculator、curated objectives。
- :从 出发,分为 from scratch、bootstrap frozen、bootstrap updating 三个变体。
- 模型:Gemini 3 Pro / Flash / Flash-Lite 用于 frontier harness 实验;Gemma-4 E2B、E4B、26B MoE、31B dense 用于 open-source transfer;Qwen3.5 27B/35B 作为 cross-family negative control。
4.3 Training config
- SFT:Gemma-4 variants 通过 LoRA 训练,rank ,,bf16,8K context,Unsloth + H200 GPUs;样本是
(screenshot, harness prompt, teacher response);learning rate ,3% linear warmup,cosine decay,每个模型对 teacher trajectory set 训练 1 pass。 - Offline GRPO:每个 teacher-visited state 采样 completions;Gemini-3-flash-preview per-step oracle 评分,action correctness 权重 、format compliance 权重 ;group-normalized advantages;LR ,KL ,batch size 8 states/optimizer step,590 total optimization steps。
- Online co-learning:每 iteration 步;PRM 是 Gemini-3-flash-preview;reward 权重为 progress 、action correctness 、reasoning quality 、format compliance ;Gemini-3.1-pro relabel 低分窗口;soft SFT 为 3 epochs、LR ;PRM stride 8;batch size 是 reset-free 的 1 条持续 trajectory。
5. Experimental Results (实验结果)
5.1 GPP 证明 harness refinement 可以完成完整 RPG
GPP 先以人类 in-the-loop refinement 完成 Pokémon Blue、Yellow Legacy hard mode 和 Crystal。Yellow/Crystal 后期的重要观察是:模型不是均匀地创建大量模块,而是反复更新少数关键组件,例如 navigation、battle strategy、notepad truth table、press sequence。论文把这个现象作为 Continual Harness 的经验基础:如果 harness 的变化集中在少数高价值组件,自动 Refiner 也应该围绕这些组件做 CRUD,而不是生成一次性的长 prompt。
Figure 3 解读:Yellow Legacy run 的 CRUD 操作在 2,000-turn bins 中持续出现,说明 harness 没有很快收敛为固定 scaffold;右侧 top-updated components 显示大部分更新集中在少数 navigation/battle 相关组件。这支持论文的“recurrent refinement rather than one-shot design”观点。
Figure 4 解读:battle strategist prompt 在 Elite Four 阶段的节点数、decision gates、depth 和 fan-out 先增长、再重构,最终把 per-decision logic 吸收到 master_battle_agent 的 dispatch 结构中。它说明自我改造不等于无限堆 prompt;有效 harness evolution 也会压缩和重组。
5.2 Continual Harness 缩小到 expert harness 的效率差距
Figure 5 解读:横轴是 cumulative button presses,纵轴是 milestone。Red 图覆盖到 Thunder Badge 的 11-milestone subset,Emerald 图覆盖到 Knuckle Badge 的 9-milestone sequence,x-axis capped at 8.5k。Continual Harness 在两款游戏中都相对 降低每个 monitored milestone 的按钮成本,并恢复了 到 expert harness 之间的大部分效率差;残余差距主要在 dialogue-heavy gym interiors 和多轮 battle strategy。
Bootstrap-updating 在 Red 上每个 milestone 都比 from-scratch 更高效,说明 prior run 中 refinement 出来的 harness state 可以迁移到下一次 run;但 frozen/updating 的差别也表明继续 refinement 仍有增益,不只是继承已有 skills。
5.3 效果依赖模型能力:Pro 有 Pareto 优势,Flash-Lite 低于能力地板
Figure 6 解读:Emerald cost-completion Pareto 图把 24-hour seeds 的完成比例和 Gemini API spend 放在同一平面。Gemini 3 Pro 上,from-scratch 以 $130 median 达到 100% milestones,而 是 $215 达到 98%,约 40% cost reduction 且无 completion loss;两个 bootstrap variants 在 Pro 上达到 96–100%,成本 $110–$140。Flash 上收益高方差,bootstrap-updating 为 80% at $42,略高于 的 77% at $30;Flash-Lite 的 为 20% at $11,而所有 Continual Harness variants 只有 3–13%。结论是:harness 可以放大能力,但不能替代模型使用 harness 的基本能力。
5.4 技能 self-improvement 接近 oracle
Figure 7 解读:左图用 Dijkstra oracle 衡量 top-10% evolved navigation skills 的 path-cost deficit,右图统计 navigation-skill invocations。 从不调用 navigation skill;Continual Harness 条件在 24 小时 run 中积累数百次调用。from-scratch 的 path-cost deficit 从接近 half-cost penalty 快速下降到 single digits 并保持,说明技能是在 episode 内通过失败诊断和修复变好,而不是离线一次性手工写好。
5.5 Open-source model 的 warm-up 与 reset-free co-learning
Gemma-4 warm-up 的精确表格显示:单靠 SFT/Offline GRPO 主要改善 tool format 和 action quality,还不能产生主要 live milestone advance。Red held-out 20 transitions 上,Base Gemma-4 26B 为 tool_fmt=0.05, actionable=0.25, action_relevance=0.33, reasoning=0.25, grounding=0.23;SFT Gemma-4 31B 提升到 0.50, 0.50, 0.75, 0.65, 0.44;Offline GRPO heuristic 26B 为 0.50, 0.50, 0.65, 0.50, 0.44;Offline GRPO Gemini-oracle 26B 为 0.50, 0.50, 0.55, 0.30, 0.40。Qwen3.5 35B 虽能产生 parseable tool calls,但 live game progress 为 0 / stuck。
Emerald eval matrix 中,SFT 26B 在 tool_format=0.95、actionable=0.95、grounding=0.72 上最好;但 GRPO Emerald prompt set 出现 degenerate completion,tool_format=0.00、actionable=0.00。Red matrix 中,31B SFT 是 viable checkpoint,action_relevance=0.75、reasoning_similarity=0.65 最高;26B Red SFT adapter 因 eval-time degenerate 被省略。
Figure 8 解读:A 面板显示 SFT 能把 tool-calling format 从接近 0 拉起来;B 面板显示两类 offline GRPO 的 reward curves,相对于 SFT baseline 有改进但并不等价于 live milestone progress。也就是说,format/reward warm-up 是必要准备,但论文的核心结果来自后续 reset-free online loop。
Figure 9 解读:五条 advancing runs 的 milestone index 随 training iteration 增长;filled dots 表示 beginning-of-game,open rings 表示 mid-game checkpoint,stars 是 judge-verified advances,+N 是 net objective gain。虚线 untrained Gemma-4 baseline 是 zero advance beyond starting milestone。早期和中期 checkpoint 都能推进,说明训练信号不是只拟合 early-game distribution;更重要的是每条曲线都是同一 agent 的连续 in-game trajectory,而不是独立 reset rollout 的平均。
5.6 限制与开放问题
作者明确承认 co-learning 仍依赖 frontier teacher:Gemma-4 up to 31B 还不足以同时担当 teacher 和 trainee;co-learning horizon 未证明收敛,只报告了已运行训练区间内的 sustained milestone progress;reset-free 与传统 reset batch accumulation 的同任务 head-to-head comparison 仍然开放。另一个实际限制是 memory/context bottleneck:长轨迹让 harness 有更多证据,但也会带来上下文、检索和 stale memory 管理压力。
Figure 10 解读:memory bottleneck 图强调了 Continual Harness 的代价面:长期运行需要保存并检索越来越多 trajectory、memory、skills 和 sub-agent 版本;如果 memory curation 不足,旧信息会污染后续决策。因此本文的方法不是“无限加记忆”,而是依赖 Refiner 持续做 importance rebalance、stale entry update 和组件删除。