第 5 章 第一部分:快速上手

产出训练数据

HyTuner RL 集成、从评测轨迹到 SFT/DPO 数据、Trace 体系、Cache 体系、Reward 工具

Nexus 训练数据 RL HyTuner Trace SFT DPO RWML

评测和训练的关系

Nexus 的核心定位是研究框架,它天然连接评测和训练两个环节:

评测(Eval)                        训练(Train)
  Agent 在 Benchmark 上跑   →   收集对话轨迹
  计算 Score                →   Score 作为 Reward
  轨迹 + Score              →   SFT / DPO / RL 训练数据

这一章讲的就是右半部分——如何从 Nexus 的执行过程中提取可用于训练的数据。

Trace 体系:对话轨迹的采集

Nexus 有一套自研的轻量级 Trace 系统(不依赖 OpenTelemetry),通过 @observe 装饰器自动埋点。每次 Agent 的 LLM 调用、工具执行都会产生 Span。

Rollout 生命周期

UCSD + Adobe + 多伦多大学等联合发布的 GFCR 框架(arXiv: 2605.02913)将 RL 训练中的轨迹生成生命周期拆解为四个模块。对照 Nexus 的现有覆盖:

GFCR 模块功能Nexus 对应覆盖程度
Generate生成轨迹Trace 体系(@observe 装饰器自动埋点)✅ 完整
Filter筛选高质量轨迹score ≥ 0.8 筛选逻辑(较简单)⚠️ 基础
Control并发控制/超时/重试run-batch 参数(max_concurrent/timeout/retry)✅ 完整
Replay轨迹重放与复现Cache 体系(pickle 格式缓存)✅ 完整

当前 Filter 策略(score ≥ 0.8)较为简单。更细粒度的过滤方案可参考 GFCR 的多维度筛选(如按步骤正确率、工具使用效率等)。

Processor 插件

Span 数据通过 Processor 插件来消费。在 YAML 中配置:

processors:
  chat:
    name: "chat_tracker"
  tokens:
    name: "token_counter"
    args:
      client_filter: "main"  # 只统计 main client 的 token

内置 Processor:

Processor注册名产出说明
ChatTrackerchat_tracker完整对话历史每条消息含 role/content/tool_calls/timing
TokenCountertoken_countertoken 用量统计按 client 分组的 prompt/completion tokens
HyTunerhytunertoken-level 训练数据prompt_ids / response_ids / logprobs
JsonLinejson_lineJSONL 文件流式写入 span 事件
ChromeTracechrome_traceChrome trace 文件可用 chrome://tracing 查看时间线
Printprint控制台输出调试用

执行完成后,从 RunOutput.processor_results 获取数据:

result = await run(run_input)

# 对话轨迹
trajectory = result.processor_results['chat']
# trajectory = [{'role': 'system', ...}, {'role': 'user', ...}, ...]

# Token 统计
tokens = result.processor_results['tokens']
# tokens = {'prompt_tokens': 12500, 'completion_tokens': 3200, ...}

从轨迹到训练数据:三阶段管线

2026 年 tool-using agent 的主导后训练管线是三阶段递进:SFT → DPO → RL。ICLR 2026 论文”SFT Memorizes, RL Generalizes”验证了 RL 后训练可恢复 SFT 损失的 99% OOD 性能。Nexus 的 Trace 体系天然支撑这三阶段:

Stage 1: SFT 冷启动(筛选高分轨迹,覆盖格式和工具调用模式)

# 筛选高分轨迹
if result.task_output.score >= 0.8:
    sft_sample = {
        'messages': result.processor_results['chat'],
        'score': result.task_output.score,
        'task': run_input.task_name,
    }
    save_to_dataset(sft_sample)

Stage 2: DPO 对齐(同问题多次运行,高分 vs 低分组成偏好对)

# 在 Generator 中为同一实例生成多个 run
for instance in dataset:
    for trial in range(5):
        items.append(BatchItem(
            config=RunInput(
                task_name='swe_task',
                task_config={'instance': instance},
                # ...
            ),
            run_id=f"{instance['id']}_trial{trial}",
        ))

# 批量执行后,按 instance 分组,取 max/min score 组成偏好对

Stage 3: RL 精调(HyTuner 在线 RL,score 作为 reward)

→ 详见下方 HyTuner RL 训练集成

RL 路线选择:当前主流 RL 方法基于 GRPO 组采样(SC-GRPO/TRACE/GAIN-RL 均在此框架内)。SAO(2026-07)提出了废除组采样的异步范式,已在 GLM-5.2 大规模验证。对 Nexus + HyTuner 用户:如果采用 SAO,需调整 rollout 生成策略(单 prompt 单 rollout)+ 配置价值模型 + 跳过观测 GAE 参数。建议 Nexus 的 HyTuner 集成文档标注支持的 RL 算法类型(GRPO-based / SAO-based)。

成本递增:Stage 1 纯离线(成本最低)→ Stage 2 离线+多次采样 → Stage 3 在线 RL(成本最高)。每阶段的数据需求来源:SFT 来自 ChatTracker 高分轨迹,DPO 来自同问题多次运行的偏好对,RL 来自 HyTuner 的在线交互。

HyTuner RL 训练集成

HyTuner 是腾讯内部的 RL 训练框架。Nexus 提供了开箱即用的集成——nexus.runner.hytuner 模块。

工作原理

HyTuner 集成的核心思想是:用 Nexus 作为 RL 的 Environment。训练循环中,HyTuner 控制模型生成,Nexus 负责执行动作并返回 reward。

HyTuner Training Loop
  ├─ 模型生成 response
  ├─ Nexus 执行 Task + Agent(使用模型 response)
  ├─ 收集 trajectory(prompt_ids, response_ids, logprobs)
  ├─ 计算 reward(task score)
  └─ 训练更新

配置方式

from nexus.runner.hytuner import run, HyTunerRunInput

run_input = HyTunerRunInput(
    imports=['nexus.extensions.projects.calculator'],
    task_name='calculator_task',
    task_config={
        'user_prompt': 'Compute (7 @ 4 @ 9) @ 4 @ 3',
        'ground_truth': '159',
    },
    agent_name='calculator_agent',
    agent_config={'max_iterations': 10},
)

result = await run(
    run_input,
    tokenizer=tokenizer,           # HyTuner 的 tokenizer
    server_manager=server_manager,  # HyTuner 的推理服务
    sampling_params={'temperature': 1.0},
    tool_parser=tool_parser,        # 工具调用解析器
)

# result.segments[0].prompt_ids    → 训练用 prompt token IDs
# result.segments[0].response_ids  → 训练用 response token IDs
# result.segments[0].response_mask → attention mask
# result.reward_score              → task score as reward

HyTunerRunOutput 结构

class HyTunerRunOutput:
    segments: list[HyTunerSegment]  # 轨迹片段(多段对应 context compaction)
    reward_score: float             # Task 评分,用作 RL reward
    termination_reason: str         # 终止原因
    metrics: dict                   # 额外指标
    success: bool                   # 是否成功执行

