跳到主要内容

cowagent — 本课题摘录

读了哪几篇: 02-system-prompt(每一轮都从零重建)、03-context-robustness(上下文管理与健壮性)。 其余四篇(agent 循环、记忆与知识、技能与工具、自进化)本轮没读。

这一家贡献的是"循环跑久了会出什么事"的一份清单,而且每一条都配了具体阈值。它自己说这是整个项目工程含量最高的一章。

它对本课题回答了什么

决定四:四类真实会翻车的场景 —— 本课题最实用的一张表

翻车场景白话后果
上下文超长箱子塞不下了请求失败
配对被打断「我要调工具」后面没跟上「工具的结果」各家 API 直接报 400
死循环模型反复调同一个工具、同样的参数烧钱、卡住、永远不回复
取消 / 溢出用户点取消,或箱子当场爆了若不收尾,历史留下半截调用,下一轮直接崩

(依据:Agent 库 · CowAgent · 上下文管理与健壮性:让长对话不崩 —— 四类翻车分别是上下文超长、tool_use/tool_result 配对被打断(各家 API 报 400、MiniMax 专门返回 2013)、模型反复同参调同工具的死循环、取消或溢出时不收尾会在历史里留下半截 tool_use)

它对"配对"这条硬约束的说法很形象:模型只有一个固定大小的箱子,而且这个箱子对摆放顺序有洁癖——"我要调工具 A"下一条必须是"工具 A 的结果",id 还得对上;少一个、错一个、顺序反了,直接退货。

这张表就是我们的循环要过的四关。 前面 rig、nanobot 都强调过"每个调用必须有结果", 这一家把它的后果写死了:各家 API 都会报 400,而且有的厂商还有专门的错误码。

防线一:裁剪只裁一次,而且绝不在工具中途裁

它把理由说得很清楚: 如果在循环里边跑工具边裁,很可能把当前正在进行的调用连同它的结果一起或分开砍掉,导致配对断裂——模型下一步看到一个孤零零的"我要调工具"却没有结果,于是懵了、又调一遍,死循环。

做法:追加完用户消息后立刻裁一次,之后整个循环内部再也不裁;裁完立刻修一次配对。 (依据:Agent 库 · CowAgent · 上下文管理与健壮性:让长对话不崩 —— 裁剪只在 run_stream 追加用户消息后做一次,循环内部不再裁,理由是中途裁剪会把进行中的 tool_use 与它的 tool_result 砍断导致配对断裂;裁完立刻跑一次配对修复)

而且是按"完整轮次"扔,不按条数。

防线二:发送前修复配对 —— 四步,而且要循环到稳定

配对为什么会断: 裁剪切在了边界上、持久化写坏、上一轮工具执行时进程被打断。

修复四步:

做什么
① 邻接修复调用后面没跟上结果,就合成一个占位结果插进去(标成错误)
② 删开头孤儿历史开头若是一条只有结果、没有对应调用的消息,删掉
迭代删不匹配对求差集把落单的两边都删;因为删一条可能又制造新孤儿,所以循环最多 5 次直到稳定
④ 删完再修一次邻接删除会破坏邻接

(依据:Agent 库 · CowAgent · 上下文管理与健壮性:让长对话不崩 —— sanitize 四步——邻接修复合成占位 tool_result、删开头孤儿、迭代删不匹配对(循环最多 5 次直到稳定,因为删一条可能制造新孤儿)、删完再修一次邻接)

第 ③ 步的"删一条可能又制造新孤儿"是很容易漏掉的。 nanobot 也做了两遍(裁剪后再保证一次配对), 这一家更彻底:循环到稳定,而且有次数上限防止死循环。

而且它在两个位置各修一次:裁剪之后修一次,发送之前再修一次。

防线三:分级熔断 —— 四档,阈值都写出来了

触发条件判定口径动作是否致命
同工具 + 同参数被调 5无论成功失败停这步,提示"结果已在之前返回"
同工具 + 同参数连败 3只数失败,遇成功清零停这步
同工具连败 6 次(任意参数)只数失败停这步,警告
同工具连败 8 次(任意参数)只数失败致命中止整段对话,给用户建议换方式

