第 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 接口,均属推论,实现前须读全文。

📌 复用 verifier 结构的前提:verifier 自身要与 agent 代码隔离(2026-11-10 补)。上面”常见坑”一节新增的第三类作弊面(verifier 进程内执行 agent 代码)与本节是同一件事的两个半边:VICT 复用的是 verifier 的判定结构(atoms + 证明边),RewardKit Worker 隔离的是 verifier 的执行环境——结构可复用的前提是执行已隔离。VICT”只改 advantage tensor”的集成改动量仍然成立,但其 atoms 的执行环境必须与被测 agent 分离,否则信用分配信号本身可被 agent 篡改(与 Reward Hacking 第三类同根)。

没有标准答案时:用 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 支持历史的记账方式(如何定义”已接受的支持”)属推论,实现前须读原文。

同一场景的另一条路:用数据自带的证据链(EG-GRPO)(2026-10-22 补)。Dr.Credit 问的是”这条工具返回相对 rubric 提供了多少新增支持”,EG-GRPO 问的是”这个证据主张首次被哪条引用支持”——前者需要一个写好的 rubric,后者需要数据里已有证据标注。

EG-GRPO(arXiv:2610.25269,2026-10-18,7 位作者)从合成 deep search 数据里已有的证据标注直接推导 step 级监督:为每个必需的证据主张定位首个支持性引用,用新获得的证据构造 step 级过程奖励;再把累积证据表示为演进的知识状态,据此做 state-conditioned relative advantage,最后与 trajectory 级 outcome advantage 结合。五个 deep search 基准上优于标准 GRPO 与其他 step 级 RL 基线(不同模型规模一致)。

分工:有 rubric → Dr.Credit;数据自带证据标注 → EG-GRPO。

💡 由此可以提炼一条通则(编辑归纳,非原文结论):过程监督的可用性,取决于任务 artifacts 里有没有可对齐的中间结构——Dr.Credit 用的是 rubric 条目、EG-GRPO 用的是证据链、VICT 用的是 verifier atoms——而不取决于算法。反过来也就是说:如果一个任务既没有 rubric、数据里也没有证据标注、verifier 又不暴露 atoms,那么换任何 step 级算法都补不出可靠的 process reward。

结构化路由: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
step(价值差)LVRL(2610.15840,2026-10-11,6 位作者)冻结 LLM 当 critic:用预训练 LLM 的先验 + in-context learning 估计 policy-aware 状态价值,以 group context 为条件、用初态/终态已知值标定,取价值差无需训练 critic(但需 LLM 推理)⚠️ 需 group context;摘要级理解,未回 HTML 正文核
数据集侧证据EG-GRPO(2610.25269,2026-10-18,7 位作者)数据自带的证据标注:为每个必需证据主张定位首个支持性引用,用新获得的证据构造 step 级过程奖励需合成数据里已有证据标注限 deep search 类任务;摘要级理解
rubric→token 蒸馏ORSD(2611.06543,2026-11-04,4 位作者)当前策略对同组轨迹独立生成多份 rubric bundle(从跨轨迹行为差异提取候选),冻结 verifier 按”rubric 满足度与任务成功的对齐度”选一份;训练时把 rubric 当特权信息,带/不带 rubric 的 token log-likelihood 差作 token 级过程信用,additive advantage shaping 叠加到 trajectory 级 GRPO advantage需冻结 verifier + 每 step 多份 rubric 生成(推理开销)ALFWorld/WebShop 域、Qwen3-1.7B/4B 验证;摘要级理解

选型判据:有可暴露 atoms 的 verifier → 用 VICT(成本最低,且验证器你已经写好了);有可恢复状态 → 用 Gated-BEPO / GRAFT,恢复成本高则只验关键片段用 ProVer;只有 terminal reward + 多轮 Trace → 用 IAPO(从既有 rollout 重建图)或 MileGPO(从分组 rollout 找 milestone);有 teacher 模型 → 用 AgentOPSD;任务没有标准答案 → 有 rubric 用 Dr.Credit,数据自带证据标注用 EG-GRPO;有 rubric 且想要 token 级粒度、但不想依赖自身验证轨迹(SC-GRPO 前提)→ 用 ORSD;有算力跑 LLM 推理但不想训 critic → 用 LVRL。

📌 LVRL 在本章阶梯里的位置特殊:它是唯一在 SWE-bench Verified 上验证过的 step 级方法(其余 step 级方法多在 ALFWorld / WebShop),与本章的 SWE / Terminal-Bench 场景同域。更关键的是它打破了本章隐含的一个等式——本章此前把”用 critic”等同于”要额外训练一个价值网络,昂贵且不稳定”(Gated-BEPO / GRAFT / ProVer 全部标”无 critic”),而 LVRL 的 critic 是冻结的预训练 LLM,不训练。摘要自述的提升为 AppWorld +13.6pp、SWE-bench Verified +8.6pp(相对 GRPO,4B–32B),BabyAI 从完全失败到 75.0%;Monte-Carlo 分析称冻结 critic 能识别语义陷阱与恢复点。⚠️ 均为摘要自述,未回主表核。

