研究周报 · 2026.04.26–05.09 · C3/CCPO 反事实信用分配与 minimal-agent-harness 搭建

字数 2,560 预计阅读 7 分钟

精读 C3、CCPO 两篇反事实信用分配论文,对比 message-level/agent-level counterfactual 与 CAD-GRPO 的 tradeoff;Q&A 讨论全对/全错 trajectory 的价值(NPO/DAPO);搭建 minimal-agent-harness 单 agent rollout 环境;规划 CAD-GRPO 实验框架。

作者 Yoyo_Lee 发表于

这两周继续围绕 multi-agent credit assignment 读了两篇 counterfactual 方向的工作——C3 和 CCPO。
补了一轮 Q&A,把全对/全错 trajectory 的价值问题从 GRPO 梯度信号和 NPO 辅助轨迹的角度理了一遍。
另外手动复现了一个 minimal-agent-harness,为后面的 multi-agent RL 实验搭 rollout 环境。

本周清单

  1. 阅读 C3 / CCPO 两篇 counterfactual credit assignment 论文
  2. Q&A:全对 / 全错 trajectory 是否一定没有价值(DAPO / NPO)
  3. 复现 minimal-agent-harness,搭建单 agent rollout 环境
  4. to-do:CAD-GRPO 实验框架规划

一、阅读论文

multi-agent 训练里不能直接把 team reward 粗暴广播给所有 agent,否则 free-riding / lazy agent / credit contamination 这些问题都会冒出来。但代价也很明显,都要构造某种反事实轨迹或反事实 replay。

1.1 C3: Contextual Counterfactual Credit Assignment for Multi-Agent Reinforcement Learning in LLM Collaboration (arXiv:2603.06859)

C3 这篇主要解决的是 trajectory-level reward 太粗的问题。multi-agent LLM collaboration 里一条轨迹通常是多个 agent 轮流发 message,最后才有一个 sparse terminal reward。standard GRPO / MAGRPO 这类方法会把最终 reward 反传到整条轨迹上,但它并不知道到底是哪一句 message 让系统从失败变成成功,或者从成功变成失败。

核心 idea: C3 把每个 message 当成一个 macro-action,在某个 decision point 上固定已有 transcript-derived context,然后只替换当前 message,后面的交互用固定 continuation replay 继续跑。

可以粗略写成:

Au,j=Rˉ(u,aj)1Au1ajAu,jjRˉ(u,aj)A_{u,j} = \bar{R}(u, a_j) - \frac{1}{|\mathcal{A}_u|-1}\sum_{a_{j'} \in \mathcal{A}_u, j' \neq j}\bar{R}(u, a_{j'})

其中 uu 是某个 message-level decision point,aja_j 是在这个固定 context 下采样到的一个候选 message,Rˉ(u,aj)\bar{R}(u, a_j) 是把这个 message 接到后续 fixed continuation 之后得到的平均回报。后半项就是 leave-one-out baseline,用同一个 context 下的其他候选 action 做对照。

控制住了 prompt 难度、上下文状态、后续 agent 的随机性等因素。比直接用 trajectory reward 干净很多。

启发:

把 credit assignment 的粒度从 trajectory-level 往下拉到了 message-level。不过有一点还没完全想清楚:C3 的 fixed continuation replay 要求框架能非常稳定地复现某个中间上下文和后续执行,这在真实 coding agent / tool-use agent 里不一定容易。tool 调用有副作用,环境状态也可能不是纯文本 transcript 能完全表示的,这块后面如果要接到 harness 上,需要单独 verify。

1.2 CCPO: Counterfactual Credit Policy Optimization for Multi-Agent Collaboration (arXiv:2603.21563)

CCPO 关注的问题是多个 agent 协作解题时,team reward 会掩盖每个 agent 的真实贡献,导致有的 agent 明明没帮忙也跟着吃正 reward,有的 agent 明明做对了但被队友拖累。

核心 idea: 对每个 agent 构造一个"去掉该 agent 贡献"的反事实 baseline,然后用真实 team reward 和反事实 reward 的差作为这个 agent 的 marginal contribution:

Δi=Rteam(τ)R¬i(τ¬i)\Delta_i = R_{\text{team}}(\tau) - R_{\neg i}(\tau_{\neg i})

其中 τ¬i\tau_{\neg i} 表示把 agent ii 的贡献删除或屏蔽之后得到的 counterfactual trajectory,R¬iR_{\neg i} 是这个反事实轨迹的 team reward。如果 Δi>0\Delta_i > 0,说明 agent ii 对最终结果有正贡献;如果 Δi0\Delta_i \le 0,说明它的输出可能是冗余的,甚至是有害的。

论文里在不同 collaboration topology 下定义了不同的反事实构造方式。比如 Think-Reason dyad 里,可以把 thinker 的 reasoning trace 拿掉,让 solver 直接回答;voting 场景里,可以直接从投票集合里删掉某个 agent 的 vote,再重新聚合剩下的 vote。后者几乎不需要额外 decoding,所以 counterfactual 的成本会低很多。

然后 CCPO 还加了 global-history-aware normalization,用历史 rollout 的统计量去校准不同阶段、不同任务分布下的 Δi\Delta_i scale。

启发:

CCPO 可以看成 CAD-GRPO 的一个强 counterfactual baseline。可以在小规模环境里先用反事实方法估一个更可信的 marginal credit,再看 CAD-GRPO 的 β^\hat{\beta} 和它的相关性。

1.3 C3 和 CCPO 的对比

方法 粒度 反事实对象 主要优势 对 CAD-GRPO 的意义
C3 message / decision-level 固定 context 下替换某个 message 信号非常细,可以定位具体哪一步有用 给后续 step-level extension 提供思路
CCPO agent-level 删除某个 agent 的贡献 更贴近 multi-agent credit decomposition 可以作为 CAD-GRPO 的 counterfactual baseline
CAD-GRPO agent-level 不显式生成反事实,用 batch 内自然变异做回归 理论上零额外 rollout 开销 需要验证 qiq_i 的充分性和 β^\hat{\beta} 的稳定性

实验里应该同时有"强反事实 baseline"(比如 CCPO / SHARP / C3 的某种简化版本)和"零开销 baseline"(比如 Dr.MAS),然后证明 CAD-GRPO 处在一个比较好的 cost-performance tradeoff 上。


二、Q&A

关于全对 / 全错 trajectory 是否一定没有价值

Q: 这个全对和全错的 traj 一定没有价值吗?这个感觉可以思考,可以看看 NPO (arXiv:2604.20733v1),即往里面加一个好的 traj,或者修正 traj,其实是有利于探索的。全错里面不一定就是没有有用的信息,可能只是一个步骤的问题导致 failure,因此它里面是可以进行探索的。

A: 我觉得对 standard GRPO objective 有没有梯度价值,和这条 trajectory 本身有没有信息价值不是一回事。

在 GRPO 里,如果同一个 prompt 下采样的 group 全对或者全错,那么所有 reward 都一样:

r1=r2==rKr_1 = r_2 = \cdots = r_K

组内归一化之后:

AiGRPO=riμGσGA_i^{\text{GRPO}} = \frac{r_i - \mu_G}{\sigma_G}

这里要么 σG=0\sigma_G = 0,要么实现里会用某种 clipping / filtering 直接跳过。也就是说,从这一角度看,全对 / 全错 group 确实没有可用的相对优势信号。DAPO 里的 dynamic sampling 会把这类 batch 丢掉。

但 trajectory 有信息价值

(1)全对 trajectory 的价值

全对说明当前 prompt 对模型来说可能太容易了,组内没有差异,GRPO 没法区分哪条更好。NPO 这篇的思路里,有效学习信号看 traj 和当前 policy 的距离。论文把这个 tradeoff 写成:

S=QV\mathcal{S} = \frac{Q}{V}

其中 QQ 代表 auxiliary trajectory 的质量,VV 代表因为 off-policy / distribution mismatch 带来的 variance cost。外部 teacher 的 trajectory 可能 QQ 高但 VV 也高;过去 checkpoint 的 trajectory 可能 VV 低但 QQ 不够。NPO 的做法是用同一次训练里的 near-future checkpoint 提供辅助轨迹,因为它比当前 policy 强一点,同时又没有离当前分布太远。

如果当前 group 全对,本身没有 pairwise contrast,但可以从另一个角度构造 contrast。比如加入一个 near-future / repaired / higher-quality trajectory。这样全对不再只是 binary reward 下的一坨 1,而是可以变成一个更细粒度的学习信号来源。

(2)全错 trajectory 的价值

我倾向于把全错 trajectory 看成需要被重新表征的信息。可行的处理方式可能有几种:

  1. process / step-level proxy:给中间步骤引入可验证指标,比如代码场景里的 compile success、unit test partial pass、lint/format 是否通过。
  2. counterfactual repair:像 C3 那样固定 context,只替换某个 message 或 step,看是否能把失败轨迹修成成功轨迹。
  3. guided contrast:像 NPO 那样加入一个更强但分布相近的 guide trajectory,让原本全错的 group 里出现可比较对象。
  4. agent-level decomposition:用 CAD-GRPO 的 qiq_i 尝试识别"虽然 team reward 为 0,但 agent ii 的局部质量是否高于队友/高于均值"。

综上,全对/全错的 trajectory 是有价值的,如果只用 binary final reward,很多 batch 会没有足够 variance,回归也会不稳。


三、复现 minimal-agent-harness

先在 blog 上写了 Claude Code 的架构分析,然后手动复现了一个 400loc 的单 agent harness。重点关注状态、权限、上下文和错误恢复怎么组织。

该项目 minimal-agent-harness,已经推送 GitHub。这个版本没有追求功能完整,主要目标是把最小闭环跑通:

  1. 消息循环:维护 system / user / assistant / tool result 这几类消息,让模型每次都能在完整历史上继续决策。
  2. tool registry:把可用工具注册成结构化 schema,模型只负责提出 tool call,执行逻辑由 harness 统一接管。
  3. tool dispatch:根据 tool name 分发到对应 handler,并把 stdout / stderr / error message 重新包装成 observation。
  4. 终止条件:区分"模型给出最终回答"和"模型继续请求工具",避免 loop 无限制跑下去。
  5. 错误处理:tool 调用失败时不直接崩掉,而是把失败结果作为上下文返回给模型,让它有机会 self-correct。

本质上就是一个受控的 rollout environment。LLM policy 每次输出 action,tool/environment 返回 observation,context manager 决定历史怎么保留,stop condition 决定 episode 什么时候结束。

这和后面要做的 multi-agent RL 实验是直接相关的。因为如果要训练 coding agent 或 multi-agent solver,首先就要把每次 rollout 记录成结构化数据:

τ={(st,at,ot,rt)}t=1T\tau = \{(s_t, a_t, o_t, r_t)\}_{t=1}^{T}

在 LLM agent 里,sts_t 是一整段 message history;ata_t 可能是自然语言,也可能是 tool call;oto_t 可能是命令输出、文件 diff、测试结果或者 verifier feedback。rtr_t 自然是奖励信号。

从 CAD-GRPO 的角度看,harness 至少要额外暴露几类信息:

  • 每个 agent / role 在每个 step 的输出;
  • 每个 tool call 的执行结果和失败类型;
  • 每个 agent 对最终 artifact 的可验证 proxy,比如 compile success、test pass、格式约束、局部 verifier score;
  • team-level final reward;
  • prompt id / group id / rollout id,方便后面做 group-relative normalization 和 batch-level regression。

四、to-do

4.1 开始搭建 CAD-GRPO 实验框架

尝试先把数据流打通:

  1. rollout schema:定义 prompt_id / group_id / rollout_id / agent_id / step_id / message / tool_call / observation / q_i / reward 这些字段,保证后面能直接从日志里还原 per-agent trajectory。
  2. quality proxy:先在 coding 或 toy math 环境里定义便宜的 qiq_i,比如格式合规、局部答案可验证、compile/test partial pass、是否产生有效中间结果等。
  3. batch-level ridge regression:实现最小版 CAD-GRPO credit estimator:

Rb,k=μ+αb+iβiqb,k,i+ϵb,kR_{b,k} = \mu + \alpha_b + \sum_i \beta_i q_{b,k,i} + \epsilon_{b,k}

然后输出每个 agent 的 β^i\hat{\beta}_i 和去混淆 advantage。

  1. baseline:先实现 naive team-reward GRPO、Dr.MAS-style per-agent normalization、以及一个简化的 counterfactual oracle(小环境里可以手工删 agent 或替换 agent 输出)。

4.2 把 harness 的实验日志接起来

让它能产出训练需要的日志。计划先加三个东西:

  1. 每次 tool call 和 message 的结构化 trace export;
  2. 一个简单 verifier,把 final reward 和局部 proxy 同时写入日志;
  3. 一个离线 analysis script,读日志之后能直接生成 regression matrix X=[q1,,qN]X=[q_1,\ldots,q_N] 和 reward vector yy

如果这条链路能跑通,后面就可以先在小 batch 上验证 CAD-GRPO 的 β^i\hat{\beta}_i 能不能奏效。


劳动节快乐!