(依据:Agent 库 · CowAgent · 上下文管理与健壮性:让长对话不崩 —— 分级熔断四档——同工具同参数 5 次(无论成败)、同工具同参数连败 3 次、同工具连败 6 次、同工具连败 8 次致命中止;失败账本只留最近 50 条)

它点出了两种不同的循环:

  • 反复失败: 某工具一直报错,模型不换思路;
  • 成功也循环: 工具明明成功返回了,模型却像没看见,用同样参数一调再调,永不收尾。

第二种是 cline 的"循环检测"和 openmanus 的"数重复"都覆盖不到的: cline 数的是连续相同调用,openmanus 数的是助手消息重复,而"成功了还反复调"两者都能抓,但都没点破这是两种不同的病。 这一家把"无论成功失败"和"只数失败"分成了两个口径,这是本课题最细的一份。

防线四:取消要有多个探测点,而且探测频率要摊薄

检查点代价
每轮开头一次检查
每次工具调用之间一次检查
流式生成中每 8 个数据块探测一次用计数器摊薄,不必逐 token 查

(依据:Agent 库 · CowAgent · 上下文管理与健壮性:让长对话不崩 —— 取消有三个探测点——每轮开头、每次工具调用之间、流式中每 8 个 chunk 探测一次(用计数器摊薄);流式中途取消时只保存已生成的纯文本,不保存可能被截断的半截 tool_use 参数)

流式中途取消时只保存已生成的纯文本,不保存可能被截断的半截调用参数——否则又会破坏配对。

这条把防线四和防线二连起来了:取消如果不小心,自己就会制造出防线二要修的问题。

决定一:系统提示每一轮都从磁盘重建

它用一个具体场景说明为什么:

如果你在对话中说"以后叫你小牛吧",模型用编辑工具把设定文件里的名字改了——下一句话它还记得自己叫小牛吗? 如果系统提示是启动时生成、之后一直缓存的常量 → 不记得,得重启进程才生效。

所以它每次请求前从磁盘重读几份设定文件、刷新技能/工具/时间,按"重要性顺序"拼出来。 (依据:Agent 库 · CowAgent · 系统提示词:每一轮都从零重建 —— 系统提示每轮从磁盘重读 AGENT/USER/RULE 并刷新技能、工具、时间后按重要性顺序拼装,理由是模型改了自己的设定文件后下一句就要生效,否则得重启进程)

它的比喻:别的 agent 常把"人格"当成开机时定死的启动参数;它把人格当成每轮重新加载的配置文件——像开了热重载的服务器。

这条跟 hermes-agent 的"拼一次就冻住整场会话以换缓存"、whale 的"不可变前缀"是直接冲突的。 冲突的实质:改动即时生效 vs 前缀缓存命中。 这是本课题一个必须自己拍板的取舍,而且两边的理由都很硬。

它没回答什么

  • 不用原生工具调用怎么办——它假设模型支持。
  • 压缩的具体算法——只说"按完整轮次扔",没讲摘要。
  • 循环状态怎么存下来续跑——不在这两篇。

坑与代价

  • "每轮从磁盘重读"意味着每轮都有文件 IO,而且前缀每轮都可能变。 它换来的是即时生效,赔上的是缓存命中。

    判断(无锚): 折中做法是:重读,但把"读出来的内容"算个指纹——内容没变就复用上一轮拼好的字符串,这样既即时生效又不破坏缓存。 如果错,会错在: 如果设定文件里有"当前时间"这类每轮必变的东西,那指纹每轮都变,折中无效——那就得把易变部分挪到末尾(nanobot 就是这么做的)。

  • 四档熔断的阈值是经验值。 5 / 3 / 6 / 8 这几个数没有推导,换场景要重调。
  • 失败账本只留最近 50 条。 超长会话里更早的失败模式看不见了。