今日概览

今天六篇分成两条线:一条是长程 Agent 的训练侧精细度——小红书 AllSpark 给"每一步该拿多少 Credit"做了唯一性刻画并提出 PACT;另一条是桌面 Agent 运行时的工程侧精细度——MCP 启动隔离与降级、UI 终态与重型 IO 解耦、死循环分级治理、上下文压缩两阶段预热。四篇运行时文章有一个共同母题:关键路径只做最必要的事,重活异步化、可选件可降级、状态靠对账兜底。最后是百万月活产品 AIHOT 的开源复盘。

今日重点

1. 长程 Agent 如何论功行赏?小红书 AllSpark 提出 PACT

一个数学 Agent 长篇推理才得到正确答案,一个 Coding Agent 多次检索、修改、测试才修好程序——最终成功可以验证,但沿途每一步甚至每个 Token 究竟该拿多少 Credit?这是强化学习里的 Credit Assignment 问题,对长程 Agent 而言,它直接决定模型怎么从一个最终结果里学习成千上万次决策。

值得关注:

  • 团队提出三个正则条件并证明满足条件的 Credit 存在且唯一:完整性(所有 Credit 相加完整解释最终奖励相对初始预期的偏差)、前缀一致性(同一已生成前缀的累计 Credit 不被后续轨迹改写)、中立性(下一步尚未发生时,下一个 Token 的 Credit 条件期望为零)。最终形式是"这一步及其反馈揭示后,预期最终奖励发生的变化"——不是把最后一次测试的成败复制给此前每个动作。
  • 一个表示串起三种训练现象:① 在理想教师假设下证明 OPD 的期望策略梯度与唯一 Credit 诱导的策略梯度成比例,因此教师可被理解为一个隐式 Critic;② RLOO 的响应级优势虽不是 Token 级 Credit,但期望梯度贡献相同(梯度等价 ≠ 统计效率相同,采样噪声仍可能不同);③ 证明近似 Credit 稀疏性(有界奖励下超幅 Credit 的期望数量有不随轨迹长度增长的上界),这解释了为什么更细粒度的 GAE 会对 Critic 误差敏感,取 λ=1 可消除中间误差项。
  • **PACT(Policy Aligned Critic Training)**两项改动:用 BCE 替代 MSE 回归归一化到 [0,1] 的有界 Value(固定策略对照中收敛更快、误差更低、更能区分成功/失败轨迹);采用 Actor-Then-Critic 顺序,先用新策略概率对 Critic 训练做重要性采样修正,使其对齐更新后的策略,只需额外一次前向计算。
  • 实测:SWE-bench Verified 上 PACT 达 67.4% Pass Rate(较 GRPO +2.0pp、PPO +2.4pp、SAO +3.8pp);四个数学推理基准 Avg@16 平均准确率 72.87%(较 GRPO +8.80pp、PPO +13.16pp)。消融显示仅移除 Critic 重要性采样修正,数学均分从 72.87% 掉到 67.74%——修正贡献 5.13pp。
  • 价值在于:信用分配被确定下来后,训练方法就有了明确的估计对象,可以进一步研究如何减少估计误差、让 Critic 跟上策略变化。

来源:小红书技术REDtech

2. 开源百万月活级产品 AIHOT,愿每个行业也都有自己的「AIHOT」

作者把月活百万、近 10 万 Skill 用户的 AI 热点资讯站 AIHOT 正式开源(github.com/KKKKhazix/AIHOT),并分享了用 AI 重写 2.0 的全过程。同时开源了采集流程、精选评分流程、聚簇机制以及生产环境里用到的所有 Prompt(信源清单因争议与通用性未放出)。

值得关注:

  • 绕过屎山的重构思路是全文最有价值的部分:作者判断"即使强如 Claude Opus 5.5 / GPT-6 Astra,一旦读了历史代码库也会受屎山影响,重构不彻底",于是回到第一性原理——先把整个旧代码库蒸馏成一个全面、解耦的功能文档,再让模型出一个交接包,完全不给模型看旧代码。
  • 多模型分工:用 Codex + GPT-6 Astra 做第一遍"旧库→功能文档+交接包",同时用 Claude Fable 5.1 把同样的活再做一遍以互相校验,最后由 Claude Opus 5.5 担任绝对开发主力完成开发,全程约 12 步。
  • 重构后收益:架构干净、模块化清晰,Agent 开发时"只需读最少上下文就能精确定位",前端全部组件化并重做 UI;性能和速度大幅提升,2.0 已上线。
  • 定位:核心价值是作为基础框架——法律、HR、金融、贵金属等行业的朋友把仓库交给自己的 Agent,把信源和精选标准换成自己行业的 KnowHow 即可。作者明确不承诺后续每次迭代都同步到仓库。