每个 HyTunerSegment 包含:

  • prompt_ids:prompt 的 token ID 序列
  • response_ids:response 的 token ID 序列
  • response_mask:哪些 token 参与 loss 计算
  • response_logprobs:每个 response token 的 log probability
  • num_turns:对话轮数

Context Compaction 与多段轨迹

当对话超过上下文窗口时,Nexus 的 Compaction 机制会压缩历史。这会产生多个 segment:

  • 段 1:原始对话(被压缩前)
  • 段 2:压缩后的新对话
  • …
  • 最后一段:最终的完整对话

HyTuner 的训练代码需要处理这种多段结构。segments[-1] 是最终段(“主”段)。

外部对照:Harbor 的 RolloutDetail

同一件事 Harbor 也定义了一个结构。两者几乎同构——这不是谁抄谁,而是”要把一次对话还原成可训练的张量”这个约束逼出来的收敛:

字段Nexus HyTunerSegmentHarbor RolloutDetail
prompt tokenprompt_idsprompt_token_ids(每个元素 = 该轮含此前历史的完整 prompt)
response tokenresponse_idscompletion_token_ids
loss 掩码response_mask无(由 prompt / completion 的切分隐含)
logprobsresponse_logprobslogprobs
轮数num_turns隐含在三个 list 的长度里
扩展位无extra: dict[str, list[Any]]——按轮存 provider 私有数据,源码注释举例 routed_experts
多段约定segments[-1] 为最终段list[RolloutDetail],约定第一项是主 agent 的对话

两处差异值得单独记下来:

一是 extra。 Harbor 允许按轮挂任意 provider 私有数据,注释里举的例子是 routed_experts(MoE 的专家路由记录)。Nexus 的 HyTunerSegment 没有这个口子,这类信息只能塞进 metrics: dict——而 metrics 是 per-run 的,不是 per-turn 的,一旦塞进去就丢失了它与具体轮次的对齐关系。对本章后面讨论的 token 级信用分配(SC-GRPO)来说,这是结构性障碍,不只是少一个字段。

二是多段的语义根本不同。 Nexus 的 segments 来自 compaction(上下文超限被压缩,沿时间线一段段切下来),Harbor 的多个 RolloutDetail 来自 subagent 与 summarization(主 agent 之外还有子 agent 的历史,以及被总结掉的历史)。前者是”同一条时间线被切断”,后者是”多条时间线并存”。这意味着 Harbor 的结构能表达子 agent 轨迹,Nexus 的不能。若要把 Nexus 用于多 agent 场景的 RL(主 agent 调子 agent、两者都要学),segments 这个模型得先扩成”主轨迹 + 子轨迹引用”,而不是再往后加一段。

RL 训练新范式:RWML 世界模型学习

当前 HyTuner RL 集成的 3 阶段管线(SFT → DPO → RL)依赖任务成功/失败作为奖励信号。RWML(Reinforced World Model Learning, arXiv:2602.05842)提出了一种根本性不同的训练范式:

核心思想:不直接用任务成功/失败作为奖励,而是训练世界模型(World Model)预测环境状态转移,使用 sim-to-real 奖励——世界模型在模拟环境中学习,奖励信号从模拟迁移到真实任务。

实验结果:RWML 在 ALFWorld/WebShop 上超越直接任务成功 RL 6.9/5.7 分。

与 Nexus 的契合点:

  1. Trace 天然适配:Trace 系统记录的每一步状态转移(Agent action → 环境响应 → 下一步观察)可直接作为世界模型的训练数据
  2. HyTuner 辅助评估:世界模型可作为 HyTuner 的辅助评估器,提供更密集的中间奖励信号,不依赖昂贵的任务验证
  3. GFCR 互补:在 GFCR 框架中,世界模型可增强 Filter 模块——不仅按 score 过滤,还按”轨迹是否符合世界模型预测”来筛选

对 3 阶段管线的可能扩展:

SFT → DPO → World Model Pretraining → RL
                    ↑ 新增阶段
          用 Trace 数据训练世界模型
          为 RL 阶段提供密集中间奖励

⚠️ RWML 目前仍在学术验证阶段,与 Nexus 的整合方案需要进一步工程验证。

Token 级信用分配:SC-GRPO

当前 3 阶段管线中 SFT 冷启动的 Filter 策略(score ≥ 0.8)是轨迹级粗粒度过滤。SC-GRPO(Self-Conditioned GRPO, arXiv:2606.18810, 2026-06-17)提出了 token 级细粒度信用分配:用模型在自身验证轨迹上条件化的 KL 散度作为 GRPO 梯度的乘法权重,自动识别哪些 token 对成功更重要。

与 Nexus 的契合点:

  1. Filter 模块增强:SC-GRPO 的 token 级重要性权重可作为 GFCR Filter 模块的附加维度——不仅筛选高分轨迹,还标记轨迹内”关键 token span”
  2. DPO 偏好对构造优化:当前 DPO 阶段用同问题多次运行的 max/min score 组成偏好对。SC-GRPO 的 token 级信号可细化偏好对比——不是在轨迹级比较,而是在关键 token 段比较
  3. Processor 候选:可新增 token_credit Processor,在轨迹生成后计算 token 级重要性权重

效果:比 GRPO 高 8.1%,比 DAPO 高 5.9%,且 OOD 性能更强。

⚠️ SC-GRPO 与 Processor 的整合方案尚在概念阶段。

置信门控信用分配:Gated-BEPO

SC-GRPO 用 token 级 KL 权重做信用分配,但依赖”自身验证轨迹”这一额外信号。Gated-BEPO(“Confidence-Gated Bellman Credit Assignment for Large Language Model Agents,” arXiv:2608.06861, 2026-08-07)则提出了无需 critic 的置信门控 Bellman 信用分配:从经验 rollout 图(empirical rollout graph)推导步骤级信用,用均值备份 Bellman 不动点估计节点价值,再沿轨迹用 GAE 累积 TD residual 得到步骤级 Bellman advantage。

核心机制——置信门控:只有当某个状态出现多个不同后继(即有可比较的分支证据)时,才接入步骤级 Bellman credit;否则回退到 episode-level credit。这直接回应了”硬造步骤信用”的风险——没有分支证据的状态,强行分配步骤级信用只会引入噪声。

与 Nexus 的契合点:

  1. Filter 模块的信用分配升级:当前 GFCR Filter 模块的 score ≥ 0.8 是轨迹级粗粒度过滤。Gated-BEPO 提供”步骤级信用”的第三选择——仅在 Trace 中存在可比较分支(同一状态多个后继)时才做步骤级筛选,与 SC-GRPO 的 token 级信号形成互补。
  2. Processor 候选:可新增 credit_gate Processor,在轨迹生成后基于经验 rollout 图计算步骤级 Bellman advantage,并输出”哪些步骤有可靠的分支证据”。
  3. 效果参照:WebShop/ALFWorld/视觉 Sokoban 上语言模型与 VLM 一致提升;消融显示步骤级信用应”选择性接入”而非”均匀混合”。

