SkillOpt: Executive Strategy for Self-Evolving Agent Skills
Paper: arXiv:2605.23904 Code: microsoft/SkillOpt Code reference:
main@99212e39(2026-05-26)
1. Motivation(研究动机)
现有 Agent skill 的核心问题不是“不会写一段提示词”,而是缺少一种像训练神经网络那样可控、可复现、可用 held-out feedback 验证的优化过程。论文把当前方法分成三类:人工手写 skill、一次性 LLM 生成 skill、以及 Trace2Skill / TextGrad / GEPA / EvoSkill 这类从轨迹或文本反馈中自我修订的方案。它们都能把经验写回文本,但通常缺少明确的 step size、严格的 validation gate、rejected update 记忆和跨 epoch 的稳定更新机制,因此一次看似合理的自我修改可能覆盖已有好规则、过拟合局部失败样例,或者在真实 harness 上退化。
SkillOpt 试图解决的具体目标是:在不更新 target model weights 的前提下,把一个自然语言 skill document 当作 frozen agent 的外部可训练状态来优化。给定 target model 、执行 harness 、任务 和当前 skill ,系统只改变 ;target model、backend、工具环境和 benchmark evaluator 都保持固定。这样要回答的问题是:skill 的 add / delete / replace 编辑能否被组织成一个有训练集、验证集、测试集、有学习率、有 rejected feedback 的优化循环,而不是一次性的 prompt rewriting。
这个问题值得研究,是因为 agent 的部署环境越来越像 software execution loop:direct chat、Codex、Claude Code、spreadsheet/runtime、document QA、embodied simulator 都可能有不同 harness,但共享一个事实——很多能力缺口并不需要改模型权重,而是需要可审计、可迁移、可部署的程序性操作规则。若能把这些规则训练成一个 compact best_skill.md,部署时只需要把该文件放入 agent context,不增加 optimizer model calls,也不需要继续保留训练时的 rollout buffer 或 meta skill。
更细地说,论文瞄准的是“procedural knowledge gap”:模型本身可能知道很多事实,但在受限输出格式、工具调用顺序、表格证据绑定、搜索 frontier 管理等任务中,经常缺少稳定执行纪律。人工 skill 可以写出一部分纪律,但难以覆盖每个 benchmark 的隐性失败模式;一次性 LLM skill 又没有机会从真实 rollout 中看到系统性错误。因此,作者把 skill optimization 放在 agent adaptation 的中心位置:不改模型参数、不额外训练 adapter,而是让经验驱动的文本规则逐步收敛。
这也解释了为什么论文强调 held-out selection split。没有 selection gate 时,self-evolving skill 很容易把局部错误归纳成过强规则,或者把成功轨迹中的偶然步骤写成必做步骤。SkillOpt 的动机不是追求更“聪明”的反思文本,而是让每一次文本修改都有外部执行证据约束:训练集负责暴露模式,选择集负责阻止退化,测试集只用于最终泛化报告。
2. Idea(核心思想)
SkillOpt 的核心洞见是:skill document 可以被看作一种“文本空间参数”,其优化也需要类似深度学习 optimizer 的结构约束。rollout evidence 类似 forward pass,optimizer model 对成功/失败轨迹的 reflection 类似 backward pass,textual learning rate 限制每步最多应用多少编辑,held-out validation gate 决定候选 skill 是否能进入当前状态。
关键创新不是再提出一种“让 LLM 反思并改提示词”的流程,而是把自我演化 skill 变成一个受控训练循环:单独的 optimizer model 读取 scored trajectories,提出结构化 add/delete/replace patch;patch 先被合并、排序、按 截断,再在 selection split 上验证;未通过验证的编辑进入 rejected-edit buffer,成为后续 optimizer prompt 的负反馈。
与 GEPA / TextGrad 这类文本优化或 EvoSkill / Trace2Skill 这类经验蒸馏方法相比,SkillOpt 的根本差异在于它不把反思结果无条件写入 skill,也不无限扩张 skill library,而是优化一个单文件 skill artifact,并用严格的 “candidate score must be greater than current score” gate 来约束每次状态转移。这使它更像可复现的离线训练,而不是松散的 prompt search。
可以把它理解成一个三层分工:第一层是 frozen target model,只负责在当前 skill 下执行任务;第二层是 optimizer model,只在离线阶段读取轨迹并提出 patch;第三层是 validation gate,只根据真实 selection score 决定是否接受 patch。这个分工让 SkillOpt 可以使用比 target 更强的 optimizer,却不会在部署时增加推理成本;也可以把同一个优化接口接到 Codex / Claude Code 等不同 harness 上,因为优化对象始终是外部文本 skill。
另一个关键点是“bounded edits 而不是 full rewrite”。full rewrite 给 optimizer 太大自由度,容易擦除已有有效规则;bounded add/delete/replace 则像小步梯度下降,让 skill 保持历史连续性。rejected-edit buffer 和 slow/meta update 则分别对应短期负反馈与长期记忆:前者提醒 optimizer 刚才哪些方向伤害了 score,后者把跨 epoch 的稳定经验固化到受保护区域或 optimizer-side memory 中。
3. Method(方法)
Overall framework:把 skill 当成 frozen agent 的外部状态
Figure 1 解读:图中 target model 是被冻结的执行者,当前 skill 被注入 agent context;optimizer model 不参与部署,只在离线训练阶段读取 rollout traces、提出 bounded edits,并通过 held-out gate 决定是否接受。被接受的编辑成为可部署的 best_skill.md,被拒绝的编辑不会进入目标 agent,但会作为负反馈供后续 optimizer prompt 使用。
形式化地,skill 是插入 agent context 的自然语言 policy。对于 harness 、任务 、skill 和 frozen target model ,执行产生轨迹与分数: 给定训练、选择和测试划分 ,SkillOpt 用训练集生成候选 skill 集合 ,在 selection split 上选择 best skill,并只在 test split 上报告最终性能: 直觉上,SkillOpt 有效的原因在于它把“从失败经验中写规则”拆成两个互相制衡的部分:optimizer model 负责扩大搜索,validation gate 负责保守选择。LLM 的 reflection 很擅长提出 plausible rule,但这些规则是否真的让目标模型在目标 harness 中变好,只有重新执行 selection split 才知道;因此 SkillOpt 宁愿拒绝大量看起来合理的修改,也只把少数经验证的规则写入部署 artifact。
Pipeline:rollout → reflection → bounded patch → validation gate
Figure 2 解读:左侧是 dataset split:训练集只供应 rollout evidence,validation split 只用于 update gate,test split 只用于最终报告。中间的 rollout batch 会被切成成功/失败 minibatches,optimizer model 分别生成 个 atomic edits;这些 edits 经 batch-level merge、ranking 和 learning-rate clipping 后形成候选 skill 。右侧强调 current skill 与 candidate skill 之间不是自由重写,而是受 控制的局部状态转移。
3.1 Forward pass:rollout evidence
每个 optimization step 先让 target model 使用当前 skill 在 上执行一个 rollout batch。harness 记录 task metadata、messages、tool calls、observations、command outputs、final answers、verifier feedback,以及 benchmark-specific context,例如 spreadsheet preview、document reference 或 compact execution trace。batch 太小会更新快但噪声大,batch 太大则降低迭代频率;released code 通过 scripts/train.py 的 --batch_size 和 per-benchmark YAML 控制该折中。
在代码中,skillopt/engine/trainer.py 的 ReflACTTrainer.train 负责训练主循环;不同 benchmark 的 adapter / rollout 负责把 native environment 转成 scored trajectories,例如 skillopt/envs/spreadsheetbench/rollout.py、skillopt/envs/searchqa/rollout.py、skillopt/envs/alfworld/rollout.py。
def collect_rollout_batch(adapter, target_model, skill_text, batch_size, seed):
batch = adapter.build_train_env(batch_size=batch_size, seed=seed)
results = []
for task in batch.items:
trace = adapter.run_with_skill(model=target_model, task=task, skill=skill_text)
score = adapter.evaluate(trace.final_answer, task.gold)
results.append({"task": task, "trace": trace, "hard": score > 0, "score": score})
return results3.2 Backward pass:minibatch reflection
Optimizer model 先把 rollout results 分成 failures 和 successes,再按 reflection minibatch size 分组。失败 minibatch 负责提出缺失规则或纠错规则;成功 minibatch 负责保留已经有效的行为,避免 optimizer 只会“修 bug”却破坏已有策略。skillopt/gradient/reflect.py 的 run_minibatch_reflect 明确执行了这一分流:failures = [r for r in results if not r.get("hard")],successes = [r for r in results if r.get("hard")],之后分别写出 minibatch_fail_*.json 和 minibatch_succ_*.json patch proposals。
def reflect_minibatches(results, optimizer, skill, minibatch_size, edit_budget):
failures = [r for r in results if not r["hard"]]
successes = [r for r in results if r["hard"]]
patches = []
for group in split_minibatches(failures, minibatch_size):
patches.append(optimizer.propose_patch(
prompt="analyst_error", skill=skill, trajectories=group, max_edits=edit_budget
))
for group in split_minibatches(successes, minibatch_size):
patches.append(optimizer.propose_patch(
prompt="analyst_success", skill=skill, trajectories=group, max_edits=edit_budget
))
return patches3.3 Bounded text updates:文本学习率
SkillOpt 的 learning-rate analogue 是 edit budget :第 步最多应用多少条 skill edits。optimizer 先合并 failure-driven 和 success-driven proposals,再让 ranking prompt 返回优先级最高的 edit indices,最后只保留前 条。released code 中对应 skillopt/gradient/aggregate.py 的 merge_patches 和 skillopt/optimizer/clip.py 的 rank_and_select;后者会构造 ranking prompt,并要求 optimizer 返回 selected_indices。
def bounded_update(skill, raw_patches, optimizer, max_edits, meta_skill_context=""):
merged_patch = merge_patches(raw_patches, failure_first=True)
selected_patch = rank_and_select(
skill_content=skill,
patch=merged_patch,
max_edits=max_edits,
meta_skill_context=meta_skill_context,
update_mode="patch",
)
candidate_skill, apply_report = apply_patch_with_report(skill, selected_patch)
return candidate_skill, selected_patch, apply_reportpatch 模式使用局部操作,而不是任意全文重写。skillopt/optimizer/skill.py 的 _apply_edit_with_report 支持 append、insert_after、replace、delete,并且会跳过 protected slow-update region;如果 append 时存在 slow-update field,代码会把新内容插到该 region 之前。这是论文中“fast local edits 与 slower epoch-wise consolidation 分离”的工程实现。
3.4 Validation gate 与 rejected-edit buffer
每个 candidate skill 都用同一个 frozen target model 和同一个 harness 在 上评估。若 candidate score 严格大于 current score,则进入 current skill;若也超过历史 best score,则更新 best_skill.md。否则该候选被拒绝。released code 的 skillopt/evaluation/gate.py 的 evaluate_gate 是一个纯函数,核心条件就是 if cand_hard > current_score;ties 会走 reject 分支。
def validation_gate(candidate, cand_score, current, current_score, best, best_score):
if cand_score > current_score:
current, current_score = candidate, cand_score
if cand_score > best_score:
return "accept_new_best", current, current_score, candidate, cand_score
return "accept", current, current_score, best, best_score
return "reject", current, current_score, best, best_score被拒绝的编辑不会被部署,但它们不是浪费:训练器会把 rejected edits、score drop 和观察到的 failure patterns 写入 epoch-local buffer,后续 reflection prompt 可看到这些负反馈。这样 optimizer 不会在同一 epoch 里反复提出已经伤害 selection score 的方向。
3.5 Epoch-wise slow/meta update
Fast update 学当前 batch;slow/meta update 学跨 epoch 的稳定趋势。每个 epoch 末,SkillOpt 在同一批 sampled tasks 上比较 previous epoch skill 与 current epoch skill,把样例分成 improvements、regressions、persistent failures、stable successes。slow update 写入 skill document 的 protected slow-update field,仍然要经过 selection gate;meta skill 则只存在于 optimizer side,被 prepended 到后续 reflection / merge / ranking prompt,不随 best_skill.md 部署。
def epoch_boundary_update(prev_skill, curr_skill, comparison_pairs, optimizer, val_set):
slow_guidance = run_slow_update(
prev_skill=prev_skill,
curr_skill=curr_skill,
comparison_pairs=comparison_pairs,
)
candidate = replace_slow_update_field(curr_skill, slow_guidance["content"])
if evaluate(candidate, val_set) > evaluate(curr_skill, val_set):
curr_skill = candidate
meta_skill = run_meta_skill(
prev_skill=prev_skill,
curr_skill=curr_skill,
comparison_pairs=comparison_pairs,
)
return curr_skill, meta_skill论文公式与 released code 实现差异:论文实验设置写到 LiveMathematicianBench 使用每 epoch 35 个 training items 且 rollout batch 200,ALFWorld 使用 39 train / 140 selection / 134 test;但 main@99212e39 的公开配置 configs/livemathematicianbench/default.yaml 中 train.batch_size=40,没有随仓库发布对应 batch_size=200 的 launch script 或 override。笔记中的训练配置因此同时记录 paper protocol 与 released config,并把论文级数字视为实验报告、把 repo YAML 视为可复现代码默认值。
Algorithm 1:完整优化循环
def skillopt_train(model, optimizer_model, harness, splits, initial_skill, config):
current_skill = initial_skill
best_skill = initial_skill
current_score = evaluate(model, harness, current_skill, splits.selection)
best_score = current_score
score_cache = {hash_skill(initial_skill): current_score}
rejected_buffer = []
meta_skill = ""
for epoch in range(config.num_epochs):
rejected_buffer.clear()
for rollout_batch in harness.iter_train_batches(splits.train, config.batch_size):
traces = harness.run(model, rollout_batch, current_skill)
raw_patches = reflect_minibatches(
traces, optimizer_model, current_skill,
minibatch_size=config.minibatch_size,
edit_budget=config.learning_rate,
)
candidate, patch, report = bounded_update(
current_skill, raw_patches, optimizer_model,
max_edits=config.lr_schedule.step_budget(),
meta_skill_context=meta_skill,
)
cand_score = score_cache.get(hash_skill(candidate))
if cand_score is None:
cand_score = evaluate(model, harness, candidate, splits.selection)
score_cache[hash_skill(candidate)] = cand_score
action, current_skill, current_score, best_skill, best_score = validation_gate(
candidate, cand_score, current_skill, current_score, best_skill, best_score
)
if action == "reject":
rejected_buffer.append({"patch": patch, "score": cand_score, "report": report})
if config.use_slow_update or config.use_meta_skill:
pairs = compare_previous_and_current_epoch_skills(harness, model, splits.train)
if config.use_slow_update:
current_skill = maybe_apply_slow_update(current_skill, pairs, splits.selection)
if config.use_meta_skill:
meta_skill = update_optimizer_side_meta_skill(meta_skill, pairs, rejected_buffer)
test_score = evaluate(model, harness, best_skill, splits.test)
save_text("best_skill.md", best_skill)
return best_skill, test_scoreCode-to-paper mapping
Code reference:
main@99212e39(2026-05-26) — pseudocode and mapping based on this commit
| Paper Concept | Source File | Key Class/Function |
|---|---|---|
| 训练主循环、current/best skill 状态、score cache、rejected buffer | skillopt/engine/trainer.py | ReflACTTrainer.train, _format_step_buffer, _save_skill |
| 失败/成功 minibatch reflection | skillopt/gradient/reflect.py | run_minibatch_reflect |
| failure/success patch aggregation | skillopt/gradient/aggregate.py | merge_patches, _hierarchical_merge |
| textual learning rate / top- edit selection | skillopt/optimizer/clip.py | rank_and_select |
| patch application and protected slow-update field | skillopt/optimizer/skill.py | apply_patch_with_report, _apply_edit_with_report |
| validation gate | skillopt/evaluation/gate.py | evaluate_gate, GateResult |
| slow update | skillopt/optimizer/slow_update.py | run_slow_update, replace_slow_update_field |
| optimizer-side meta skill | skillopt/optimizer/meta_skill.py | run_meta_skill, format_meta_skill_context |
| config inheritance and CLI overrides | skillopt/config.py, scripts/train.py | load_config, flatten_config, apply_overrides |
| benchmark adapters / rollouts | skillopt/envs/*/adapter.py, skillopt/envs/*/rollout.py | per-benchmark build_train_env, rollout/evaluator functions |
4. Experimental Setup(实验设置)
Benchmarks and data splits
| Benchmark | 任务形态与规模信息 |
|---|---|
| SearchQA | 单轮 QA;论文使用 native hard score / exact-match accuracy。公开配置 configs/searchqa/default.yaml 设置 train_size=400、batch_size=40、split_dir=data/searchqa_split。完整 train/selection/test 样本总量论文未详细说明。 |
| SpreadsheetBench | 多轮 spreadsheet code/tool use,default mode=multi,最多 30 turns,真实 openpyxl / pandas runtime。公开配置 configs/spreadsheetbench/default.yaml 设置 train_size=80、batch_size=40、split_dir=data/spreadsheetbench_split。 |
| OfficeQA | 多轮 tool loop,最多 24 tool calls,search_mode=offline。公开配置 configs/officeqa/default.yaml 使用 split_dir=data/officeqa_split,具体样本数论文未详细说明。 |
| DocVQA | document / multimodal document QA。公开配置 configs/docvqa/default.yaml 使用 split_dir=data/docvqa/splits、batch_size=40;具体样本数论文未详细说明。 |
| LiveMathematicianBench | 数学 multiple-choice reasoning;论文说明 bounded training pool 为 35 training items per epoch,并报告 per-benchmark configs 会调整 batch size;公开配置默认 batch_size=40,见上文 paper-code 差异。 |
| ALFWorld | persistent embodied interaction,最多 50 steps per episode;论文说明 39 training tasks、140 selection environments、134 test environments;公开配置 configs/alfworld/default.yaml 使用 split_dir=data/ablation_splits/alfworld/2-1-7_seed42。 |
Dataset-backed runs 使用 deterministic train/selection/test split,split_seed=42。默认 split ratio 是 2:1:7;Table 2 的 train-size ablation 另固定为 4:1:5 train/selection/test。selection split 只用于接受或拒绝 candidate skill,headline scores 都来自 disjoint held-out test split。
Models, baselines, metrics, harnesses
- Target models:direct-chat 主表覆盖 GPT–5.5、GPT–5.4、GPT–5.4-mini、GPT–5.4-nano、GPT–5.2、Qwen3.5–4B、Qwen3.6–35B-A3B;Codex / Claude Code harness 主要报告 GPT–5.5。
- Optimizer model:主要设置使用 GPT–5.5 作为 optimizer;Table 5 额外比较 strong optimizer (GPT–5.5) 与 target-matched optimizer。
- Baselines:No skill、Human skill、LLM skill、Trace2Skill、TextGrad、GEPA、EvoSkill。Codex / Claude Code harness 中还比较 EvoSkill-style agentic loop skill。
- Metrics:各 benchmark 的 native hard score 或 exact-match accuracy,表中均为百分比。non-obvious metrics 包括 ALFWorld hard success rate、SpreadsheetBench 执行结果正确率、LiveMathematicianBench MCQ accuracy。
Training config
论文默认设置:4 epochs;rollout batch size 40 per step;reflection minibatch size 8;16 analyst workers 并行 reflection;merge batch size 8;textual learning rate ;cosine decay floor ;strict held-out validation gate(candidate 必须严格超过 current selection score,ties rejected);slow update 每 epoch 比较 20 sampled tasks;optimizer-side meta skill enabled;patch edit mode;teacher reflection 每 minibatch 最多 3 refinement rounds;student 和 teacher calls 默认 medium reasoning effort。
released code 对应配置路径:configs/_base_/default.yaml 给出 train.num_epochs=4、train.batch_size=40、train.seed=42、gradient.minibatch_size=8、gradient.merge_batch_size=8、optimizer.learning_rate=4、optimizer.min_learning_rate=2、optimizer.use_slow_update=true、optimizer.slow_update_samples=20、optimizer.use_meta_skill=true;scripts/train.py 负责把 CLI overrides 映射到结构化 config。
硬件信息:论文未详细说明 GPU 类型或 GPU 数量;该方法主要消耗 LLM API / harness rollout tokens,而不是 GPU 训练。Table 6 把训练成本报告为 train tokens 与 cost / point。
5. Experimental Results(实验结果)
Main results
Table 1 的核心结论是 SkillOpt 在 52/52 个 evaluated (model, benchmark, harness) cells 上 best or tied-best。GPT–5.5 direct chat 从 no-skill 平均 58.8 提升到 82.3,绝对提升 +23.5 points;同时比“每个 cell 从 Human / LLM / Trace2Skill / TextGrad / GEPA / EvoSkill 中挑最佳”的 oracle baseline 平均高 +5.4 points。
| Setting | No skill | SkillOpt | Gain |
|---|---|---|---|
| GPT–5.5 direct SearchQA | 77.7 | 87.3 | +9.6 |
| GPT–5.5 direct SpreadsheetBench | 41.8 | 80.7 | +38.9 |
| GPT–5.5 direct OfficeQA | 33.1 | 72.1 | +39.0 |
| GPT–5.5 direct DocVQA | 78.8 | 91.2 | +12.4 |
| GPT–5.5 direct LiveMath | 37.6 | 66.9 | +29.3 |
| GPT–5.5 direct ALFWorld | 83.6 | 95.5 | +11.9 |
| GPT–5.5 Codex average | — | — | +24.8 over no skill |
| GPT–5.5 Claude Code average | — | — | +19.1 over no skill |
跨模型看,论文报告 direct-chat 平均增益分别为 GPT–5.4 +12.7、GPT–5.4-mini +15.4、GPT–5.4-nano +26.7、GPT–5.2 +16.6、Qwen3.5–4B +19.2、Qwen3.6–35B-A3B +9.1。小模型相对收益更明显:例如 GPT–5.4-nano 在 DocVQA 上接近翻倍,在 ALFWorld 上约三倍;Qwen3.5–4B 的 SpreadsheetBench 从 9.3 到 23.9。
Ablations and trends
Figure 3 解读:三个子图分别展示 SpreadsheetBench、SearchQA、LiveMath 的 epoch checkpoints。train rollout、selection-best 和 held-out test 的趋势不完全一致,说明只看训练轨迹会误判 skill 质量;selection gate 通常能更好地追踪最终泛化点。图中也能看到一些 step 在 train score 上升但 selection/test 不接受,这正是 rejected-edit buffer 可以利用的负反馈来源。
Table 2 显示 evidence 与 batch-size 对不同任务的敏感度不同:train evidence 从 1 example 增到 100% train 时,SpreadsheetBench 从 47.5 到 78.0,LiveMath 从 59.1 到 70.5;SearchQA 在 20% train 后基本饱和在 84–86。reflection minibatch size 从 1 到 32 时,SearchQA 维持在 85.9–87.1,SpreadsheetBench 维持在 75.4–77.9;rollout batch size 从 8 到 full epoch 时,SearchQA 在 85.1–87.2,SpreadsheetBench 在 75.0–77.5。
| Component | Default / ablation | SearchQA | SpreadsheetBench | LiveMath |
|---|---|---|---|---|
| Learning-rate form | lr=4 default | 87.1 | 77.5 | 61.3 |
| Learning-rate form | dynamic lr | 85.8 | 71.8 | 54.0 |
| Learning-rate form | without lr | 84.6 | 75.7 | 57.3 |
| Rejected buffer | with rejected buffer | 87.1 | 77.5 | 61.3 |
| Rejected buffer | without rejected buffer | 85.5 | 72.9 | 58.9 |
| Slow/meta update | meta skill + slow update | 87.1 | 77.5 | 61.3 |
| Slow/meta update | without meta skill | 85.1 | 75.7 | 58.1 |
| Slow/meta update | without both | 86.3 | 55.0 | 59.7 |
这个 ablation 的关键解释是:具体 batch size 或 schedule 不是唯一决定因素;真正重要的是 bounded text-space update、严格 gate、rejected feedback、slow/meta update 共同把 LLM reflection 变成可控训练循环。尤其 SpreadsheetBench 对 slow/meta update 非常敏感,去掉 meta skill 和 slow update 后从 77.5 掉到 55.0。
Transfer, optimizer strength, and cost
Transfer results 说明 best_skill.md 是 reusable artifact,而不只是某个训练 run 的 prompt。跨 model:GPT–5.4 的 SpreadsheetBench skill 迁移到 GPT–5.4-mini 得到 45.5(baseline 36.1,direct 47.5),迁移到 GPT–5.4-nano 得到 26.5(baseline 23.5,direct 42.5);LiveMath skill 迁移到 GPT–5.4-nano 反而达到 28.8,高于 direct 27.2。跨 harness:Codex-trained SpreadsheetBench skill 转到 Claude Code 得到 81.8,相对 Claude Code no-skill 22.1 提升 +59.7;Claude Code-trained SpreadsheetBench skill 转到 Codex 得到 71.1,相对 Codex no-skill 27.5 提升 +43.6。跨 benchmark:OlympiadBench skill 转到 Omni-MATH,在 GPT–5.4 / mini / nano 上分别得到 60.3、36.6、40.1,均高于 baseline 56.6、34.8、38.8。
Table 5 说明强 optimizer 有优势但不是唯一来源。SpreadsheetBench GPT–5.4-mini:baseline 36.1,GPT–5.5 optimizer 得到 47.5,target-matched optimizer 得到 43.2;SearchQA GPT–5.4-nano:baseline 55.8,GPT–5.5 optimizer 得到 74.8,target-matched optimizer 得到 69.9。也就是说,frontier optimizer 提供更强反思能力,但 bounded update + validation gate 让弱模型作为自身 optimizer 时也能恢复相当一部分收益。
| Benchmark | Initial tokens | Final tokens | Accepted edits | Train tokens | Cost / point |
|---|---|---|---|---|---|
| SearchQA | 16 | 857 | 4 | 213.8M | 37.9M |
| SpreadsheetBench | 224 | 1,995 | 4 | 21.4M | 0.6M |
| OfficeQA | 145 | 883 | 1 | 20.8M | 1.1M |
| DocVQA | 81 | 959 | 3 | 188.2M | 46.4M |
| LiveMath | 154 | 379 | 1 | 23.2M | 3.6M |
| ALFWorld | 516 | 1,321 | 2 | 59.3M | 15.9M |
Table 6 的重要结论是 edit economy:最终 skill 都很小,379–1,995 tokens;accepted edits 只有 1–4 条,中位数 2.5。LiveMath 的 +29.3 和 OfficeQA 的 +39.0 都来自单个 accepted edit,说明性能增益不是靠无限追加经验,而是靠 selection gate 从大量候选修改中筛出少数有效规则。
Figure 4 给出的 learned rules 都是 procedure-level,而不是记住某个样例:SearchQA 学到先推断 answer type 并选择最短 canonical entity;SpreadsheetBench 学到检查 workbook structure / formulas 并写入 evaluated static values;OfficeQA 学到把 parsed pages 作为主证据并锁定 table/date/unit context;DocVQA 学到先绑定 visual row/header/field;LiveMath 学到 strongest-statement MCQ 要按 theorem strength 排序;ALFWorld 学到维护 visited/frontier ledger 并在同类失败后 diversify search。这些规则解释了为什么 procedural benchmarks 提升最大。
Limitations
作者明确承认四类限制。第一,SkillOpt 依赖 scored trajectories 和 held-out selection split,因此最适合有 automatic verifier、exact match、executable check 或可靠反馈的任务;开放式、主观、多维成功标准的任务需要更强 human/model-based evaluation gate。第二,部署 artifact 很小,但训练阶段要额外 rollout 和 optimizer model calls;若任务是一次性的,摊销价值较低。第三,SkillOpt 有意优化一个 portable skill,而不是大规模 skill library 或 model weights,因此高度异质的领域可能需要多个 disjoint procedures,单一 skill 不够。第四,skill 仍可能编码训练分布中的 domain-specific heuristics,迁移到差异较大的 model、harness 或 task 前仍需 held-out evaluation。
Overall conclusion
SkillOpt 的主要贡献是把“agent skill 会从经验中变好”这件事从经验性 prompt engineering 变成有 optimizer discipline 的离线训练问题:bounded edits 提供 step-size control,validation gate 防止 harmful self-revision,rejected buffer 提供负反馈,slow/meta update 维持跨 epoch 记忆。实验上,它在 52/52 cells best or tied-best,并且把最终产物压缩成一个无需额外推理成本的 best_skill.md,这使它特别适合 tool-use agent 的可审计能力迁移。