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 条。 超长会话里更早的失败模式看不见了。