⚠️ Gated-BEPO 依赖”可恢复的中间状态”(重复访问同一观测才能建图)。若环境无法恢复中间状态,其经验图构建的前提不成立。

当前提不成立时的替代:MileGPO(arXiv:2608.19803,2026-08-20,6 位作者)。 MileGPO 从分组 on-policy rollout 推导信用,而不是从可恢复状态建图——在成功 rollout 上识别 milestone、在失败 rollout 上识别反复出现的 trap,再用 Reliability-Calibrated Shaping(按 outcome 置信度加权,强化可靠的 milestone/trap、压低不确定的)与 Progress-Contrastive Calibration(检验候选是否反映局部进展、其入边转移是否优于同状态的其他分支)校准。论文明确「既不需要辅助模型,也不需要额外的环境交互」——这一条让它在无法恢复中间状态的环境里仍然可用,恰好补上 Gated-BEPO 的这一限制。ALFWorld / WebShop 达 SOTA,且 ID→OOD gap 小。

同一族的更新版本:GRAFT(arXiv:2609.28963,2026-09-24,4 位作者)。Gated-BEPO 讲的是”怎么做”,GRAFT 补的是”为什么必须这么做”,而且把论证放到了 RL 定义层面。原文的动机句可以直接引:GRPO 的 group-normalized advantage「在 response 级可靠,在 step 级系统性有偏」——因为粗粒度的 trajectory-level advantage 反映不出单步贡献,失败轨迹里也可能含有高价值的步骤。忠实于 RL 定义的做法本应在每个中间状态采样多个 action(GRPO 在单轮任务上的成功正来自它恪守”同一状态下多个 action 的回报均值才是可信的状态价值估计”这一条),但逐步采样代价太高。GRAFT 的绕法是把全部 rollout 嫁接成一张轨迹图(trajectory graph),在图上做 Bellman 迭代恢复节点状态价值,用节点价值之差给每条边分配信用,并提出 Graph GAE 把 GAE 推广到图上以压低价值估计偏差的影响。

与 Gated-BEPO 的差别值得记一句:Gated-BEPO 的门控是开关式的(有分支证据才接入 step 级,否则回退 episode 级),GRAFT 是全图估计式的(先把图建出来,再在图上算节点价值差)。前者便宜且保守,后者信息量更大但对价值估计偏差更敏感——Graph GAE 就是为这个敏感度设计的。两者共用同一条前提限制:都需要可恢复或可对齐的中间状态,前提不成立时同样退回 MileGPO。

⚠️ 以上为摘要级理解。Bellman 迭代在轨迹图上的具体递推式、Graph GAE 与标准 GAE 的差别,均属推论,实现前须读原文。

互补的另一侧:ProVer(arXiv:2609.36178,2026-09-28,10 位作者)。Gated-BEPO 与 GRAFT 都在问”每个状态的信用是多少”,ProVer 问的是”哪些状态值得算信用”。三阶段:Propose——用一个 LLM agentic judge 对比同一 GRPO 组内的成功与失败轨迹,提出一个可能造成分岔的片段;Verify——恢复该片段前后的环境状态,用当前策略采样新的 continuation,用经验终端成功率之差估这个片段的 advantage;Credit——只把正的估计叠加到该片段内 policy token 的原 GRPO advantage 上。设计上最关键的一点是把判断与验证分离:judge 只负责选段,advantage 由真实结果决定,不采信 critic 分数。Qwen3.5-2B / 4B 上相对 GRPO 提升 9.91% / 7.12%(ALFWorld、WebShop、SearchQA)。

ProVer 与 Gated-BEPO 需要的”恢复环境中间状态”是同一个前提,因此在下面的阶梯表里它俩占同一行、共用同一条限制。选型上的分工是:环境好恢复且你想全量估计 → GRAFT;环境能恢复但恢复成本高、只想把预算花在刀刃上 → ProVer;环境恢复不了 → MileGPO。

验证器侧信用追踪:VICT

SC-GRPO 与 Gated-BEPO 的共同前提是:信用必须从 rollout 侧推断。VICT(Verifier-Instrumented Credit Tracing,arXiv:2608.28128,2026-08-28,5 位作者)指出这是把验证器浪费成了标量——原文表述是「现有方法仍把判定成功的验证器当作一个标量奖励,丢弃了它内部的任务结构」。

VICT 的做法是把验证器暴露为可执行或有证据支撑的 atoms,沿依赖有效的证明边回溯到 action,只在那些边上重分配 group-relative advantage,把信用分配从”rollout 侧推断”转为”验证器侧追踪”。四个性质对工程落地特别友好:

  • 保留原始终端奖励——不改奖励定义,不引入奖励黑客面
  • 证据不足或歧义时弃权——不硬猜(与 Nexus Filter 的设计哲学一致)
  • 只改训练时的 advantage tensor
  • 不需要 critic、不需要过程标签、不需要分支 rollout、不需要推理时访问 verifier

对 Nexus 的意义:本章实战节(§「生产数据接入 RL 训练」)让读者为 Terminal-Bench 任务写 verifier_script——那个 verifier 内部的判定结构,就是 VICT 所说的 atoms。读者为评测写的验证器可以复用于训练期的信用分配,不需要额外标注、不需要额外采样。 集成改动局限在 Filter / Processor 侧(改 advantage tensor),GFCR 的 Generate 与 Replay 均不受影响。原文的消融还排除了两条看起来更省事的替代路径:dense atom rewards 与 final-commit credit。

⚠️ 以上为摘要级理解(性质描述逐条取自原文摘要)。atoms 的具体定义方式、证明边的构造算法,以及如何把 Nexus 的 verifier_script 改造成 VICT 接口,均属推论,实现前须读全文。

没有标准答案时:用 rubric 做信用锚点(Dr.Credit)

前面两条路都默认一件事:reward 要么来自任务分数、要么来自 verifier 判定。可一旦任务是开放式的(deep research、调研报告、方案设计),根本不存在 canonical solution,依赖 ground truth 定义 process reward 的方法集体失效。Dr.Credit(arXiv:2609.34296,2026-09-28,9 位作者)给了第三条路。

它把任务 rubric 当作最终答案评测与过程监督的共享参照:评估每次工具返回的信息,相对于该 rubric 已接受的支持历史,提供了多少新增支持。这一句里有两个设计要点值得拆开看——「相对于已有支持历史」意味着它能区分新证据与轨迹里已经存在的证据(第二次拿到同一条证据不再计分),「承认部分支持」意味着 rubric 的每一条可以是 0 到 1 之间的中间态,不是二元的。得到的 process advantage 与 GRPO 的 outcome advantage 结合,在指导检索决策的同时保留对最终报告质量的监督。8B backbone 在四个 in-domain / out-of-domain 基准上超过被测的开源 deep research 基线。

对 Nexus 的工程含义是复用而非新增:本章 §「关键设计决策」第 3 条已经建议把 score 从二元 pass/fail 改成细粒度打分(未尝试=0、尝试未过=0.3、通过=1.0)。把这条从”打分”再往前推一步变成 rubric,同一套 rubric 就可以直接当训练期的信用锚点,不额外标注、不额外采样——这与 VICT 复用 verifier_script 的思路是同构的,只是一个复用判定结构、一个复用评分条目。

