我越来越确信:AI 编程真正难的不是模型,而是 Harness 工程

3 阅读16分钟

前阵子我让一个 coding agent 帮我修一个不大的 bug。

问题其实很具体:

  • 一个接口 500
  • 只在某个 feature flag 打开时出现
  • 复现路径明确
  • 影响范围也不大

按理说,这种活儿很适合交给 AI。结果它上来先读了 8 个文件,跑了 5 次测试,顺手改了两个无关组件,又把 lint 配置动了,最后还非常自信地告诉我:

“问题已经修复,建议你跑一下验证。”

我一看 git diff,差点笑出声。

真正的 bug 还在,倒是 eslint 配置被它改松了,console.log 多了两个,README 还被顺手润色了一段。

如果你用过一阵 AI 编程工具,对这种场面应该不陌生。

很多人会把这类问题理解成:

  • 模型还不够聪明
  • 提示词没写好
  • 这家模型最近变笨了

我现在越来越不这么看。

我更愿意把问题说得更直一点:

AI 编程真正难的,往往不是模型本身,而是你有没有给它搭一套像样的工作环境。

这个“工作环境”,就是我今天想聊的:Harness 工程

它听上去很像一个会把读者劝退的词,但你要是真开始做 AI 编程系统,就会发现它根本绕不过去。

因为你很快就会撞上这些问题:

  • 工具应该给到什么粒度?
  • 哪些规范应该长期生效,哪些应该按需加载?
  • 复杂任务要不要先规划?
  • 长时间会话怎么避免上下文烂掉?
  • 多个 agent 并行改代码时怎么不互相踩死?
  • MCP 是不是越多越好?
  • 怎么防止 agent 把“我觉得差不多了”当成“真的验证通过了”?

这些问题,靠“更强的 prompt”都解决不了。

它们是纯工程问题。

而所谓 harness 工程,说白了就是一句话:

给模型搭一个能长期稳定干活的工作环境。

不是让它看起来更像人。
而是让它真的更像一个靠谱的同事。


一、先别把 Agent 想得太玄,它的心脏通常只有一条循环

我现在看各种 AI Agent 产品,第一反应已经不是“哇,太智能了”,而是“来,我看看你这层壳子到底是怎么接的”。

因为你把外壳剥开以后,内核经常朴素得让人不好意思:

while True:
    response = model(messages, tools)
    if response asks for tools:
        execute tools
        append results
    else:
        return

就这。

模型看上下文,决定下一步。
你负责把它要做的动作真的做掉。
再把结果喂回去。

如果你愿意,可以把这套东西想象成一个特别能说会道、脑子也不错,但手脚全靠外包的实习生:

  • 它知道“下一步应该查日志”
  • 但它自己不会真的去查
  • 你得给它一个 read_file
  • 或者给它一个 bash

所以一个 AI 编程系统最核心的第一步,不是“怎么让模型变聪明”,而是:

怎么让模型有手、有脚、有眼睛。

这就是 tools。

但事情到这里才刚开始。

因为光有手脚,不代表它会有条理地干活。

我见过太多 agent 一旦开始工作,就进入一种非常熟悉的人类状态:

  • 明明要修 bug,先去整理目录
  • 明明要加鉴权,先改按钮颜色
  • 明明要跑测试,先写文档
  • 明明是修一个接口,最后把半个项目重构了

这个时候你就会意识到,AI 编程最可怕的问题,从来不是“不会写”,而是:

它太会动手了,但不一定知道自己在干什么。

所以真正有效的 harness,不会停在 loop + tools,而是会继续补三样东西:

  • planning
  • context management
  • verification

这三样,缺哪个都迟早翻车。


二、我最看重的一条原则:复杂任务先规划,再执行

这件事对人类程序员成立,对 AI 更成立。

一个很现实的例子:

假设我要加一套“市场结算后自动通知用户”的功能。

这听起来很直白,但实际上至少会牵出这些东西:

  • 数据表是不是要加通知记录
  • 用户偏好要不要建模
  • 邮件和站内信要不要一起发
  • 市场结算的触发点在后端哪个事件上
  • 前端要不要加未读消息入口
  • 大量用户同时通知时会不会打爆

如果我只是丢一句:

“帮我把通知功能做了。”

那很容易出现一种 AI 经典事故:

  • 它先从最容易写的 UI 入手
  • 再绕回后端
  • 中途发现缺表
  • 然后边写边改方案
  • 最后产出一堆“看起来很忙”的 diff

