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.pyReflACTTrainer.train 负责训练主循环;不同 benchmark 的 adapter / rollout 负责把 native environment 转成 scored trajectories,例如 skillopt/envs/spreadsheetbench/rollout.pyskillopt/envs/searchqa/rollout.pyskillopt/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 results

3.2 Backward pass:minibatch reflection

Optimizer model 先把 rollout results 分成 failures 和 successes,再按 reflection minibatch size 分组。失败 minibatch 负责提出缺失规则或纠错规则;成功 minibatch 负责保留已经有效的行为,避免 optimizer 只会“修 bug”却破坏已有策略。skillopt/gradient/reflect.pyrun_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_*.jsonminibatch_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 patches

3.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.pymerge_patchesskillopt/optimizer/clip.pyrank_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_report

patch 模式使用局部操作,而不是任意全文重写。skillopt/optimizer/skill.py_apply_edit_with_report 支持 appendinsert_afterreplacedelete,并且会跳过 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.pyevaluate_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.yamltrain.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_score

Code-to-paper mapping

Code reference: main @ 99212e39 (2026-05-26) — pseudocode and mapping based on this commit

Paper ConceptSource FileKey Class/Function
训练主循环、current/best skill 状态、score cache、rejected bufferskillopt/engine/trainer.pyReflACTTrainer.train, _format_step_buffer, _save_skill
失败/成功 minibatch reflectionskillopt/gradient/reflect.pyrun_minibatch_reflect
failure/success patch aggregationskillopt/gradient/aggregate.pymerge_patches, _hierarchical_merge
textual learning rate / top- edit selectionskillopt/optimizer/clip.pyrank_and_select
patch application and protected slow-update fieldskillopt/optimizer/skill.pyapply_patch_with_report, _apply_edit_with_report
validation gateskillopt/evaluation/gate.pyevaluate_gate, GateResult
slow updateskillopt/optimizer/slow_update.pyrun_slow_update, replace_slow_update_field
optimizer-side meta skillskillopt/optimizer/meta_skill.pyrun_meta_skill, format_meta_skill_context
config inheritance and CLI overridesskillopt/config.py, scripts/train.pyload_config, flatten_config, apply_overrides
benchmark adapters / rolloutsskillopt/envs/*/adapter.py, skillopt/envs/*/rollout.pyper-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=400batch_size=40split_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=80batch_size=40split_dir=data/spreadsheetbench_split
OfficeQA多轮 tool loop,最多 24 tool calls,search_mode=offline。公开配置 configs/officeqa/default.yaml 使用 split_dir=data/officeqa_split,具体样本数论文未详细说明。
DocVQAdocument / multimodal document QA。公开配置 configs/docvqa/default.yaml 使用 split_dir=data/docvqa/splitsbatch_size=40;具体样本数论文未详细说明。
LiveMathematicianBench数学 multiple-choice reasoning;论文说明 bounded training pool 为 35 training items per epoch,并报告 per-benchmark configs 会调整 batch size;公开配置默认 batch_size=40,见上文 paper-code 差异。
ALFWorldpersistent 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=4train.batch_size=40train.seed=42gradient.minibatch_size=8gradient.merge_batch_size=8optimizer.learning_rate=4optimizer.min_learning_rate=2optimizer.use_slow_update=trueoptimizer.slow_update_samples=20optimizer.use_meta_skill=truescripts/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。

SettingNo skillSkillOptGain
GPT–5.5 direct SearchQA77.787.3+9.6
GPT–5.5 direct SpreadsheetBench41.880.7+38.9
GPT–5.5 direct OfficeQA33.172.1+39.0
GPT–5.5 direct DocVQA78.891.2+12.4
GPT–5.5 direct LiveMath37.666.9+29.3
GPT–5.5 direct ALFWorld83.695.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。

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。

ComponentDefault / ablationSearchQASpreadsheetBenchLiveMath
Learning-rate formlr=4 default87.177.561.3
Learning-rate formdynamic lr85.871.854.0
Learning-rate formwithout lr84.675.757.3
Rejected bufferwith rejected buffer87.177.561.3
Rejected bufferwithout rejected buffer85.572.958.9
Slow/meta updatemeta skill + slow update87.177.561.3
Slow/meta updatewithout meta skill85.175.758.1
Slow/meta updatewithout both86.355.059.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 时也能恢复相当一部分收益。

BenchmarkInitial tokensFinal tokensAccepted editsTrain tokensCost / point
SearchQA168574213.8M37.9M
SpreadsheetBench2241,995421.4M0.6M
OfficeQA145883120.8M1.1M
DocVQA819593188.2M46.4M
LiveMath154379123.2M3.6M
ALFWorld5161,321259.3M15.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 的可审计能力迁移。