⚠️ “8B 达到与前沿闭源模型可比”为摘要自述,本轮未核具体主表;rubric 支持历史的记账方式(如何定义”已接受的支持”)属推论,实现前须读原文。

结构化路由:IAPO

与前述方法不同,IAPO(Influence-Aware Policy Optimization,arXiv:2608.24588,2026-08-25,4 位作者)不换信用来源,只换分发路径:把每条 rollout 表示为「可训练 agent action 上的 typed influence-dependency graph」,user 与 tool observation 作为证据,把 support-use / failed-use 结构转成路由权重,重分配同一个 trajectory-level advantage。原文强调它利用的是”已完成 rollout 本身已记录的信息与错误如何在 action 之间流动”,不依赖其他轨迹的对比证据或重采样延续。

对 Nexus 的实用价值在于零额外成本:IAPO 所需的图完全可从 GFCR Generate 阶段已产出的 rollout 重建(含 user/tool observation),不需要新增采样、不需要辅助模型、不需要 critic。Qwen3-4B / 8B 在 τ²-Bench、UserBench、AgentChangeBench 三个 service-agent 基准上超越多轮 RL 基线,BFCL-v4 Multi-Turn 显示增益不损害多轮 function-calling 能力。

(该工作来自复旦大学与微信腾讯团队——机构取自论文 HTML 全文的显式 Affiliation: 字段——其 service-agent 场景设定与本书的多轮 Trace 结构高度接近。)

Turn 级递归:AgentOPSD

AgentOPSD(arXiv:2608.05987,2026-08-06,13 位作者)补上了本书此前没有的 turn 级粒度:把 token 级 teacher-student log-prob 差距聚合成 turn 级证据,在 log-odds 空间递归更新 Bayesian 信念状态,通过相邻状态间的 marginal belief revision 识别 pivotal turns。critic-free、不需额外 rollout,与标准 policy optimization 完全兼容。Qwen2.5-7B 在 ALFWorld 达 89.1%,优于 GRPO 与强自蒸馏基线。

为什么这一档对 Nexus 特别合适:Nexus 的 Trace 本身就是多轮结构,turn 是与数据格式天然对齐的粒度——不像 step 那样需要定义切分规则、也不像 token 那样过细。

信用分配粒度阶梯(总览)

本章的信用分配讨论分散在各节,读者难以判断”该用哪一档”。下面这张梯子的读法是:粒度不是越细越好,而是取决于你手里有什么信号。

粒度代表方法信用来源额外成本前提 / 限制
trajectoryGRPO 基线终端奖励广播无粗,长程失效
turnAgentOPSDteacher-student log-prob gap + 递归信念无 critic / 无额外 rollout需 teacher
milestoneMileGPO成功 rollout 的 milestone / 失败的 trap无辅助模型 / 无额外环境交互需分组 on-policy rollout
stepGated-BEPO / GRAFT / ProVer经验 rollout 图或轨迹图的 Bellman 不动点;或只对选中片段做终端成功率差无 critic(ProVer 需对片段重采样)⚠️ 需可恢复的中间状态
tokenSC-GRPO自身验证轨迹上的 KL需自身验证轨迹与 Processor 整合仍在概念阶段
结构化路由IAPOinfluence-dependency graph(重分配同一个 trajectory advantage)无(可从既有 rollout 重建)需 user/tool observation 作为证据
验证器侧VICTverifier 内部 atoms + 证明边只改训练时 advantage tensor需可暴露 atoms 的 verifier

选型判据:有可暴露 atoms 的 verifier → 用 VICT(成本最低,且验证器你已经写好了);有可恢复状态 → 用 Gated-BEPO / GRAFT,恢复成本高则只验关键片段用 ProVer;只有 terminal reward + 多轮 Trace → 用 IAPO(从既有 rollout 重建图)或 MileGPO(从分组 rollout 找 milestone);有 teacher 模型 → 用 AgentOPSD;任务没有标准答案 → 用 Dr.Credit 的 rubric 锚点。

上表是”你手里有什么信号”的静态分档,不是”一条轨迹只能选一档”。 GACA(arXiv:2609.12424,2026-09-11,12 位作者)直接质疑了”选一档”这个前提:它的论点不是”再来一档”,而是粒度本身应当逐状态自适应。

原文的批评很具体。GRPO 把「一个 trajectory 级标量广播给每一步」,说不出是哪个决策导致了结果;GiGPO 用 anchor-state 分组恢复了 step 级信号,但它「在同一个固定权重下合并 step 级与 episode 级的估计」——于是对一个关键分支决策和对一个近乎确定的常规转移,花掉了同样的分辨率。GACA 主张正确的分辨率是状态相关的:用 rollout 自身已经记录下来的 NLL 给每一步打分(critic-free,不引入额外模型),按这个分数逐步混合两种 advantage——NLL 高于均值时偏向细粒度信号,低于均值时回退到 episode 级。论文还给出了这个混合的精确风险分解,并证明足够小的调制在方向对齐为正时优于固定混合。

读法上要做一次分离:先用上表选定可用的档位,再问是否需要逐档位自适应混合,两步不互斥。GACA 是加在已选档位之上的一层调制,不是第八档。

⚠️ GACA 为 2026-09-11 预印本,验证在 ALFWorld / WebShop 的 1.5B 与 7B 两档上,与本章的 SWE / Terminal-Bench 场景不同域;风险分解的前提条件本轮未读全文核验。

RL 训练效率优化:GAIN-RL

NeurIPS 2025 的 GAIN-RL(Gradient-driven Angle-Informed Navigated RL)提出了显著提升 RL 训练效率的方法:

核心机制:利用梯度方向的角度集中信号(angle concentration)指导训练。关键洞察——不是所有梯度方向都有价值,角度集中的梯度方向包含更多有效信息。

效果:2.5× 训练效率提升,同等性能下训练步数减少 60%。

与 Nexus + HyTuner 的整合可能性:

  1. HyTuner 效率优化:在 rollout 生成后用角度信号过滤低价值梯度,减少无效训练步
  2. Processor 插件候选:可新增 angle_concentration Processor,在轨迹生成后计算角度集中度,作为轨迹质量评估的附加维度
  3. 与 TSR 协同:角度信号可指导 TSR 树搜索的展开方向——优先展开角度集中度高的 action 分支
# 潜在配置方式(工程验证后)
processors:
  chat:
    name: "chat_tracker"
  angle_concentration:
    name: "angle_concentration"
    args:
      top_k_ratio: 0.4    # 保留角度集中度前 40% 的梯度

⚠️ GAIN-RL 与 Processor 插件的整合方案尚在概念阶段,需要验证角度信号在 Agent RL 场景下的有效性。

RL 范式变革:SAO 废除组采样的异步 RL

