今日概览
今天六篇分成两条线:一条是长程 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而绝不向外抛异常,探测全部结束后再聚合。 - 动态工具注册表 + 前台提示:只有成功的工具进注册表,降级项被过滤掉,会话正常拉起并在前台提示"降级运行"。改完后哪怕断网或某个插件配置写错,基础功能依然能秒级拉起。
- 结论:依赖外部环境的插件系统必须做好隔离与降级,不要让一个锦上添花的边缘功能影响核心可用性。
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 标记"的会话,主动校准清理,兜住窗口失焦和极端丢包造成的孤儿状态。 - 结论:前台交互尽可能乐观敏捷,耗时持久化放后台异步跑,中间靠定期对账兜底一致性。
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 预算,也让长任务完成率明显改善。
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%+。
趋势观察
- 训练侧与运行时侧都在从"一刀切"走向"分级/分步" — 训练侧 PACT 不再把最终奖励均匀撒回去,而是先证明信用分配的唯一刻画再围绕它做估计;运行时侧把"直接强杀死循环"换成 Warn→Block→Halt 阶梯、把"单次全局压缩"换成两阶段预热。同一思路在不同层次上重复出现。
- Harness 工程的共同母题:把重活移出关键路径 — UI 终态解耦(持久化后台跑,输入框乐观解锁)、上下文压缩预热(Pass 1 后台预摘要)、MCP 启动隔离(并发探测 + 独立超时 + 聚合),三篇都在做同一件事:关键路径只做最必要的事,其余异步化、降级、靠对账兜底。
- 依赖外部环境的能力必须可降级 — MCP 用
required标记把"核心挂了就退出、可选挂了只熔断"写进配置,死循环守卫也是"先警告再拦截最后熔断"。共同点是承认外部依赖不可靠,并让故障被限制在最小范围而非级联。 - 工程经验开始以"开源框架 + 可复用重构套路"外溢 — AIHOT 把采集/评分/聚簇/生产 Prompt 全开源;更值得注意的是它的重构方法:先把旧库蒸馏成解耦的功能文档与交接包,再让模型重写,从而避免模型被历史屎山带偏。这是可以迁移到任何"老项目 + 强模型"场景的套路。