但你很难说它真的在解决问题。

我现在更倾向于强制让 AI 先做一件事:

先把任务重新说清楚,再拆计划,再列风险,再等确认。

这一步的意义非常大,因为它做了三件事:

  1. 把模糊需求变成共享认知
  2. 把风险提前暴露,而不是埋进代码里
  3. 给后续执行建立一条“别跑偏”的轨道

很多人觉得 planning 很浪费 token。

我刚开始也这么想。

后来我发现,真正浪费 token 的从来不是 planning,而是:

  • 没计划直接写
  • 写了半天方向错了
  • 再回来重做
  • 再解释一次自己为什么改了这些东西

那才是真正的 token 黑洞。

所以如果你让我只给一条最先落地的 harness 建议,我会说:

复杂任务先规划,这不是礼貌,这是系统稳定性的门槛。


三、别把所有东西都塞进一份总提示词,规则、技能、命令必须分层

这是我后来踩坑踩出来的一个血泪教训。

很多人刚开始搭 agent 配置时,最自然的做法是:

  • 把团队规范塞进去
  • 把技术栈注意事项塞进去
  • 把 TDD 原则塞进去
  • 把安全要求塞进去
  • 把代码风格塞进去
  • 把自己不喜欢的写法也塞进去

最后得到一个 3000 行的“神级系统提示词”。

然后你每次开会话,都在拿一辆满载的卡车去买瓶水。

问题不是“信息不够”,问题是:

什么都常驻,就等于什么都不重要。

后来我越来越喜欢把东西拆成三层:

1. Rules:长期生效的硬约束

比如:

  • 不准硬编码 secret
  • 提交前必须验证
  • 代码风格底线
  • 目录结构约束
  • 审批红线

这层像法律。

它回答的是:

“无论你现在在做什么,有哪些事情就是不能越线。”

2. Skills:按需加载的方法论

比如:

  • 怎么做安全审查
  • 怎么做 TDD
  • 怎么做上下文压缩
  • 怎么做 context budget 审计
  • 怎么做某个框架下的实现

这层像 SOP。

它回答的是:

“遇到这种任务时,应该按什么套路做。”

3. Commands:高频工作流入口

比如:

  • /plan
  • /tdd
  • /verify
  • /code-review
  • /learn

这层像快捷操作台。

它回答的是:

“哪些流程值得我以后别再每次重讲,直接一键触发。”

这套分层对开发者特别有用,因为它能帮你避免一种很经典的配置病:

什么都想放进去,结果系统越来越重,越来越难维护。

我现在的判断很明确:

  • 永远有效的,放规则
  • 某类任务才需要的,放技能
  • 高频动作,做成命令

只要这层分不清,后面上下文和工作流一定会乱。


四、别让 AI 靠记忆做人,能自动化的都尽量自动化

这应该是我最近一年对 AI 编程系统最大的观念变化。

我以前总觉得:

“没事,模型够聪明,它会记得的。”

后来我发现这句话和“我以后会记得健身”一样不靠谱。

什么东西最容易被忘?

  • git push 前再看一眼 diff
  • 改完文件记得删 console.log
  • 长时间跑 dev server 最好别堵在主会话里
  • compact 前把当前状态写下来
  • 编辑 TS 文件后顺手 typecheck 一下

这些事对不对?

对。

重要不重要?

重要。

但它们都太机械、太高频、太容易漏。

所以对这类动作,我现在越来越倾向一个简单粗暴的标准:

如果它又高频、又机械、又重要,那就别再指望模型或者人类“记得做”,直接做成 hook。

举几个非常典型的开发场景:

场景 1:dev server 把主会话堵死

你让 agent 跑 npm run dev,它就真的在前台傻跑。
然后你眼睁睁看着主会话被日志淹没。

更好的做法是:

  • 识别 dev server 命令
  • 自动放进 tmux 或独立窗口
  • 主会话继续工作
  • 日志可追踪

场景 2:agent 改完一堆代码,末尾留了 4 个 console.log

这类问题特别适合做 stop hook。

每次响应后扫一遍改动文件,发现就提醒。

场景 3:你写着写着上下文爆了

最糟糕的不是 compact,而是:

  • 在错误时机 compact
  • 刚好把还没落盘的状态压没了

所以 compact 前自动记一笔当前状态,这类 hook 非常值。

我对 hook 现在的理解不是“增强项”,而是:

它是把工作习惯从“靠记忆”变成“靠系统”的关键层。

而真正成熟的 hook 系统,还得满足两件事:

  1. 能区分“提醒”和“阻断”
  2. 能分级,不是只有开和关

研究探索阶段和生产交付阶段,治理强度当然不一样。
把这两种状态混成一套固定规则,只会让人越用越烦。


五、上下文不是聊天记录,它更像一根内存条

这个比喻虽然土,但特别准。

很多人嘴上知道“上下文有限”,但行动上还是把 Claude Code 当微信聊天。

今天塞:

  • 团队规范
  • 框架指南
  • 历史 bug
  • API 文档
  • 架构说明
  • PR diff

明天再塞:

  • 一堆 MCP
  • 一堆 rules
  • 一堆 skills
  • 一堆 agent 描述

最后开始抱怨:

“怎么越用越钝?”

这事儿我现在看得很明白:
不是模型突然失忆了,是你的上下文预算被你自己干烂了。

我现在特别认同一个观点:

上下文不是自然资源,是预算。

你必须知道预算被谁花掉了。

比如:

  • 常驻规则吃多少
  • 已加载 skills 吃多少
  • MCP tools schema 吃多少
  • agent 描述吃多少
  • 当前会话历史吃多少

一旦你不拆这个账本,就会陷入一种非常熟悉的错觉:

“最近对话有点长,compact 一下应该就好了。”

其实不一定。

可能真正的问题是:

  • 你同时开了 12 个 MCP
  • 一堆技能常驻了
  • 你把项目说明写成了一本小书
  • 你给每个 agent 都写了八百字人物小传

所以对上下文,我现在有三条硬习惯:

1. Always-on 内容必须短

凡是常驻加载的,都要克制。

2. 重知识一律按需加载

不是所有会话都要带着一整本操作手册。

3. Compact 要在人类能看懂的阶段边界发生

最适合 compact 的时候通常是:

  • 调研结束,准备执行前
  • 一个阶段做完,切到下一阶段前
  • 一条死胡同走完,准备换方向前

最糟糕的 compact,是正在改到一半时突然压缩。
那种感觉就像你写代码写到一半,脑子被人拔电源。


六、Subagent、后台任务、Worktree,这三种“分出去做”根本不是一回事

这是我觉得开发者最容易搞混的一组概念。

表面上看,它们都像是在“把事情分出去做”。

但分出去的,其实不是同一种东西。

1. Subagent:把“思考和探索”分出去

比如我要回答一个问题:

“这个仓库的测试框架到底怎么接起来的?”

这时候我不一定要让主 agent 带着几十轮读文件记录去查。

更好的做法是:

  • 派一个 subagent
  • 让它自己读文件、自己搜
  • 最后只带摘要回来

这解决的是:

上下文污染。

2. Background task:把“等待”分出去

比如:

  • npm install
  • pytest
  • docker build

这些命令本身不需要另一个脑子,它们只是慢。

这种时候你真正想分出去的不是“思考”,而是:

等待时间。

3. Worktree:把“工作空间”分出去

这又是第三种完全不同的分离。

假设一个 agent 在改登录流程,另一个 agent 在改通知系统。

如果两个人都在同一个目录里改,你迟早会看到:

  • 脏 diff 互相污染
  • 回滚边界失控
  • 一堆“这个文件到底是谁改的”事故

这时候要分出去的不是思考,也不是等待,而是:

执行空间。

所以对这三者,我现在会这么记:

  • subagent:分出去的是脑力
  • background task:分出去的是等待
  • worktree:分出去的是工位

这个区分一旦分清,很多 agent 系统设计问题会突然变得非常清楚。


七、多 agent 真正难的,从来不是“多”,而是“乱”

很多人一说到 AI agent 并行,就很兴奋:

  • 多开几个窗口
  • 多起几个 agent
  • 一起干活

听起来很美。

但只要你真的试过,很快就会进入一种熟悉的项目管理地狱:

  • 谁在做什么?
  • 谁已经做完了?
  • 谁改了哪几个文件?
  • 谁的结果可信?
  • 哪个任务被谁卡住了?
  • 哪个 worktree 现在还能删,哪个不能动?

也就是说,多 agent 系统一旦往前走,真正的瓶颈根本不是“会不会并行”,而是:

有没有控制平面。

我现在对这件事的理解越来越像管理一个小团队。

一个靠谱的多 agent harness,至少得有这些东西:

1. 任务系统

不是 TodoList,而是:

  • 有状态
  • 有依赖
  • 可认领
  • 可并行

2. 角色边界

谁负责:

  • 规划
  • 实施
  • 审查
  • 安全
  • 验证

3. handoff 机制

不是“我把上下文全丢给下一个人”,而是:

  • 当前结论是什么
  • 哪些文件动了
  • 风险是什么
  • 下一个 agent 只需要知道什么

4. 工作区隔离

并行改代码,不隔离目录,就是在等事故。

所以我现在越来越相信一件事:

AI 多 agent 的成熟度,不取决于你能同时开几个实例,而取决于你有没有把控制平面搭出来。


八、MCP 很重要,但越用越觉得:它不是越多越好

我对 MCP 的态度这两年变化挺大。

刚开始会觉得:

“太爽了,什么都能接。”

后来用久了发现,MCP 和公司里开 SaaS 账号有点像:

  • 开的时候都觉得必要
  • 过一阵发现一半根本没在用
  • 但成本和复杂度一直在那儿

MCP 当然有价值,尤其是:

  • 跨系统检索
  • 浏览器自动化
  • 一些平台交互
  • 外部能力接入

但它的问题也很真实:

  • tool schema 占上下文
  • server 多了会拖慢体验
  • 健康状态得管
  • 认证、限流、异常恢复得管

我现在更赞同一种比较现实的策略:

1. 不是所有能力都要常开

2. 高频、边界清楚的能力,很多时候可以退回 CLI + commands

3. 真正复杂、动态、需要探索的外部系统,再优先上 MCP

4. MCP 一旦多起来,就必须进入治理

比如:

  • 数量控制
  • 健康检查
  • backoff
  • reconnect
  • fail-open / fail-close 策略

说白了:

MCP 不是装饰品,而是基础设施。

基础设施一旦不治理,迟早会反噬你。


九、我现在最信的一条:Harness 本身必须进入测试

这条以前我真没那么重视。

总觉得:

  • hooks 反正能跑
  • rules 反正是 Markdown
  • commands 反正只是入口
  • manifests 反正配置一次就好了

后来越写越发现,这种想法非常危险。

因为真正长期使用后,最容易坏掉的往往不是业务代码,而是这些“外围基础设施”:

  • command 指向了一个早就不存在的 skill
  • agent frontmatter 写错,结果整个委托链失效
  • hook JSON 格式漂了
  • 一条路径写成了个人电脑绝对路径
  • 一个安装 manifest 跟 README 里的数量对不上

这些问题单看都不大,但它们会持续破坏整个工作系统的可信度。

所以我现在很认同一个很硬的结论:

如果你的 harness 是长期资产,那它就必须像代码一样被测试。

不是“顺手测一下”,而是:

  • 有 CI
  • 有校验链
  • 有单元测试
  • 有不变量

什么时候我会觉得一套 AI 工程系统开始像样了?

不是它能不能写出一段漂亮代码。
而是它能不能证明:

  • 自己的规则没漂
  • 自己的 hooks 没坏
  • 自己的命令没失效
  • 自己的配置没泄漏私人垃圾

这件事听上去一点也不性感。

但恰恰是这种“不性感”的地方,决定了一套 harness 能不能活久。


十、最后说一句我自己的感受

我现在越来越不相信“AI 编程的未来,拼的是谁家模型更像一个程序员”这套说法。

我反而越来越相信另一件事:

未来真正拉开差距的,是谁更会给模型搭工作环境。

同一个模型,放进不同的 harness 里,表现差异会大得离谱。

有的像实习生第一天上班:

  • 找不到文件
  • 改代码没计划
  • 没验证就说完成
  • 多开几个实例就互相打架

有的则越来越像一个成熟团队里的靠谱同事:

  • 有规则边界
  • 有任务系统
  • 有自动化提醒
  • 有长期记忆
  • 有并行机制
  • 有恢复能力
  • 有验证闭环

这两者拼的根本不是“智商”,而是外面那层工程。

所以如果今天有人问我:

“AI 编程真正值得研究的是什么?”

我现在大概率会回答:

不是怎么写一句更神的 prompt。 而是怎么把 loop、tools、rules、skills、hooks、memory、tasks、worktrees、verification 这些东西,组合成一套长期稳定工作的 harness。

这件事听上去没那么炫。

但真做进去以后,你会发现它比“提示词工程”要硬核得多,也值钱得多。