GRPO 的组采样(每 prompt 生成 G 条 rollout,组内比较计算 advantage)与异步训练天然矛盾——等组填满就失去了异步的意义。SAO(Single-rollout Asynchronous Optimization, arXiv:2607.07508, 2026-07-08, 清华 KEG + 智谱)从根本上废除了组采样:每 prompt 只生成 1 条 rollout,完成后立即训练。

核心机制:

  • DIS(直接双边重要性采样):无需保存历史 checkpoint 处理离策略偏差,简化了异步训练的工程复杂度
  • 价值模型解耦:每更新 1 次策略即更新 2 次价值模型,降低单 rollout 梯度方差
  • 跳过观测 GAE:环境反馈 token(如工具输出)不参与 advantage 计算,避免噪声干扰
  • 固定注意力参数:稳定价值模型训练,防止异步更新导致的 Q 值估计崩溃

生产验证:已部署于 GLM-5.2(750B-A40B)的 Agent RL 训练管线,可稳定训练 1000 步,AIME2025 84.2% → 97.3%(+13.1 点),SWE-Bench Verified 27.0% → 29.8%。

对 Nexus + HyTuner 的启示:

  • SAO 与 TRACE 互补——TRACE 选择高信息量 rollout(“采样什么”),SAO 决定如何利用 rollout(“怎么训练”)
  • SAO 与 GAIN-RL 互补——SAO 减少无效等待,GAIN-RL 过滤低价值梯度
  • 如果 HyTuner 采用 SAO,需调整 rollout 生成策略(单 prompt 单 rollout)+ 配置价值模型 + 跳过观测 GAE 参数
  • 建议 Nexus 的 3 阶段管线文档标注支持的 RL 算法类型(GRPO-based / SAO-based)

⚠️ SAO 与 HyTuner 的具体整合方案需工程验证。

RL 采样效率优化:TRACE

NeurIPS 2025 的 GAIN-RL 优化了”怎么用梯度”,清华+腾讯的 TRACE(Tree Rollout Allocation for Contrastive Exploration, arXiv:2606.11119, 2026-06-10)优化了”怎么分配采样预算”。核心发现:Agent RL 训练中约 80% 的 rollout 样本因奖励对比度太低,对策略更新几乎没有贡献。

核心机制:将 ReAct 风格 thought-action-observation 轮次建模为树节点,用共享预测器估算每个节点的条件成功概率,将有限的 rollout 预算导向最可能产生混合奖励(既有成功也有失败)的 prompt 根节点和中间前缀。结果是:Qwen3-14B 多跳 QA +2.8 点(同等采样成本),有效对比信号占比提升 25-34%。

与 Nexus + HyTuner 的整合可能性:

  1. 与 GAIN-RL 互补:TRACE 选择高信息量 rollout,GAIN-RL 在高信息量 rollout 上过滤低价值梯度——两者协同形成”采样→梯度”双重优化
  2. GFCR Filter 模块增强:TRACE 的节点级条件成功概率预测器可作为 GFCR Filter 模块的增强——不仅按 score 过滤,还按”轨迹内部的信息对比度”筛选
  3. 成本感知训练:TRACE 的预算分配思想可直接应用于 HyTuner 的 rollout 生成阶段,减少无效采样

⚠️ TRACE 目前主要在数学推理、多跳 QA、函数调用场景验证(8B-14B),在更大规模 Agent 场景的泛化性待验证。

GRPO 组内信号增强:AdaPrefix-GRPO

GRPO 在复杂问题上会遇到”组内全失败→无梯度信号”的问题——所有 G 条 rollout 都失败时,组内没有对比,梯度无法传播。AdaPrefix-GRPO(arXiv:2607.07674, 2026-07-08, Beliaev)解决了这个问题:通过反馈控制器动态调整解前缀长度,将成功率维持在 ~50%(GRPO 梯度信号最大处),训练结束后逐步撤销辅助前缀。0.6B 模型准确率 2.1× 提升,Qwen3-1.7B 1.6×,AIME 1.7×,同时轨迹长度缩短约一半。

五个维度的 GRPO 优化全景:

维度方法解决的问题
组采样架构SAO”不要等组”——废除组采样,单 rollout 异步训练
组内信号AdaPrefix-GRPO”组内没信号”——动态调难度保梯度
采样预算TRACE”选什么 rollout”——高信息量优先
梯度质量GAIN-RL”用哪些梯度”——过滤低价值梯度
归一化方式AsyncWebRL”归一化有偏”——1/

⚠️ AdaPrefix-GRPO 目前仅在 0.6B-7B 模型验证,大规模部署待确认。

GRPO 归一化修正:AsyncWebRL

AsyncWebRL(“Efficient Asynchronous Reinforcement Learning for Multi-Step Visual Web Agents,” arXiv:2606.05597, 2026-06-04, UIUC + Microsoft + CMU)从系统与算法两侧同时优化多步 Web Agent 的 RL 训练,其中算法侧的发现对 GRPO 信用分配有直接的理论澄清意义。

核心发现——归一化方式本身就是信用分配的关键变量:多步 GRPO 的每轨迹归一化项 1/|τᵢ|(除以轨迹长度)是长度偏置的根因——失败轨迹系统性地比成功轨迹更长,1/|τᵢ| 会稀释失败 token 上的负梯度权重,导致策略持续产出冗长、低效的轨迹(例如反复生成 verbose 的记忆模式)。AsyncWebRL 将 1/|τᵢ| 替换为常数 1/k(与轨迹长度解耦),打破了”失败越长→惩罚越弱”的耦合,在保持总体成功率的同时显著收缩轨迹长度。

与 Nexus + HyTuner 的契合点:

  1. 对 SC-GRPO/GAIN-RL 信用分配讨论的直接补充:SC-GRPO 用 token 级 KL 权重识别关键 token,GAIN-RL 用梯度角度过滤低价值梯度,但两者都默认了 GRPO 的每轨迹归一化方式。AsyncWebRL 揭示——在讨论”给哪些 token 更多信用”之前,应先修正”归一化方式”这个更底层的偏置源。
  2. HyTuner 配置候选:如果 HyTuner 的 GRPO 实现使用 1/|τᵢ| 归一化,长程 Agent 任务(如 SWE-bench、Terminal-Bench)会系统性低估失败轨迹的梯度,导致 Agent 学不会”何时该收手”。建议在 HyTuner 集成文档中标注 GRPO 归一化参数,并针对长程任务评估 1/k 常数归一化。
  3. 效果参照:WebGym 分布外测试集 +5.8%(相对此前最佳 42.9%),困难切片收益最大(Medium +42%、Hard +48%)。

⚠️ AsyncWebRL 的归一化修正源自视觉 Web Agent 场景的实证观察(失败轨迹更长),在其他任务域的普适性待验证。

Cache 体系:轨迹重放与确定性复现

Nexus 的 Cache 系统允许你”录制”一次执行过程,然后在后续运行中”重放”——LLM 调用直接从缓存返回,不再请求 API。

启用方式