来源:数字生命卡兹克

3. 可选插件不能拖死核心会话:MCP 服务的启动隔离与平滑降级

MCP 生态铺开后,Agent 运行时挂几个 MCP Server 已是标配,但多 MCP 很容易暴露一个脆弱点:配了 5 个 Server,本地文件读写、代码搜索都正常,偏偏某个辅助翻译或网页检索的第三方 Server 网络抖动超时、或依赖版本不对报错——结果整个 Agent 进程在启动阶段直接 Panic 崩溃,连最基础的本地对话都用不了。一个非核心可选插件挂了,把整个基础会话拉去陪葬。

值得关注:

  • 旧写法就是简单的串行循环:for server_config in mcp_servers { client = connect(...).await?; tools = client.list_tools().await?; }——任意一个 connect 或 list_tools 挂了整段直接 return Err。
  • 配置分级:给每个 Server 加 required 标记(核心 true:挂了明确报错退出;可选 false,默认值:启动失败或超时只记日志并熔断),并支持 per-server 的 timeout_ms。
  • Tokio 并发隔离:为每个 MCP Server 单独 tokio::spawn 一个异步任务并发探测,套上独立 timeout;失败的结果标记为 Degraded 而绝不向外抛异常,探测全部结束后再聚合。
  • 动态工具注册表 + 前台提示:只有成功的工具进注册表,降级项被过滤掉,会话正常拉起并在前台提示"降级运行"。改完后哪怕断网或某个插件配置写错,基础功能依然能秒级拉起。
  • 结论:依赖外部环境的插件系统必须做好隔离与降级,不要让一个锦上添花的边缘功能影响核心可用性。

来源:Code is cheap, let’s talk

4. 解耦重型 IO 与 UI 终态:消除桌面 Agent 输入框假死与流式卡顿

桌面/Web Agent 客户端常见的烦人体验:模型已经把最后一个字输出完了,输入框却依然置灰显示"任务执行中…",等几秒甚至十几秒才解锁;中途切窗口有时直接永久卡死在 loading。看起来是前端状态没绑好,顺调用链排查后本质是后端异步持久化与事件分发顺序没理顺。

值得关注:

  • 旧写法是等当前轮所有持久化完成才发终态:await persistTurnToSqlite()(50–200ms)→ await materializeImagesToDisk()(500ms–2s)→ await appendSessionJsonl()(100–500ms)→ 最后才 emitToRenderer('agent_end')。长会话或机械硬盘上这一步动辄卡几秒。
  • 三个卡顿来源:① 重型持久化 IO 阻塞 agent_end 终态通知;② 前端为保序维护的顺序事件队列(Emit Chain),模型收尾时吐的一堆诊断日志会把真正的 agent_end 排到后面;③ 用户在生成中途切走窗口,Chromium 降低后台定时器频率,跨进程事件抖动丢包,前端把 agent_end 丢了就永久卡在"执行中"。
  • 三项优化:① 乐观判定——只要前端收到 MessageComplete 且满足"无后续工具"等三个条件就立刻解禁输入框,不等磁盘写完,用户感知延迟直接归零;② Bypass 快轨——agent_end/error/cancelled 等生命周期事件优先级最高,在 IPC 层单独走通道,不和文本增量排队;③ 10 秒周期 Stale 对账——前端定时扫"超过 10 秒无新数据却仍挂着 streaming 标记"的会话,主动校准清理,兜住窗口失焦和极端丢包造成的孤儿状态。
  • 结论:前台交互尽可能乐观敏捷,耗时持久化放后台异步跑,中间靠定期对账兜底一致性。

来源:Code is cheap, let’s talk

5. 告别暴力熔断:Agent 死循环的分级治理阶梯(Warn → Block → Halt)

跑复杂任务时 Agent 最容易出现"原地打转":某个正则没搜到内容,模型换着花样连续调 3~4 次完全相同的 grep_search;某个文件不存在,依然反复用同一个路径调 view_file。以前处理死循环的手段很粗暴——要么不管,要么直接强杀整轮。作者在运行时里加了分级工具循环守卫(Graded Loop Guard)。

