从失败中搭建编程 Agent 运行框架
作者围绕个人项目,将编程 Agent 的运行环境拆为上下文、权限、反馈、编排和框架自身测试五层。工作流倾向于缩短会话,以降低上下文压缩造成的信息损失,但未记录实际窗口占用。
AI 深度解读
Claude Code 的会话越长,越容易把预算花在反复读取历史上。作者为此搭了一套围绕模型运行的控制机制:任务拆成短会话,文件保存交接记录,hook 拦截危险命令,再用测试证明这些规则确实生效。
这套做法的价值,不是证明某个提示词更强,而是展示如何把一次次真实踩坑变成工程约束。不过,作者没有记录上下文窗口大小,也没有做严格对照实验。哪些经验能直接借用,哪些结论还缺证据,需要分开看。
上下文为什么要按长期成本来管理
作者独立开发家庭食品储藏与营养追踪应用 Pantensa,大部分代码由 agent 敲出来。他把模型之外的运行环境称为 harness,包括上下文、权限、反馈、编排,以及对 harness 自身的测试。
模型负责生成内容和调用工具;harness 决定模型先看到什么、能执行什么、怎样知道自己错了,以及什么时候结束。
作者几乎看不到 Claude Code 的自动压缩提醒。他不喜欢压缩把对话替换成有损摘要,并表示嵌套指令文件无法在这个过程中保留下来。因此,他尽量让会话在上下文窗口用到一半之前结束。但「一半」只是观察,没有测量记录,不能当成优化成果。
具体做法从会话启动开始。SessionStart hook 对应的 session_state.py 会重新检查分支、HEAD、已跟踪与未跟踪文件,以及相对 main 的分歧。上一轮的交接说明可以提供线索,仓库现在是什么状态,必须重新查。
同一个文件还把每轮都会加载的记忆索引限制在 6144 bytes。原因很实际:留下的一行信息,后续每轮都可能继续付费读取。作者引用截至 2026 年 8 月的 API 文档,称缓存输入价格约为基础输入价格的十分之一,但缓存不等于免费。
长期会话应该保存决策和测量结果,详细材料交给短会话处理。
拆成短会话之后,任务靠什么接得住
作者把长任务组织成 wave,再拆成多个 link,每个 link 使用新会话。跨 link 的记忆来自账本和 git log,不依赖聊天记录。
2026 年 8 月这轮工作由三个 skill 承担计划审计与任务提示生成、单个 link 的控制、仅凭账本完成收尾;另有八种 reviewer 角色放在 .claude/agents/ 下。
控制器的职责很窄:传递任务、收集结论、更新账本。任务说明传文件路径,diff 交给 reviewer 读文件,报告落盘,测试在后台运行并写日志。控制器只接收必要的摘要和退出码,也不读取子 agent 的对话记录。
这来自一次具体教训:某轮提示长到 42k 字符,其中 99% 是粘贴的历史。
这轮工作的六份计划共 143 KB,控制器一次也没打开,脚本接收路径即可。23 个任务的执行者各自返回四行:状态、提交、测试数字、注意事项。作者笔记记载,截至 2026 年 8 月 17 日,完整报告共 353 KB;这些文件后来已删除,读者无法重新核验。
更极端的一次,单个测试文件运行时输出了 74 MB 的 stderr,进入上下文的只有 tail -5 截取的五行。
这并非没有代价。作者明确承认,多窗口加多 reviewer 会增加总 token 消耗;订阅用户可能更快耗尽额度。他希望降低的是长期控制器反复携带历史的成本,并提高恢复能力:每个任务结束就写账本,会话中断后,最多重做一个任务。
质量数据也要按这个范围理解。这轮 23 个任务原计划增加约八到九个测试用例,最终从 1910 增至 1932,增加 22 个。一次预计纯重构的 link 增加了两个测试,另一次预计增加一个,实际增加三个,原因都是独立审查发现被改写区域缺少验证。这能说明审查发现了遗漏,不能单凭测试数量证明缺陷率下降。
规则怎样才能真的拦住错误
这套机制从事故中逐步长出来。首批五个 hook 于 2026 年 8 月 14 日提交;工作 wave 两天后的午夜刚过启动。当天下午,又有五条可落实为命令规则的教训进入仓库,四分钟后补上检查规则接线的测试,wave 于 8 月 17 日结束。
权限层的 guard_bash.py 在每条 shell 命令之前运行,把结果分成拒绝和警告。
拒绝仅用于不可逆损失,共十四类。git checkout -- 曾经抹掉未提交的修复,因此进入名单。commit、merge、rebase 和切换分支没有被一律拦截,作者认为例行操作反复确认会制造摩擦。
警告处理的是另一类问题:命令正常返回,但结果容易被误读。例如文件统计漏掉未跟踪文件、视口并非要求的 390 px,或者检查根本没运行。它们不阻止执行,而是在出错命令旁边提醒。
warn_pipefail.py 针对的就是这种误读。作者曾用 pytest | tail -5 缩短输出,却把失败测试当成通过:在未启用 pipefail 等情况下,管道可能返回末端 tail 的成功退出码。现在提醒直接发生在命令处。
但只检查退出码也不够。负责审计测试证据的 reviewer 要求同时提供 Tests、Test Files、failed 三项数字。只看到「N passed」,可能漏掉仍然失败的测试文件。
规则自身也会坏。作者曾有两条 i18n lint 规则因配置错误静默失效,lint 仍然全绿。因此,test_claude_hooks_are_armed.py 会检查 hook 是否注册在正确事件和 matcher 上、规则 id 是否恰有一个裁决、警告通道是否无法回答权限确认,以及每条规则是否有行为测试。
规则的测试,必须在规则被移除时变红。
检查器使用 ast 解析 hook,避免文本搜索把说明文档里引用的命令误认成代码。另一个证据审计角色只读账本和报告,为每道检查给出 OURS、NOT OURS、UNPROVEN 三种判断,分别表示失败归因于本次修改、失败与本次修改无关、证据不足。「大概没问题」也归入 UNPROVEN。
作者为它整理了十二种检查静默失效的模式,包括删除文件后自动成立的 not.toContain(file)。断言绿了,却没有证明预期行为。
我的判断
我最认同的是把修复闭环做完整:发生损失,加入规则,再证明规则失效时有人能发现。相比继续往提示词里补一句「下次注意」,hook 和行为测试更适合约束可机械判断的问题。
作者也测试过指令本身。为阻止控制器读取计划,他曾加入约 180 个英文单词的解释表格;删除表格后重跑场景,测试仍然通过,于是把它移到证据文件,减少每轮加载的内容。直接列出「允许打开哪些文件」,在他的测试中也比单写「不要打开计划」更有效。
但这里有两层局限。
首先,这是单人项目的一轮实践,机制还在边运行边修改,没有上下文峰值、成本或耗时的对照数据。「环境比提示词更决定产出」仍是作者的经验判断。
其次,文件和账本也有维护成本。报告四行是否足够,取决于执行者能否识别重要风险。临时材料判定后删除有助于减负,却降低了事后核验能力,353 KB 报告就是例子。
对普通团队,更合适的起点可能是清晰的任务边界、可靠的日志和少量高价值 hook。是否需要三个 skill、八类 reviewer,应由实际复杂度决定。作者的案例支持逐步补约束,还不足以证明整套编排适用于所有项目。
能拿来做什么
- 给日常 AI 编程增加一个启动检查,重新读取分支、HEAD、工作区改动和未跟踪文件,再开始执行任务,减少基于过期交接信息工作的机会。
- 对前端或 Node 项目的长测试保留完整日志,回传摘要时同时提供真实退出状态与测试结果统计,尤其检查
tail等管道是否掩盖失败。 - 长任务拆分后,把完成状态、提交、测试结果和未解决问题写进账本,让下一会话能够据此恢复;先确认交接够用,再压缩返回内容。
- 为已经造成过损失的命令添加 hook,并补一个能证明拦截仍生效的行为测试,同时验证注册配置,避免出现「规则存在,实际从未运行」。