runner:
  cache:
    enabled: true
    file_path: "/tmp/cache/trajectory.pkl"
    ignore_args_hash: false
    overwrite_tags: ['tool']  # 重放 LLM,但重新执行工具

使用场景

  1. 确定性复现:相同输入 → 相同输出,便于调试
  2. 节省 API 成本:调试工具逻辑时不需要重复调用 LLM
  3. 部分重放:overwrite_tags: ['tool'] 只缓存 LLM 调用,工具每次都真实执行

Cache 文件是 pickle 格式,包含所有被 @cacheable 装饰的函数的输入输出。LLM Client 的 create_completion 和 Tool 的执行函数都标记了 @cacheable。

Reward System

nexus/utils/reward_system.py 和 nexus/utils/reward_score.py 提供了 reward 计算工具。

对于自定义 Task,在 TaskRunOutput 中使用 WithScore Mixin:

class MyTaskOutput(TaskRunOutput, WithScore):
    pass

# Task 函数中
return MyTaskOutput(score=calculated_score)

Score 会自动被 HyTuner runner 提取为 reward_score。

导出训练数据

离线训练替代路径

HyTuner 在线 RL 效果好但成本高。OffSeeker(arXiv: 2601.18467)验证了 8B 模型纯离线训练(SFT → DPO)在 6 个基准上领先同规模模型,且与 30B 在线 RL 模型竞争力相当。这意味着从 Nexus 批量评测结果直接构造 SFT + DPO 数据是一种低成本的可行方案——无需在线 RL,只需跑足够多的评测批次。

离线路径的工作流:

Nexus 批量评测 → ChatTracker 轨迹 + Score
        ↓
筛选高分轨迹 → SFT 冷启动数据
筛选偏好对   → DPO 对齐数据
        ↓
离线训练(无需 HyTuner 在线交互)

Rollout 搜索增强

TSR(Trajectory-Search Rollouts, arXiv: 2602.11767)在训练时的 rollout 阶段执行轻量级树搜索(best-of-N / beam / shallow lookahead),选择高分 action 构建高质量轨迹。与 PPO/GRPO 配合,在 Sokoban/FrozenLake/WebShop 上最高提升 15%。核心思想:将搜索从推理时移到训练时的 rollout 阶段。

在 Nexus + HyTuner 集成中,TSR 可作为高级优化选项——在 HyTuner 的 rollout 阶段,不直接用模型采样的 action,而是执行轻量级搜索后选择最优 action,从而产出更高质量的训练轨迹。

导出训练数据(续)

批量评测完成后,Nexus 提供多种导出格式:

# JSONL 格式(每行一个 run 的完整结果)
nexus export results.db --format jsonl -o results.jsonl

# CSV 格式(汇总表)
nexus export results.db --format csv -o results.csv

# 完整目录结构(每个 run 一个子目录,含 config/output/trajectory/logs)
nexus export results.db --format full -o export_dir/

full 格式的目录结构:

export_dir/
├── 00000/
│   ├── config.json      # RunInput
│   ├── output.json      # RunOutput
│   ├── metadata.json    # BatchItem metadata
│   └── processors/
│       ├── chat.json    # ChatTracker 轨迹
│       └── tokens.json  # TokenCounter 统计
├── 00001/
│   └── ...
└── summary.json         # 批次汇总

实战:生产数据接入 RL 训练(Terminal-Bench 2.0 格式)

⚠️ 版本提示(2026-09-14):Terminal-Bench 现行版本为 4.0(2026-08-28 发布,66 任务),2.0(2025-11)与 2.1(2026-05)均已退役。本节代码按 2.0 格式编写,4.0 的 task.toml schema 与目录结构兼容性尚未逐字段核对——读者照抄前请先比对官方仓库。

⚠️ 不要只把 “2.0” 全局替换成 “4.0”:任务数(64+ / 89 → 66)与仓库链接都已变,直接替换会制造新的错误。这一节是读者会照着抄代码的一节,替换错了比不改更糟。

另需注意:本节与第 3 章 terminal_task 行是同一问题的两个落点,两处需同步改。另可参见上文 §「验证器侧信用追踪:VICT」——本节教读者写的 verifier_script,其判定结构可复用于训练期信用分配。

⚠️ TB 是持续演进基准,不是离散版本序列:官方 README(2026-10-03 抓取)原文把 TB 定义为「a benchmark designed to measure the frontier of agent work with a diverse, difficult, high quality set of tasks that evolve over time」,并自称 continuous benchmark;数据集的 latest published version 挂在 Harbor Hub 上,而不是某个固定 tag。所以本节的兼容提醒不该读成”等 5.0 出来再迁一次”,而应读成:每次批量跑之前重新对齐任务集版本与 task.toml schema。

⚠️ 运行口径只认官方仓库:本轮在第三方榜单站(datalearner,页面标注更新于 2026-10-01)上见到”每任务跑 5 次 / 8 小时超时上限 / 报告 95% 置信区间 / 修复 19 个任务、移除 8 个任务”等表述。这些字面在官方 README 里一处都没有(8 hours、5 times、95% 均为零命中)。README 里唯一与 5 相关的句子是「run the oracle solutions 5x to confirm all tasks work as expected」——那是验证 oracle 解的次数,不是榜单重复次数,第三方很可能把二者混为一谈。落笔前不得采用;要写运行口径就逐字段抄官方仓库。同理,本节的”66 任务”本轮在 README 里也复现不出来(README 不列任务数),引用前需回官方仓库或 Harbor Hub 数据集页确认。

本节用完整可运行的代码走通:你按 Terminal-Bench 2.0 格式生产了一批任务目录,如何接入 Nexus 做 RL 训练。 真实格式参考:terminal-bench-2

Terminal-Bench 2.0 任务目录结构

每个任务是一个目录(不是 JSON 文件):

terminal-bench-2-tasks/
├── adaptive-rejection-sampler/          # 任务 1
│   ├── task.toml                       # 元数据 + 环境配置(TOML)
│   ├── instruction.md                  # Agent 指令(Markdown)
│   ├── environment/
│   │   └── Dockerfile                # 沙箱镜像构建
│   ├── tests/
│   │   ├── test.sh                   # 验证脚本(bash)
│   │   └── test_outputs.py           # 验证脚本(Python)
│   └── solution/                     # 参考答案(可选)
├── bn-fit-modify/                    # 任务 2
│   └── ...
└── ...(共 64+ 个任务)

task.toml 是核心配置,真实格式(来自 adaptive-rejection-sampler/task.toml):

version = "1.0"

[metadata]
author_name = "jvpoulos"
author_email = "poulos@berkeley.edu"
difficulty = "medium"
category = "scientific-computing"
tags = ["applied-statistics", "adaptive-rejection-sampling", "Bayesian-inference", "simulation"]
expert_time_estimate_min = 180.0
junior_time_estimate_min = 480.0

[verifier]
timeout_sec = 900.0

[agent]
timeout_sec = 900.0

[environment]
build_timeout_sec = 600.0
docker_image = "alexgshaw/adaptive-rejection-sampler:20251031"
cpus = 1
memory = "2G"
storage = "10G"

