从零开始写 Agent,我在 Prompt、Context 和 Harness 三层工程上踩过的坑
2026-06-10 从零开始写 Agent,我在 Prompt、Context 和 Harness 三层工程上踩过的坑
这篇博客聊聊我这一年多接触 AI 辅助编程和 Agent 编写的真实经历。从去年中旬偶然发现 Cursor 官网,到十月份之后接触 TraeCN,再到后来尝试 Cursor、OpenCode、Codex、OpenClaw,最后自己动手写 Agent。整个过程走了不少弯路,也推翻了很多最初想当然的认识。
在写这篇文章时,我也翻了一些资料——从 Anthropic 的官方工程博客,到 TMLR(Transactions on Machine Learning Research)正在评审的《Agent Harness Engineering: A Survey》综述论文,再到 harn.app 上整理的实践框架和掘金社区的讨论。这些资料让我把之前零散的经验串联成了一条更清晰的线,也补充了一些我自己实践中还没有注意到的维度。
起点,一次偶然的发现
去年中旬,我偶然点进了 Cursor 的官方网站。当时正在准备竞赛培训,时间很紧,只是大概看了下产品介绍,知道这是个 AI 编程助手,能做代码补全和生成。心里想着挺有意思,但手头事情太多,没空深入了解,就这么搁下了。
竞赛培训结束后,时间空出来一些。去年十月份之后,我开始接触 TraeCN。那时候我的用法很初级,主要是让它帮忙写一些简单的函数,或者解释看不懂的代码片段。TraeCN 确实能省不少时间,尤其是一些 boilerplate 代码,再也不用自己手敲了。
但用了一段时间后,我开始不满足于这种浅层辅助。我想让 AI 做更复杂的事,比如理解整个项目的结构,或者帮我做跨文件的改动。这时候我开始尝试 Cursor、OpenCode、Codex、OpenClaw 这些工具。(顺便提一嘴,Codex 是真好用)
工具尝试阶段,各家有各家的脾气
Cursor 给我的第一印象是快。代码补全几乎没有延迟,而且它能理解我当前打开的文件上下文,生成的代码和项目风格比较一致。但 Cursor 的问题也很明显,它更像是一个超级加强版的 IDE 插件,擅长单文件或者邻近文件的改动。一旦涉及复杂的跨模块重构,它就有点力不从心了。
OpenCode 和 Codex 的集成度很高,尤其是在特定生态里。Codex 配合 GitHub 用很方便,能直接读取仓库信息。但它们的自由度相对较低,很多时候只能按照预设的套路来,遇到非常规需求就卡住了。
OpenClaw 是我用过的工具里自主性最强的。它能执行 shell 命令、读写文件、甚至自己规划任务步骤。刚开始用的时候很惊艳,感觉像雇了一个真正的程序员。但很快我就发现了问题,它太自由了,经常在一些边界条件上出错,而且错误会连锁传播,越到后面越难收拾。
这一轮工具尝试让我明白一件事。现有的 AI 编程工具,要么太死板,要么太放任。它们解决的是特定场景的问题,但没有一个能真正按照我的想法去工作。于是我萌生了自己写 Agent 的念头。
自己写 Agent,推翻了一个想当然的认识
刚开始写 Agent 的时候,我走了最直观的一条路。把 Prompt 写得尽可能详细,给 Agent 规定死每一步该干什么,能调什么工具,不能干什么,输出格式必须怎样。甚至在代码里写了一大套硬编码的门禁逻辑,Agent 每做一步都要过一遍检查清单。
最开始的效果似乎还不错。Agent 按照我规定的步骤走,输出也很规范。但我很快发现了问题。
第一个问题是僵化。Prompt 写得越细,Agent 的应变能力越差。遇到我预设之外的情况,它完全不知道怎么处理,要么报错退出,要么机械地套用不合适的步骤。
第二个问题更隐蔽。当我换一个更强的模型时,效果反而下降了。因为 Prompt 里那些为了约束弱模型而写的详细规则,对强模型来说变成了多余的束缚。强模型本可以做出更合理的判断,却被我的硬编码规则限制住了。
这其实就是后来我在 Anthropic 的工程博客里看到的"高度"问题(Right Altitude):系统提示词存在一个 Goldilocks Zone(黄金区)——**太规定(Too Prescriptive)**会写出像流程图一样僵硬的 if-else 逻辑,难以适应真实情况的变化;**太模糊(Too Vague)**又会缺少具体的信号,让 Agent 误以为存在共享上下文而偏离任务。**恰到好处(Just Right)**的提示词应该具体到足以有效引导行为,但又足够灵活以提供强大的启发式判断,用最小的信息集完整描述预期行为。
这个发现让我挺意外的。我原以为提示词越清晰越多越好,结果在实践中恰恰相反。过于详细的指令不仅没帮助,还可能成为 Agent 能力的上限。
Anthropic 的一篇文章,让我换了思路
就在我纠结怎么改进 Agent 的时候,我读到了 Anthropic 的一篇关于 Agent 工程的文章。里面有一个观点直接击中了我。
好的 Agent 工程,重点不在于指导 Agent 该干什么,也不在于用一套硬编码的门禁去限制它。重点在于给 Agent 提供一个可运行的环境,让它在这个环境里有足够的信息和工具去自主决策。
这句话把我之前的做法完全推翻了。我原来在做的是 micromanagement,每一步都要管。而正确的做法应该是提供土壤和工具,让 Agent 自己长。
文章里提到的思路和我之前的经验也对得上。OpenClaw 之所以容易失控,不是因为它的模型不够强,而是因为它的运行环境缺乏有效的约束和反馈机制。Cursor 之所以安全但死板,也是因为它把环境限制得太死了。
从这时候开始,我的关注点从 Prompt 本身转移到了 Prompt 背后的整个系统。
从 Prompt Engineering 到 Context Engineering
理解了 Anthropic 的思路后,我做的第一个改变是精简 Prompt。把那些为了约束弱模型而写的详细规则大量删除,只保留最核心的目标描述和边界条件。Prompt 从原来的几百字压缩到了几十字。
但光精简 Prompt 不够,因为 Agent 做决策需要信息。如果信息给少了,Agent 就会瞎猜。如果信息给多了,Agent 就会在噪声里迷路。
这就是 Context Engineering 要解决的问题。用 Andrej Karpathy 的话说:"上下文工程是一门微妙的艺术与科学——在正确的时间,用正确的信息填充上下文窗口。" 它和 Prompt Engineering 的区别在于:Prompt Engineering 问的是"什么是正确的词?",而 Context Engineering 问的是"模型在执行的每个节点上应该拥有的最优信息集是什么?"
我开始认真对待这件事。每次调用模型之前,我会根据当前任务筛选出最相关的信息。Agent 在处理 bug 修复时,只给它看相关模块的代码和最近的提交记录,其他无关信息一律不放入上下文。同时,我把信息按结构化方式组织,用清晰的标题分隔不同来源的内容。
在翻资料的时候,我发现关于上下文工程有几种成熟的技术,值得单独记录一下:
1. Just-In-Time 上下文检索(即时检索)
这是最推荐给 Agent 使用的策略。Agent 不预先加载所有可能相关的信息,而是维护轻量级的标识符(文件路径、存储的查询等),在需要时通过工具动态加载数据。这样做的好处是让 Agent 的工作记忆中只保留当前真正需要的内容,避免了"上下文腐烂"(Context Rot)——即随着上下文窗口中 token 数量增加,模型准确回忆和推理信息的能力会下降的现象。由于 Transformer 架构中每个 token 都要关注其他所有 token,token 数 n 意味着 n² 对关系,上下文越长,模型的注意力焦点被拉伸得越稀薄。
2. 上下文压缩(Compaction)
当对话接近上下文窗口限制时,让模型总结当前对话内容,然后用摘要重新开启一个新的上下文窗口。这个过程的关键是先最大化召回率(确保捕获所有相关信息),再迭代消除冗余内容(清理重复的工具调用和中间输出),保留关键的架构决策、bug 定位和实现细节。
3. 结构化笔记(Agentic Memory)
Agent 定期将中间笔记写入持久化存储(位于上下文窗口之外),在后续需要时再将其拉回上下文窗口。这相当于给 Agent 配备了一个"外置硬盘",用来记录 ID、过滤器、早期决策和已验证的事实。
4. 子代理架构(Sub-agent Architectures)
使用专业化的子代理处理特定焦点任务,每个子代理使用干净的上下文窗口进行深入探索,然后只返回一个精炼、浓缩的摘要给主代理。这在处理长任务时特别有效,因为它防止了工具调用的噪声在主上下文中积累。LangChain 的 LangGraph 框架就是围绕这个思想设计的——它通过一个状态对象来隔离不同来源的上下文,LLM 只在需要时才能访问特定上下文字段。
这些技术背后有一个共同的核心原则:找到最小的高信号 token 集合,以最大限度提高期望结果的可能性。 效果提升很明显,Agent 的决策准确率提高了,响应速度也更快了,因为上下文变精简了。这个经历让我体会到,给模型喂多少信息不重要,喂什么信息、怎么组织信息才重要。
Harness Engineering,把认知再升一级
今年我读到一份关于 Harness Engineering 的调研报告,里面有个公式让我印象很深。Agent = Model + Harness。 Model 是模型本身,Harness 是模型之外的一切——系统提示词、工具接口、沙箱环境、编排逻辑、反馈循环、观测体系等等。
报告里提到了一组数据让我非常震撼。同一模型,仅改变 Harness 设计,编码基准测试分数可以从 6.7% 跃升到 68.3%。另一个例子是,通过系统提示词重构、中间件上下文注入和自我验证钩子,有人把一个固定的 GPT-5.2-Codex Agent 在 Terminal-Bench 2.0 上从 52.8% 提升到了 66.5%——13.7 个百分点的增益完全通过基础设施改变实现。还有一个 Meta-Harness 的工作通过自动化 Harness 优化达到了 76.4%,超过了所有手工设计的方案,但没有修改任何模型权重。这些 Harness 层面带来的改进,每一项都超过了同一基准上模型代际进步通常报告的 2 到 4 个百分点。这个差距太惊人了,它直接说明了一件事:模型能力的提升往往不是 Agent 表现不佳的瓶颈,Harness 的设计质量才是决定性因素。
TMLR 的综述论文把这个现象称为"绑定约束命题"(Binding Constraint Thesis):对于跨可比前沿模型评估的长时任务,基准测试的方差可能由执行 Harness 和模型本身共同驱动。
这让我回顾自己写过的 Agent,发现很多稳定性问题确实出在 Harness 层,而不是模型层。比如 Agent 在两个文件之间来回修改,改完 A 发现 B 有问题,改完 B 发现 A 又不对,陷入死循环。这不是模型不够聪明,而是 Harness 缺少断路器机制。后来我给 Agent 加了最大迭代次数和超时检测,这个问题就彻底消失了。
再比如 Agent 把一段合法的解构赋值改成了错误的形式。这也不是模型的问题,而是 Harness 缺少验证步骤。后来我在写入文件前强制运行测试,失败就回滚,准确率立刻上去了。
Harness Engineering 把 Prompt Engineering 和 Context Engineering 当成基础层,在此基础上搭建执行环境、工具管理、状态记忆、观测评估和故障恢复。它的核心思想是:Agent 的可靠性取决于整个系统的设计,而不是单次模型调用的质量。
关于 Harness 的系统分层,不同资料有不同的归纳方式,放在一起看反而更容易理解全貌:
harn.app 提出的 CAR 框架(三层):
- Control(控制层):约束与规范。不要让 Agent 写好代码,而是机械地强制执行好代码的样子——通过 AGENTS.md、linters、类型检查、测试即契约、架构规则来实现。语言约束是建议,代码约束是法律。 Prompt 里写"请不要修改数据库 schema"并不可靠,因为当上下文过长时上下文腐烂几乎不可避免;但把规则写入 CI 检查、pre-commit hooks 或策略守卫,模型就无法绕过。
- Agency(工具层):工具与接口。定义模型被允许如何行动——限定工具范围,而不只是限定知识。MCP 服务器、子代理委托、浏览器/GUI 访问、权限边界都属于这一层。
- Runtime(运行层):执行与恢复。管控工作随时间的展开方式——生命周期钩子、重试与回滚、上下文压缩、追踪收集作为"反射弧"自动触发安全检查或格式化操作,不需要 AI 干预。
juejin 上一篇文章归纳的七层架构,更偏实践视角:
- 认知层(Context Layer)
- 工具层(Tool Layer)
- 契约层(Contract Layer)
- 编排层(Orchestration Layer)
- 记忆与状态(Memory & State)
- 评估层(Evaluation Layer)
- 约束与恢复(Constraint & Recovery)
TMLR 综述论文提出的 ETCLOVG 七层分类法,更学术系统化:
- Execution environment(执行环境):sandbox、workspace、container、隔离执行
- Tool interface(工具接口):文件系统、git、浏览器、数据库、API
- Context management(上下文管理):检索、压缩、动态注入、token 预算
- Lifecycle/Orchestration(生命周期/编排):多步骤任务串联、状态流转
- Observability(可观测性):日志、追踪、中间结果审计
- Verification(验证):测试、静态分析、自我检查、行为验证
- Governance(治理):权限层级、安全边界、人工介入点
不管用哪种分类法,核心信息都是一致的:早期系统把能力集中在单个模型循环里,而后期系统越来越把可靠性视为跨层的基础设施问题。
我现在把 Harness 拆成几块来设计。信息边界层,定义 Agent 该知道什么、不该知道什么。工具系统层,控制 Agent 如何与外部交互。执行编排层,把多步任务串起来。记忆与状态层,管理中间结果。评估与观测层,让 Agent 能自我检查。约束与恢复层,出错时怎么拦截和回滚。
这样拆分之后,调试 Agent 变得清晰多了。出问题的时候,我能快速定位是哪一层的问题,而不是对着模型的输出发呆。
工具设计要克制
我犯过的另一个错误是给 Agent 塞了太多工具。有一次写项目管理 Agent,集成了十几种工具。查代码、改代码、查文档、发邮件、创建任务、搜索知识库、读取数据库。结果 Agent 表现很糟糕,经常在不该调用工具的时候调用,或者连续调用多个无关工具,把简单问题搞得复杂无比。
后来我砍掉了大部分工具,只保留最核心的三个。读取文件、写入文件、执行命令。效果反而更好了。Agent 的决策路径变清晰了,出错率也大幅下降。
这个教训让我对工具设计有了新的认识。工具数量和控制精度之间存在权衡。工具越多,Agent 的选择空间越大,但也越容易迷失。对于特定场景的 Agent,工具集应该保持精简,每个工具都要有明确的职责边界。
另外,工具的命名也很关键。我早期有个工具叫 file_processor,Agent 经常搞不清楚什么时候该用它。改成 read_file 和 write_file 之后,调用准确率明显提升。命名原则很简单,动词加名词,一看就知道干什么。
关于这一点我在资料里看到一条很实用的规则:如果一个人类工程师都不能明确地说出在某个情境下该用哪个工具,就不能指望一个 AI Agent 能做到。 工具应该自包含(single purpose)、对错误健壮(handle edge cases gracefully)、极度清晰(intended use is unambiguous)、token 高效(returns relevant info without bloat),并且参数命名具有描述性(例如 user_id 而非 user)。
一个实际的重构案例
今年三月份,我接手了一个遗留项目的维护工作。代码库里充斥着各种过时写法,手动重构估计要花两周。我决定写个 Agent 来辅助完成。
这个 Agent 的逻辑很简单。遍历项目目录找出目标文件,逐个读取内容,让模型按照预设规则重构,比如把 var 改成 const 和 let,把回调函数改成 async 和 await,然后把重构后的代码写回文件,最后运行测试验证结果。
整个过程分成两个阶段。第一阶段是探索,Agent 先扫描项目结构,生成待修改文件清单,让我确认。第二阶段是执行,Agent 按清单逐个处理,每处理完一个就运行一次测试。
实际运行中遇到了不少问题。有一次 Agent 把一段合法的解构赋值改成了错误的形式,测试立刻失败了。好在我在设计阶段就加入了测试检查点,Agent 发现测试失败后会回滚代码,然后跳过这个文件继续处理下一个。最终在三天的间歇性运行后,Agent 帮我重构了大概百分之七十的代码,剩下的百分之三十因为逻辑过于复杂,还是需要人工处理。
这个案例让我体会到 Agent 的现实定位。它擅长处理大量重复、模式化的工作,但在涉及复杂业务逻辑的时候,人的判断依然不可替代。把 Agent 当成一个不知疲倦的初级程序员来用,期望设定合理了,反而更容易获得满意的结果。
调试 Agent 的一些经验
写 Agent 最痛苦的部分不是写代码,而是调试。Agent 的执行流程涉及多个步骤,每个步骤都可能出错,而且错误会连锁传播。
我总结了几条经验。
日志要详尽。Agent 的每一步决策、每一次工具调用、每一个中间结果都应该记录下来。出了问题之后,翻日志是最快的方式。TMLR 综述把可观测性(Observability)列为独立的架构关注点,强调中间步骤必须可读——计划、追踪、差异、失败记录都应该存在 Agent 和人类可以审计的文件中。
设置断路器。给 Agent 设置最大迭代次数和超时时间,防止陷入死循环。加了迭代上限之后,至少能保证程序不会跑飞。
保留人工介入点。在关键节点暂停执行,让人确认后再继续。比如文件写入操作前弹出确认框,显示变更内容摘要。这在处理重要代码库的时候尤其必要。harn.app 称之为 Governed(治理)——自主性必须有边界,不可逆操作受到权限层级、沙箱、验证门和人工审查的约束。
反馈要闭环。Agent 完成任务后,系统需要自动判断它是否做对了,并把结果反馈给 Agent,驱动它自我修复。这在 juejin 的文章里被称为反馈层——功能验证(代码能跑吗?逻辑对吗?)和行为验证(Agent 的行为本身符合预期吗?)都是这个循环的一部分。行为验证甚至可以写成测试,比如"变更行数不应超过 50 行"、"不应引入新依赖",这些断言能防止 Agent 过度工程化。
写在最后
从最初偶然发现 Cursor 官网,到接触 TraeCN,再到尝试各种工具,最后自己动手写 Agent,这一路的认知升级挺明显的。最开始我以为重点是写好 Prompt,后来发现 Context 更重要,再后来意识到整个 Harness 的设计才是根本。
这段时间我翻资料时发现,整个社区的认知演进也是按同样的顺序:2022-2023 年是 Prompt Engineering,大家关心的是措辞;2024 年是 Context Engineering,大家开始意识到信息选择和组织的重要性;从 2025 年开始,随着 Agent 开始真正修改代码库、调试系统、连续运行数小时,Harness Engineering 成为了新的焦点——构建一个让 AI Agent 可以安全、可预期、可审计地运行的系统。这个三代演进的脉络和我自己踩坑的顺序高度吻合,也让我对自己当前的方向更有信心。
最大的感触是 Agent 没有魔法。它的本质依然是大语言模型加循环加工具调用。那些看起来很炫酷的 Agent 应用,背后都是大量的工程细节和边界情况处理。
但写到这里我想多说一句。Agent 并不是 LLM 应用的最终形态,它只是一个过渡态。
当前的 Agent 本质上是在用工程手段弥补模型能力的不足。Harness 越复杂、钩子越多、约束越严密,恰恰说明模型本身还不够可靠——我们需要一个庞大的外部脚手架来防止它犯错。而理想的方向,应该是模型自身就能更稳定地理解任务、管理状态、处理边界,从而让 Harness 简化,而不是无限膨胀。
再往深看一层,Prompt Engineering 是在教模型"怎么说",Context Engineering 是在教模型"看什么",Harness Engineering 是在教系统"怎么管"。这三层工程加起来,其实是在回答同一个问题:当模型还不够聪明的时候,我们怎么让它把事情做对? 但如果未来模型本身具备了更强的长期规划、工具理解、状态记忆和错误恢复能力——这些能力从外部 Harness 内建到模型内部——那么今天我们精心设计的 Agent 框架就可能变得多余,就像当初详细的 Prompt 规则在更强的模型面前失去意义一样。
另一个角度是应用范式本身。当前的 Agent 仍然是"有人指挥的自动化"——人定义任务、人确认结果、人兜底异常。真正的下一代 LLM 应用可能不是"Agent"这个词暗示的"代理执行",而是模型作为系统的一个原生组件嵌入到应用逻辑里,和传统代码、数据库、用户界面无缝协作,不需要显式的工具调用循环和人工检查点。那时我们写的不再是 Agent,而是具备原生理解和推理能力的新型应用。
所以我对自己的定位很清楚:今天用心写好 Agent,不是因为我相信 Agent 是终点,而是因为在通往终点的路上,这是最有价值的练兵场。理解 Agent 的痛点,就是在理解下一代 LLM 应用需要解决什么问题。
如果你也想在日常工作中引入 Agent,建议从一个具体的小问题开始。不要想着一步到位搭建全能助手,先解决一个实际的痛点。等这个小 Agent 跑顺了,再逐步扩展能力范围。同时保持一份清醒——你在做的可能不是未来,而是通往未来必经的一段路。
技术本身并不复杂,复杂的是把技术落地到真实场景中的过程。希望这篇博客能给正在尝试写 Agent 的你一些参考。
评论讨论