简介:本文面向希望构建可落地AI编程Agent的开发者,系统拆解“模型之外的工程调度层”:从提示编排、工具调用、迭代容错、上下文压缩,到失败回传、记忆与多Agent协同,并给出模块划分、关键流程与伪代码示例,帮助读者把大模型从“会聊天”升级为“能改代码、能跑测试、能自我修正”的工程助手。
一个再强的大模型,在真实工程任务面前也只有两种动作:接收 token、输出 token。它不知道刚才那次文件写入是否真的落盘,不知道测试是绿是红,不知道上一轮的错误日志被丢到了哪里,更不知道自己已经连续第三次在同一个编译错误上打转。
这并非模型能力不足,而是反馈回路缺失。人类工程师改完代码后会保存、编译、运行测试、读报错、再改——这一整圈“动作—观测—修正”的闭环,模型自身无法完成,因为它没有手、没有眼、没有记忆载体。
要让模型变成能实际干活的编程 Agent,就必须在模型之外搭一套工程脚手架:负责把代码库喂进去、把工具调起来、把结果收回来、把失败再送回去。这套脚手架,业界普遍称为 Harness(工程调度层),其核心关系可以概括为:
Model + Harness = Agent
这个公式的关键含义是:Agent 的能力上限由模型决定,但 Agent 的可用性由 Harness 决定。同一个模型配上不同质量的 Harness,产出质量可以差出一个数量级。
Harness 的定义可以一句话概括:除模型推理本身以外的所有工程工作,都算 Harness。具体可拆为七个核心模块。
模型看到的不是“项目”,而是一串 token。Harness 的第一职责,是把工程现实翻译成模型能消化的输入:
这一步做得好不好,直接决定模型会不会“改错文件”或“重复造轮子”。实践中应遵循按需检索、渐进披露原则:先给骨架,再按任务动态加载细节,避免一次性塞满上下文窗口。
工具是 Agent 的手。典型工具集包括:
| 工具类别 | 典型能力 | 工程要点 |
|---|---|---|
| 文件读写 | 读取、创建、精确替换 | 需支持按行号/片段定位,避免整文件重写 |
| 命令执行 | 构建、测试、静态检查 | 必须有超时、沙箱与输出截断 |
| 检索 | 符号查找、文本搜索 | 结果需排序与去重,控制返回体量 |
| 网络/文档 | 查询依赖文档 | 需缓存与白名单,防止无限抓取 |
工具设计的第一原则是幂等与可观测:每次调用都要有结构化返回(成功/失败、耗时、输出摘要),否则 Harness 无法判断下一步。
Agent 必须有“刹车”。Harness 需要设定:
没有这些约束,Agent 极易陷入“改—错—再改—再错”的死循环,消耗大量 token 却毫无进展。
长任务必然遇到上下文溢出。Harness 需要分层压缩策略:
原始对话 → 轮次摘要 → 任务级摘要 → 关键决策清单
压缩时优先保留:已确认的事实(如“测试命令是 npm test”)、已排除的方案、当前未解决的错误。丢弃:冗长的工具原始输出、重复的失败日志。压缩本身可由模型完成,但触发时机与保留字段必须由 Harness 控制,不能交给模型自由裁量。
这是 Harness 最容易被低估、却最影响成功率的部分。测试失败时,Harness 不应只回传一句“测试失败”,而应回传:
结构化回传让模型从“猜哪里错了”变成“看着错误改”,修复轮次通常能显著下降。
跨任务记忆解决两类问题:项目级知识(构建命令、目录约定、常见坑)与任务级知识(本次任务已尝试方案)。实现上通常采用“文件化记忆 + 检索注入”:把记忆写成结构化文档存于工作区,任务开始时按相关性检索注入 prompt,任务结束后由 Harness 决定是否写回。关键在于写入需审核,避免错误结论污染长期记忆。
复杂任务可拆分为并行子任务:一个 Agent 负责实现,一个负责写测试,一个负责代码审查。Harness 在此扮演调度器:
子 Agent 的价值在于上下文隔离与角色专精,代价是协调开销。任务粒度太细会导致汇总成本超过收益,需按实际情况权衡。
把上述模块串起来,Harness 的核心就是一个带约束的循环:
def run_agent(task, repo, max_rounds=8):ctx = build_initial_context(task, repo) # 提示编排memory = load_relevant_memory(task) # 记忆注入for round in range(max_rounds):prompt = compose(ctx, memory)action = model.generate(prompt) # 模型推理if action.type == "finish":return verify_and_report(action, repo)result = execute_tool(action, repo) # 工具调用(含沙箱/超时)if result.failed:result = enrich_failure(result) # 失败回传增强ctx = update_context(ctx, action, result)if ctx.too_long:ctx = compress(ctx) # 上下文压缩if is_stuck(ctx): # 熔断return escalate(task, ctx)return escalate(task, ctx)
注意几个设计取舍:
git diff 或完整测试输出就能吃掉半个上下文窗口。解决:统一截断策略 + 关键片段提取。一个自然的疑问是:为什么不把 Agent 逻辑直接训练进模型?
原因在于迭代速度不匹配。工程实践中的最佳策略(该给多少上下文、失败怎么回传、何时压缩)变化极快,几乎以周为单位演进;而模型训练周期以月计。把调度逻辑放在 Harness 层,意味着调整策略只需改代码、无需重训模型;同时,同一套 Harness 可以适配不同规格的模型,模型升级时工程层可平滑复用。
这也解释了一个常见现象:同样的模型,接入不同质量的工程层,实际编程表现差异巨大。模型是引擎,Harness 是底盘、传动与仪表盘——引擎再强,没有底盘也跑不起来。
构建 AI 编程 Agent 的本质,不是让模型“更聪明”,而是用工程手段把模型的不确定性约束在可观测、可回滚、可验证的流程里。提示编排决定它看到什么,工具调用决定它能做什么,迭代控制决定它不会失控,失败回传决定它能否自我修正,记忆与协同决定它能否处理真实规模的工程任务。
对于准备落地这类能力的团队,建议的推进路径是:先做单任务闭环(读—改—测—修),把失败回传做扎实;再补上下文压缩与熔断,保证长任务不崩;最后引入记忆与子 Agent,扩展任务规模。每一步都以“可验证的成功率”为衡量标准,而非以“演示效果”为标准——这才是 Harness 工程化的正确姿势。