instruction.md 是 Agent 的指令(纯 Markdown,可能很长):

# Adaptive Rejection Sampling

Implement an adaptive rejection sampler based on Gilks et al. (1992).

## Requirements
1. Allow user to provide a function for the (potentially unnormalized) log density...
2. Perform input validation...
3. Check log-concavity during sampling...

Step 1:写 Generator — 遍历任务目录,产出 BatchItem

# my_production_tasks/generator.py
import json
import toml
from pathlib import Path
from nexus import BatchItem, RunInput

TASKS_DIR = Path(__file__).parent.parent / "terminal-bench-2-tasks"


def generate_configs() -> list[BatchItem]:
    """Terminal-Bench 2.0 任务目录 → Nexus BatchItem。"""
    batch = []

    for task_dir in sorted(TASKS_DIR.iterdir()):
        if not task_dir.is_dir():
            continue
        task_toml = task_dir / "task.toml"
        if not task_toml.exists():
            continue

        # 1. 读取 task.toml
        cfg = toml.loads(task_toml.read_text())

        # 2. 读取 instruction.md(作为 Agent prompt)
        instruction = (task_dir / "instruction.md").read_text()

        # 3. 读取验证脚本路径(用于后置评估)
        test_sh = task_dir / "tests" / "test.sh"
        test_py = task_dir / "tests" / "test_outputs.py"
        verifier_script = None
        if test_sh.exists():
            verifier_script = str(test_sh)
        elif test_py.exists():
            verifier_script = str(test_py)

        batch.append(BatchItem(
            config=RunInput(
                imports=["nexus.extensions.tasks.tb2_task"],
                task_name="tb2_task",
                task_config={
                    "task_id": task_dir.name,
                    "instruction": instruction,
                    "docker_image": cfg["environment"]["docker_image"],
                    "verifier_timeout": cfg["verifier"]["timeout_sec"],
                    "agent_timeout": cfg["agent"]["timeout_sec"],
                    "difficulty": cfg["metadata"]["difficulty"],
                    "category": cfg["metadata"]["category"],
                    "verifier_script": verifier_script,
                },
                runtime_provider_name="docker",
                runtime_configs={
                    "main": {
                        "image": cfg["environment"]["docker_image"],
                        "cpus": cfg["environment"].get("cpus", 1),
                        "memory": cfg["environment"].get("memory", "2G"),
                    }
                },
                agent_name="react_agent",
                agent_config={
                    "max_iterations": 50,
                    "timeout": cfg["agent"]["timeout_sec"],
                },
                openai_clients={
                    "main": {
                        "name": "openai",
                        "config": {
                            "client_args": {
                                "api_key": "${oc.env:OPENAI_API_KEY}",
                                "base_url": "${oc.env:OPENAI_BASE_URL}",
                            },
                            "request_args": {
                                "model": "${oc.env:MODEL_NAME}",
                                "temperature": 0.1,
                            }
                        }
                    }
                },
            ),
            run_id=task_dir.name,
        ))

    return batch

关键点:

  • task.toml 的 [environment].docker_image 直接映射到 runtime_configs.main.image
  • instruction.md 全文作为 Agent 的 user_prompt
  • tests/test.sh 挂到 task_config.verifier_script,Task 函数里后置执行

Step 2:写 Task — 执行 Agent + 运行验证脚本

# my_production_tasks/tb2_task.py
from nexus.core import (
    TaskRunInput, TaskRunOutput, WithAgentRunner, WithScore,
    task_registry, require_context,
)
from dataclasses import dataclass
import asyncio


@dataclass
class TB2Input(TaskRunInput, WithAgentRunner):
    task_id: str
    instruction: str
    docker_image: str
    verifier_timeout: float
    agent_timeout: float
    difficulty: str
    category: str
    verifier_script: str | None


@dataclass
class TB2Output(TaskRunOutput, WithScore):
    verification_pass: bool
    num_iterations: int
    category: str
    difficulty: str


@task_registry.register("tb2_task")
async def run_tb2_task(task_input: TB2Input) -> TB2Output:
    ctx = require_context()

    # 1. 调用 Agent(instruction.md 全文作为 prompt)
    agent_result = await task_input.run_agent(
        user_prompt=task_input.instruction,
    )

    # 2. 运行验证脚本(tests/test.sh 或 test_outputs.py)
    verification_pass = False
    if task_input.verifier_script:
        # 把验证脚本复制到沙箱内执行
        script_name = Path(task_input.verifier_script).name
        await ctx.runtime.exec(
            f"cp /task_mount/tests/{script_name} /tmp/ && bash /tmp/{script_name}",
            timeout=task_input.verifier_timeout,
        )
        # 注意:需要根据脚本实际退出码判断,这里简化为 exec 是否成功
        verification_pass = (await ctx.runtime.exec(
            f"bash /tmp/{script_name}", timeout=task_input.verifier_timeout
        )).exit_code == 0

    # 3. 评分(支持 reward shaping)
    if verification_pass:
        score = 1.0
    elif agent_result.get("num_iterations", 0) > 0:
        score = 0.3   # 有尝试但未通过,给部分分
    else:
        score = 0.0

    return TB2Output(
        score=score,
        verification_pass=verification_pass,
        num_iterations=agent_result.get("num_iterations", 0),
        category=task_input.category,
        difficulty=task_input.difficulty,
    )

Step 3:注册模块

# my_production_tasks/pyproject.toml
[project]
name = "tb2_tasks"
version = "0.1.0"

[tool.nexus]
tasks = { tb2_task = "my_production_tasks.tb2_task::run_tb2_task" }

Step 4:批量运行

# 检查 generator 产出(不真正跑,只打印配置)
nexus inspect-generator my_production_tasks/generator.py

# 批量运行(8 并发,每个任务最多跑 15 分钟)
nexus run-batch \
  -g my_production_tasks/generator.py \
  -d results/tb2.db \
  --max-concurrent 8 \
  --timeout 1800

# 查看进度
nexus inspect-batch results/tb2.db

# 重试失败的(最多重试 2 次)
nexus run-batch -d results/tb2.db --retry-only --max-retries 2

Step 5:导出 → RL 训练格式

# 导出完整目录(含每条轨迹的 chat.json)
nexus export results/tb2.db --format full -o export/tb2/

转换成 HyTuner 训练格式:

# scripts/convert_tb2_to_hytuner.py
import json
from pathlib import Path
from typing import Any


