先说结论:模型没有“调用”过任何工具。它只是输出一段文本,说自己想读哪个文件、想跑哪条命令。真正去开文件、去起子进程、去接测试的,是包在模型外面的那层代码。这层代码一般叫 harness,我习惯叫它壳。

我照着自己的需求搭了一个,跑了一阵才敢让它碰真实仓库。回头看,循环本身写得很快,时间几乎全花在提示词和工具分配上。这篇把壳拆开讲讲,顺便说说我在哪几个地方翻过车。

先把谁在干活分清楚

模型看到的是系统提示、任务、历史消息,以及上一次工具返回的结果。它要做的只有一件事:决定下一步。

壳要做的是拼上下文、发请求、解析模型想要的动作、执行、把结果塞回去、判断该不该停。

分开之后,很多“模型很笨”的现象就变得好解释。比如它反复读同一个文件,不是记性差,是壳每次都把文件内容塞回上下文,却没告诉它“这个文件你刚读过”。模型没有记忆,壳才有。

同理,模型不会自己跑命令,它只是提出请求。所以超时设多少、失败重试几次、危险操作拦不拦、日志写到哪,全是壳的责任。把这些推给模型,等于让一个只会写字的人负责机房安全。

一个循环就这几步

一次工具调用要经过的六步
  1. 1拼上下文系统提示、任务、历史消息、上一轮的工具结果
  2. 2请求模型带上工具定义,让它选下一步
  3. 3解析动作取出工具名和参数,解析失败就退回让它改
  4. 4执行或拦截只读的直接跑,写操作先过权限
  5. 5结果回填截断、摘要或者落盘,别整段塞回去
  6. 6判断收尾达到目标就停,轮次或预算用完也停
顺序换了,行为就变了

六步里最容易写错的是第三步和第六步。

解析动作这一步,模型给的参数经常是“差不多对”的:路径少一层、JSON 多一个逗号、工具名记成近义词。我的处理是解析失败不抛异常,把错误原文回给它,让它自己改一次;连续两次改不对就停,别让它在那儿转圈。

收尾更关键。没有硬上限的循环,碰上一个一直失败的测试,能安安静静烧掉一整晚。我在壳里放了三道闸:最大轮次、最大工具调用次数、连续失败次数。任何一道触发,保存现场然后退出。

文件工具顺便就是记忆

上下文窗口不是记忆,更像一张一次性的桌面。会话一长,前面读过的内容会被挤出去;你让它“接着上次改”,它只能从头再读一遍。

我的做法很土:读过的文件、跑过的命令、当前计划,全部落到仓库里的一个隐藏目录。读文件的工具不只返回内容,还往索引里写一行,记下路径、行数、读取时间。改文件的工具在写完之后更新索引。

这样上下文里只放摘要和指针,需要细节时再按指针去取。换会话、壳崩了重启,也能从磁盘把状态捞回来。

副作用是仓库里多了一堆状态文件,提交前得记着加 ignore。我忘了两次,diff 里全是自己的运行记录。

子 Agent 是隔开上下文,不是分身

我用的是 CrewAI,它的 delegation 本质就是一个 tool call:主 Agent 把任务和上下文写成文本发出去,子 Agent 在自己的上下文里跑,最后只回一段结论。

这个设计最值钱的地方不是并行,是隔离。子 Agent 读三十个文件,主线程的上下文一点不涨。你要是冲着“同时干三件事”去用,最后大概率在合并冲突上花掉更多时间。

代价全在交接上。任务写得含糊,子 Agent 就自己脑补,而且脑补得挺自信,回来给你一份跑偏的改动。我现在的要求是交接文本必须写清三样:动哪些文件、怎么算做完、哪些目录不许碰。缺一样就重写。

结果太大就让它落盘,只回路径和一句摘要。别指望把几千行 diff 塞进消息里,塞进去也会被截断。

长任务要能接着跑

计划不写下来就等于没有。我的壳在每一步执行前,把当前计划、已完成项、待办项写进状态文件,跑完一步改一次状态。

好处很直接:崩在第 N 步,重启后从第 N 步接着来,不用把前面读过的文件再读一遍。

坏处是状态文件会和真实代码不一致。比如改到一半被打断,文件已经是新的,状态还停在旧的。所以恢复时我让它先做一次一致性检查:列一下工作区里改过的文件,对不上就重新规划,别硬接着跑。

沙箱和审批,别等出事再加

我不建议让壳直接在你日常用的机器上跑写操作。理想情况是一个用完就扔的虚拟机或者容器:代码挂进去,网络出口收窄,跑完销毁。

退一步也要做到:写操作先出 diff 再落盘,删除和推送这类动作单独走审批,密钥不进口令环境。

图省事直接给本机写权限,是这套东西最容易被跳过的一步,也是最不该跳的。跑通一次很容易,跑通一百次不出事,靠的是这层限制,不是模型自觉。

最难的是提示词和工具分配

循环、计划、记忆这些,在框架里大多是一个开关。真正决定行为的是每个 Agent 的 role、goal、backstory,而这三样只能靠一轮一轮改。

我的体感是:同一套循环,提示词改三行,行为能差出一个版本。

几个具体的:

  • role 写“资深工程师”没什么用,写“你只负责这个目录下的测试文件,不许改实现”才有用
  • goal 要能被验收。“提升代码质量”不算目标,“补齐这个模块的测试且不改公开接口”才算
  • backstory 不是人设表演,是给边界和习惯。写“先读调用方再动手”,比写“你是一个严谨的工程师”管用

工具分配排第二,但差距不大。给一个 Agent 十几个工具,它会在“该用哪个”上反复横跳;只留当前任务真正需要的几个,它反而干脆。多出来的工具不是能力,是干扰项。

什么时候别自己搭

如果你只是想让 AI 帮忙改代码,现成的工具已经够用,自己搭纯属给自己找活。

自己搭的理由就那么几个:要接内部系统、要对权限有完全控制、要离线、要留下完整审计。少一个,都建议先用现成的,把省下来的时间花在真正卡住你的地方。

我那套壳现在注释比代码多,配置比注释多,每次改完还得自己跑一遍回归。但至少它出问题的时候,我知道该翻哪个文件,而不是对着一句“模型可能理解错了”发呆。