上表是”你手里有什么信号”的静态分档,不是”一条轨迹只能选一档”。 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 场景不同域;风险分解的前提条件本轮未读全文核验。

同一个问题的第二解:AdaStep(2026-10-22 补,arXiv:2610.03223,v1 2026-10-02 / v2 2026-10-11,6 位作者)。它也主张 step 信用应逐状态加权,但走的是推导而不是启发式:把”每个 group-derived local advantage 对 trajectory 级信号的修改强度”建模为潜 step advantage 的 MSE 估计问题,在显式条件采样假设下得出最优的 per-state 收缩系数。该系数有信噪比解释——return 变异可归因于所选 action 时保留局部信用,被下游随机性主导时抑制。无 critic、无额外 rollout、无额外模型推理,只需轻量标量计算;ALFWorld / WebShop / ScienceWorld 上三个 backbone 一致提升。

与 GACA 的分工:GACA 用 NLL 决定混合多少(启发式 + 风险分解),AdaStep 用 MSE 最优决定收缩多少(有显式假设与最优性保证)。两者都是”已选档位之上的调制层,不是新档位”,本章”两步不互斥”的读法对两者同样成立。

⚠️ 两者均为 2026-09~10 预印本,验证域为 ALFWorld / WebShop / ScienceWorld,与本章 SWE / Terminal-Bench 场景不同域;AdaStep 的条件采样假设本轮未读全文核验,“两者可叠加使用”为编辑推断。

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 模型验证,大规模部署待确认。

📌 “组内全失败”的第二解:不调难度,在信用侧恢复信号(2026-11-10 补)。CREDO(arXiv:2611.01643,2026-11-01,TKGQA 场景)指出 GRPO 的两个盲区:下游失败会惩罚有用的证据获取(最终答案错了,获取证据的中间步被连坐);all-failed 组内奖励全同、相对信号归零(尽管中间进度有差异)。CREDO 的做法是在信用侧用同题多 rollout 的阶段对比给出阶段级(stage-level)正信用、用保留答案重叠度作机会代理,且重分配守恒每条轨迹的 outcome-policy 总权重(MultiTQ 78.0%→82.4%,摘要自述)。与 AdaPrefix 的分工:能调难度(可生成前缀/可换题)→ AdaPrefix;难度不可调但保留同题多 rollout → CREDO。另对 GFCR Filter 的含义:all-failed 组的轨迹不再是废数据——至少在证据型任务(TKGQA 域验证)上,它们仍携带可恢复的阶段级信号。

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。

🔴 本条在 19 天后应验了一次(2026-10-22 复核,源:gh api repos/harbor-framework/terminal-bench/commits?since=2026-10-03,一手)。2026-10-06~10-20 上游做了四项直接影响本节代码的变更:

① 任务版本元数据成为必填——Add task version metadata (#2103)(10-06)、Require task versions in task changelogs (#2193)(10-07)、Require PR links in task changelogs (#2083)(10-06)。cfg["metadata"] 的键集合已变,本节 TB2Input 与 task_config 里硬编码的 [metadata].difficulty/category 需回源复核。 ② 所有任务镜像改为 digest 钉死并加 CI 静态检查——Reproducibility: pin all images used in tasks (#1824)(10-07)、Add static CI check for task image digest pins (#2094)(10-07),配套 Install uv from digest-pinned images without changing versions (#2211)(10-07)与 Fix hosted builds with digest-only uv image references (#2220)(10-09)。本节示例的 "alexgshaw/adaptive-rejection-sampler:20251031" 是标签形式,上游已改为 digest。 ③ 归档任务目录改名为 legacy——Rename archive task directory to legacy (#2197)(10-07);同期还有 Lock Playwright MCP dependencies for medical claims (#2203)、Enforce the Cargo lockfile for the KV load generator (#2204)(均 10-07)、Verify the sbt download checksum (#2206)(10-08)。任务集在持续增删,不只是版本号在动。 ④ /regrade 工作流锁定 Python 3.13——Pin regrade workflow to Python 3.13 (#2291)(10-20)。“重验”这个动作本身也有版本依赖。

⚠️ 本轮只从 commit 标题与日期判断,未读 task.toml schema 与官方文档:不要照着 commit 标题猜字段名(是新增 version 字段,还是把 docker_image 改写为 docker_image_digest,本轮未核)。本节 generator 代码改写前必须回官方仓库确认字段确切名称与位置。

🔁 把”重新对齐”具体化为三件事,跑批前各查一次(编辑建议): (a) 镜像是不是 digest——上游 10-07 起全部钉死且有 CI 检查,你接入的镜像若仍是标签,重跑结果不可复现(标签可变,digest 不可变); (b) task.toml 带不带版本字段——上游 10-06~10-07 起任务版本为必填; (c) 任务目录里有没有 legacy/——上游 10-07 把归档目录改名,说明任务集在持续增删,你上次跑的 N 个任务这次可能不是同一批。 (d) 发布产物取的是哪个形态——上游 11-03 起(#2538/#2537)为每个 release tag 同时发布无前缀语义版本别名与 CPU 专用数据集(terminal-bench-cpu),release 还可从 pinned snapshot 无 masked outputs 恢复(#2539,11-03)、hosted judge report 有了 apply lookup(#2703,11-09);接入脚本按 tag 拼 URL 时须确认取的是哪一形态——CPU/GPU 变体混用会引入任务子集差异(此为编辑推断,未核两个数据集的任务集差异)。

其中 (c) 对评测框架最致命:“任务数变了”与”模型退步了”在分数上是同一件事,区分它们的唯一办法是保存每次跑批的任务 ID 清单(此为编辑建议,非官方结论)。

⚠️ 运行口径只认官方仓库:本轮在第三方榜单站(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

⚠️ 并发数不是孤立的(2026-10-22 补)。上游 TB 官方把校验并发提到 1000(Raise validation concurrency to 1000 (#2199),10-07),但同期加了两条约束:[CI] Add volumes check (#1520)(10-14)与 CI: warn on excessive and missing CPU thread limits (#2247)(10-14)。能开到 1000 是因为同时限了卷与 CPU 线程。

本节示例的 --max-concurrent 8 没有配套的资源约束说明。建议选择并发数时同时写清 CPU / 内存 / 磁盘卷的约束(Nexus 侧可对齐第 6 章的三层依赖与第 10 章的 Runtime 资源配额),否则”并发 8”在资源竞争下会退化为不可复现的超时——同样的并发数,在有资源约束与没有资源约束的机器上不是同一个实验。


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;上游(10-15)给的是另一种解法——Add nonblocking effective instruction length rubric advisory (#2257):给”有效指令长度”定 rubric 并做 advisory,而不是直接截断。⚠️ 该条为 nonblocking(不使 CI 失败),且本轮只从 commit 标题判断,未读实现

| Reward Hacking | Agent 碰巧通过 test.sh 但没真正解决问题;或任务目录本身携带可作弊路径(答案文件、可直读的 test.sh、公开的 solution);或verifier 在运行测试/决定 reward 的进程内执行了 agent 提供的代码——agent 可直接篡改打分过程本身 | 人工 review 一批高分轨迹,加细粒度 rubric;并先检查你所接入的基准是否已有反作弊静态检查;凡 verifier 需要 replay/执行 agent 产物的任务,verifier 与 agent 代码必须进程隔离(如 RewardKit Worker) | | 数据分布不均 | 某个 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——改了评分口径之后回测历史数据的成本因此大幅下降。

📌 2026-10-22 复核:静态检查出现了第二类,而这一类不叫反作弊。上游 10-07 新增 Add static CI check for task image digest pins (#2094),与 Reproducibility: pin all images used in tasks (#1824) 配套——它检查的是可复现,不是作弊。

含义是:本章”先检查基准侧”这条建议的覆盖面比原来想的更宽——基准侧的 CI 不只在防 agent 作弊,也在防任务本身漂移。自己造任务时,除上面三条作弊面外应加第四条:镜像引用是否为不可变 digest(此为编辑建议,逻辑与上游做法一致)。

🔴 2026-11-10 复核:第三类作弊面已被官方命名——/cheat 检查。上游 10-29 合并 Add /cheat check for in-process verifier tampering (#2463):为「verifier 在运行测试或决定 reward 的进程内执行 agent 提供的代码」的任务添加 /cheat 检查——即使该 trial 没有利用这一暴露也会被标记,官方建议用 RewardKit Worker 做隔离;配套的 cheating-agent prompt 已升级为会主动尝试这个攻击向量(对应 CI check #1775)。与前两类不同,这一类不是”绕过打分”而是”篡改打分过程本身”——agent 提交的代码若被 verifier 在同进程执行,reward 就是可写的。对自己造任务的含义:“生成代码后由 verifier 重放/跑测试”这一整族任务(代码修复、生成-验证类),verifier 的执行环境必须与 agent 代码进程隔离。

同期(10-22~11-03)官方还把一批任务的 verifier 执行迁入 RewardKit Workers 并记录测量出处(Run Coq checks in RewardKit Workers #2317、Isolate VBA execution and report Reward Kit outcomes #2524、Pin Reward Kit references to 0.2.4 #2527 等),11-03 单日约 20 个任务 verifier 加固化——verifier 执行环境的标准化隔离已是上游的明确方向。


延伸:从生产数据 → 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 定义。