def convert(export_dir: str, output_path: str) -> None:
    """
    Terminal-Bench 2.0 评测结果 → HyTuner RL 训练格式。

    每条样本包含:
      - prompt: 初始 system + user message
      - response: Agent 完整回复轨迹
      - reward: task score(0.0 / 0.3 / 1.0)
      - task_id: 任务目录名
      - category / difficulty: 从 task.toml 读取
      - trajectory: 完整 chat 轨迹(用于 SFT warm-start)
    """
    samples: list[dict[str, Any]] = []

    for run_dir in sorted(Path(export_dir).iterdir()):
        if not run_dir.is_dir():
            continue

        config = json.loads((run_dir / "config.json").read_text())
        output = json.loads((run_dir / "output.json").read_text())

        # chat.json 可能不存在(如果没配 ChatTracker processor)
        chat_path = run_dir / "processors" / "chat.json"
        chat = json.loads(chat_path.read_text()) if chat_path.exists() else []

        # 提取 Agent 回复(所有 assistant 消息拼接)
        assistant_replies = [
            msg.get("content", "")
            for msg in chat
            if msg.get("role") == "assistant"
        ]

        samples.append({
            "task_id": config["task_config"]["task_id"],
            "category": config["task_config"]["category"],
            "difficulty": config["task_config"]["difficulty"],
            "prompt": chat[0]["content"] if chat else "",
            "response": "\n\n".join(assistant_replies),
            "reward": output.get("score", 0.0),
            "verification_pass": output.get("verification_pass", False),
            "trajectory": chat,
        })

    Path(output_path).write_text(
        json.dumps(samples, ensure_ascii=False, indent=2)
    )
    print(f"转换完成:{len(samples)} 条样本 → {output_path}")


if __name__ == "__main__":
    convert("export/tb2", "export/tb2_rl.json")

完整数据流

Terminal-Bench 2.0 任务目录(64+ 个)
├── task.toml        ← 元数据 + 环境配置
├── instruction.md   ← Agent 指令
└── tests/test.sh    ← 验证脚本
        ↓  generator.py(遍历目录,解析 TOML + MD)
Nexus BatchItem 列表(每个任务一个 BatchItem)
        ↓  nexus run-batch
SQLite 结果(agent 输出 + score + 轨迹)
        ↓  nexus export --format full
完整目录(config + output + chat 轨迹)
        ↓  convert_tb2_to_hytuner.py
HyTuner 训练格式(JSON,含 reward)
        ↓  HyTuner 训练循环
更新后的 Policy

关键设计决策

  1. Generator 是目录遍历器 — 新增任务只需往目录里扔一个新文件夹,generator 自动发现
  2. instruction.md 全文作 prompt — TB2.0 的指令可能很长(含代码要求、测试说明),直接透传给 Agent
  3. Reward Shaping — score 设计细粒度(未尝试=0,尝试未过=0.3,通过=1.0),比二元 0/1 效果好
  4. 轨迹保留 — chat.json 保留完整对话,用于 SFT warm-start 或 DPO 偏好对构造
  5. 按 category/difficulty 分层 — 导出时保留这些字段,RL 训练时可以做 curriculum learning

常见坑

问题原因解法
验证脚本找不到沙箱内路径和宿主机不同用 runtime.exec 在容器内跑,或挂载任务目录到 /task_mount
Docker 镜像拉取慢64 个任务可能用不同镜像预热:docker pull 提前拉取
instruction.md 太长某些任务指令超过 10k tokenAgent config 里开 truncate_prompt

| Reward Hacking | Agent 碰巧通过 test.sh 但没真正解决问题;或任务目录本身携带可作弊路径(答案文件、可直读的 test.sh、公开的 solution) | 人工 review 一批高分轨迹,加细粒度 rubric;并先检查你所接入的基准是否已有反作弊静态检查 | | 数据分布不均 | 某个 category 特别多(如 coding) | Generator 里做 stratified sampling | | 分数都很高 | 任务太简单 | 增加难度梯度,或加 adversarial instance |

上面 Reward Hacking 那一行的”先查基准侧”不是泛泛而谈,Terminal-Bench 官方仓库在 2026-09-29 一天内把这件事做成了 CI:

commit日期做什么
Add static check for task cheat directories (#2072)09-29对任务目录做静态检查,查是否存在可让 agent 作弊的路径
Remove cheat directories from tasks (#2058)09-29把已发现的作弊目录从任务里删掉
Check README metadata against task.toml (#2082)09-29README 元数据与 task.toml 对账
Add /regrade to re-verify existing trials with a PR's verifiers (#2109)09-29用 PR 里的新 verifier 重验已有 trial
Harden leaderboard workflows against fork-controlled content (#2081)09-23榜单流水线加固,防 fork 控制内容

含义有两层。一是作弊面的位置变了:不只是”agent 碰巧通过”,还包括”任务目录里本来就有可作弊的路径”,而这条现在已经是可被静态检查的——自己造任务时,同一套检查思路(答案文件是否可读、test.sh 是否可被直读、solution 是否留在镜像里)应当进你自己的 CI。二是 /regrade 直接服务本章的 verifier 复用主题:已经跑过的 trial 可以拿更新后的 verifier 重验,不必重跑 agent——改了评分口径之后回测历史数据的成本因此大幅下降。


延伸:从生产数据 → TB2.0 格式

如果你自己生产了一批任务,想复用这套接入方式,只需把数据转换成 TB2.0 目录结构:

# scripts/convert_production_to_tb2.py
from pathlib import Path
import toml


def convert_production_task(sample: dict, output_dir: Path) -> None:
    """
    sample = {
        "task_id": "my_task_001",
        "instruction": "...",
        "docker_image": "myimage:latest",
        "category": "coding",
        "difficulty": "medium",
        "test_script": "#!/bin/bash\n...",
    }
    """
    task_dir = output_dir / sample["task_id"]
    task_dir.mkdir(parents=True, exist_ok=True)
    (task_dir / "tests").mkdir(exist_ok=True)

    # 写 task.toml
    cfg = {
        "version": "1.0",
        "metadata": {
            "author_name": "auto-generated",
            "difficulty": sample["difficulty"],
            "category": sample["category"],
        },
        "verifier": {"timeout_sec": 300.0},
        "agent": {"timeout_sec": 300.0},
        "environment": {
            "docker_image": sample["docker_image"],
            "cpus": 1,
            "memory": "2G",
        },
    }
    (task_dir / "task.toml").write_text(toml.dumps(cfg))

    # 写 instruction.md
    (task_dir / "instruction.md").write_text(sample["instruction"])

    # 写验证脚本
    (task_dir / "tests" / "test.sh").write_text(
        sample.get("test_script", "exit 0")
    )

关联进展:长程工具 Agent RL

上述 Terminal-Bench 接入实战以离线生成 + 后置验证为主。对于需要在线 RL 训练长程工具调用 Agent 的场景,近期工作(arXiv:2608.10357)采用 rollout workers + GRPO 的训练管线,与本节的生成器设计形成互补:

  • 本节路线:Generator 遍历任务目录 → 批量评测 → 导出轨迹 → 离线 SFT/DPO/RL(适合预算有限、追求可复现性的场景)
  • rollout workers 路线:常驻 rollout worker 池持续产生轨迹 + GRPO 在线更新(适合追求训练吞吐、可接受异步离策略偏差的场景)

两者的共同点是都把”任务目录格式(task.toml + instruction.md + tests/)“作为环境标准。若后续 HyTuner 集成需要在线 RL 训练长程工具 Agent,可将本节 Generator 的产物(BatchItem)改为由 rollout workers 持续消费,复用同一套 Task/Agent 定义。