值得关注:

  • 区分工具类型是前提:只读工具(grep、view_file)重复查同一文件很多时候无害,容忍度可以高一点;变更工具(write_to_file 或执行写命令)重复调用且入参完全一致往往极度危险。所以策略是先提示纠偏,再物理拦截,最后才熔断。
  • 三级阶梯:检测到相同工具、相同入参第 2 次出现 → Warn(放行执行,但在 ToolResult 末尾追加系统提示"请调整搜索词、修改路径或改用其他工具");第 3 次 → Block(阻止物理执行,用 ToolResultContent::Error 合成错误返回,既维持 tool_use 必须有对应 tool_result 闭合的协议格式,又省掉无效 IO);第 4 次(HALT_AFTER = 4)→ Halt(终止当前 Turn,透传 RepeatedToolCalls,保留完整上下文并告知打转原因)。
  • canonical_args_hash 的细节:计算前先对 JSON 的 Key 递归排序再哈希,保证 {"a":1,"b":2} 和 {"b":2,"a":1} 指纹完全一致,避免键顺序不同造成误判。
  • 实现位置在主循环执行工具前的 before_tool_call 阶段,枚举 LoopGuardAction { Allow, Warn, Block, Halt }。
  • 效果:实际跑任务时大部分边缘打转在 Warn 阶段就被模型自行纠正;把粗暴强杀换成由浅入深的阶梯,既保护 API 预算,也让长任务完成率明显改善。

来源:Code is cheap, let’s talk

6. 把上下文压缩移出主路径:Agent 长会话的两阶段预热与可回捞设计

长任务跑起来上下文总会越滚越大,几十轮下来 token 很快逼近上限。旧办法是单次压缩(Single-pass Compaction):一超标就停下主流程,把前面历史一股脑发给模型做全局摘要再替换掉旧消息——问题是主流程被迫长时间等待。作者把运行时的上下文管理重写了一遍。

值得关注:

  • 两阶段预热:配置 prefire_margin_tokens(默认预留 10% 缓冲),当上下文用到阈值 90% 时主循环不暂停,派发一个后台异步任务把 95% 的历史前缀做预摘要,产出 NOTE1 笔记并计算指纹存缓存;当 token 真正突破 100% 时正式压缩——若指纹匹配,只合并 NOTE1 与尾部增量(Tail),发给模型的 token 减少 80% 以上,主流程等待降到百毫秒级;指纹失效或失败则回退为单次全量摘要。
  • 切分约束:不能把成对的 tool_use / tool_result 从中间切断。
  • 可回捞设计:压缩后注入的 Summary 消息会带上当前会话 JSONL 的绝对路径,并提示"历史已被概括,完整原始记录保存在 session.jsonl 中,如需核对代码行号、报错堆栈或输出细节,可用读工具直接检索该文件"——压缩不把原始细节彻底扔掉。
  • 能省一次 LLM 就省一次:压缩逻辑前先跑 trim_old_tool_results,把老旧工具输出(如 git diff、cargo check)里超长文本截断成预览;如果剪完释放的 token 已足够,直接标记 context_changed = true 放行重试,根本不需要调模型做摘要。另外把压缩检查点挪到 MessageComplete 之后、TurnEnd 之前落盘,解决"最后一轮恰好越界却要等下次提问才压缩"的边界问题。
  • 实测(连续 47 小时、1000 次请求、累计 4.9 亿 input tokens):单轮平均 Input 从 496,806 tokens 降到 299,625(↓40%);上下文形态从 175 万全量重放(膨胀)变为 ~25 万压缩起步 / 280K 平衡;Prompt Cache 命中率压缩后首个请求有约 2 万缓存重建 miss(96.91%),后续维持 99%+。

来源:Code is cheap, let’s talk

趋势观察

  1. 训练侧与运行时侧都在从"一刀切"走向"分级/分步" — 训练侧 PACT 不再把最终奖励均匀撒回去,而是先证明信用分配的唯一刻画再围绕它做估计;运行时侧把"直接强杀死循环"换成 Warn→Block→Halt 阶梯、把"单次全局压缩"换成两阶段预热。同一思路在不同层次上重复出现。
  2. Harness 工程的共同母题:把重活移出关键路径 — UI 终态解耦(持久化后台跑,输入框乐观解锁)、上下文压缩预热(Pass 1 后台预摘要)、MCP 启动隔离(并发探测 + 独立超时 + 聚合),三篇都在做同一件事:关键路径只做最必要的事,其余异步化、降级、靠对账兜底。
  3. 依赖外部环境的能力必须可降级 — MCP 用 required 标记把"核心挂了就退出、可选挂了只熔断"写进配置,死循环守卫也是"先警告再拦截最后熔断"。共同点是承认外部依赖不可靠,并让故障被限制在最小范围而非级联。
  4. 工程经验开始以"开源框架 + 可复用重构套路"外溢 — AIHOT 把采集/评分/聚簇/生产 Prompt 全开源;更值得注意的是它的重构方法:先把旧库蒸馏成解耦的功能文档与交接包,再让模型重写,从而避免模型被历史屎山带偏。这是可以迁移到任何"老项目 + 强模型"场景的套路。