ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Harness架构实战:一个人九个月20万行代码的Agent开发全解析

Harness架构实战:一个人九个月20万行代码的Agent开发全解析 1. 先搞清楚这个项目到底在做什么一个人九个月20万行代码每个月消耗40亿以上的token最终交付的是一款基于Harness架构的应用。这几个数字放在一起任何一个写过代码的人都会先愣一下——不是因为20万行代码有多夸张大厂里一个中型项目组半年也能堆出这个量级真正让人停下来想的是一个人和40亿token/月这两个条件同时成立。先说40亿token是什么概念。按主流大模型API的计费方式粗略换算40亿token如果全部走输入侧大约相当于每天处理1300万token左右也就是每天要跑完几万次中等长度的对话或代码生成请求。这个量级不是我偶尔用AI补全几行代码能解释的它意味着整个开发流程——从需求拆解、架构设计、模块编码、测试用例生成、文档撰写到重构——几乎全程都有Agent参与而且是高频、长上下文、多轮迭代的那种参与。再说Harness架构。这个词在AI Agent开发圈子里最近被反复提及但它并不是某个具体框架的名字而是一种设计思路把Agent的执行骨架和具体能力解耦。骨架负责调度、状态管理、错误恢复、上下文传递能力则以插件或Skill的形式挂载上去按需加载。你可以把它理解成一辆车的底盘和动力总成是固定的但车厢里装什么设备、拉什么货随时可以换。这种架构的核心价值在于当你要接入一个新的模型、新的工具、新的数据源时不需要动主干代码只需要写一个新的Skill挂上去。这个项目之所以值得拆解是因为它同时踩中了几个当下最热的方向Agent开发、Claude Code这类终端Agent工具的使用、Obsidian作为知识管理底座、Markdown作为贯穿始终的中间格式。而一个人九个月这个条件恰恰说明这套Harness架构在真实的高强度开发场景下是被验证过的——不是Demo不是玩具项目是能扛住每天上千万token吞吐的工程实践。这篇文章适合谁看如果你正在做Agent相关的项目或者你是一个独立开发者想搞清楚一个人怎么用AI把开发效率拉到极限又或者你只是好奇40亿token到底花在了什么地方、Harness架构到底解决了什么问题那接下来的内容应该能给你一些可以直接参考的东西。我不会只讲概念会尽量把每个环节的取舍逻辑、踩过的坑、以及可以复现的操作路径都摊开来说。2. Harness架构到底解决了什么问题2.1 从写一个Agent到养一套Agent系统的认知转变大多数人第一次接触Agent开发都是从写一个能调用工具的循环开始的给模型一个系统提示让它决定调用哪个函数拿到结果后继续推理直到任务完成。这个模式在单任务、短流程的场景下没问题但一旦你要把它用在持续九个月、每天几十万次调用的项目里问题会成倍放大。第一个问题是状态管理。单次对话的Agent不需要记住太多东西但一个长期运行的系统必须知道当前任务进行到哪一步了、之前尝试过哪些方案失败了、哪些中间结果可以复用。如果这些状态全部塞进模型的上下文窗口token消耗会爆炸如果完全不存每次都要重新推理效率极低。第二个问题是错误恢复。模型调用会失败工具执行会超时返回结果可能格式不对。在Demo里你可以手动重试但在自动化流程里必须有机制判断这个错误是可恢复的还是致命的然后决定是重试、降级还是跳过。第三个问题是能力扩展。项目进行到第三个月你突然需要接入一个新的代码分析工具或者换一个更便宜的模型来处理简单任务。如果架构是铁板一块每次改动都要动核心逻辑那维护成本会高到让你不想再碰这个项目。Harness架构的本质就是把上面这三个问题从业务逻辑里抽出来变成一层独立的执行骨架。骨架不关心你具体要做什么任务它只负责维护任务状态机、管理上下文窗口的分配和回收、处理工具调用的生命周期、在出错时执行预设的恢复策略。而具体的能力——比如读文件、写代码、查文档、调API——全部以Skill的形式注册进来骨架在运行时根据任务类型动态加载。这种设计的直接好处是当你需要新增一个能力时只需要写一个符合接口规范的Skill模块注册到配置里不需要改骨架代码。当你需要换模型时只需要改模型路由层的配置Skill层完全无感。这就是为什么一个人能在九个月里持续迭代20万行代码——因为大部分改动都是增量的、局部的不会引发全局重构。2.2 为什么是Markdown作为中间格式这个项目里Markdown不只是一个文档格式它是整个系统的通用语言。Agent之间的通信、任务描述的传递、中间结果的存储、最终产出的组织全部以Markdown为载体。这个选择乍看很朴素但仔细想是有道理的。首先Markdown是纯文本任何模型都能无损地读写。你不需要担心JSON的转义问题不需要处理XML的标签嵌套模型生成Markdown的准确率远高于生成结构化数据格式。其次Markdown天然支持层级结构标题、列表、代码块、表格这些元素刚好对应了任务拆解、步骤说明、代码片段、参数对照等常见需求。第三Markdown可以直接被Obsidian这类知识管理工具消费意味着Agent产出的内容可以无缝进入人的知识库形成Agent干活、人审阅归档的闭环。在实际操作中这个项目把Markdown用到了什么程度举一个具体的例子当一个复杂任务被拆解时骨架会生成一个Markdown格式的任务清单每个子任务是一个二级标题下面跟着输入描述、预期输出、依赖关系。Agent执行完一个子任务后会把结果以Markdown段落的形式追加到对应标题下。最终整个任务的执行记录就是一份结构清晰的Markdown文档人可以直接读也可以被下一个Agent作为上下文加载。提示用Markdown做中间格式时一定要约定好标题层级和代码块标记的规范。比如规定所有代码必须用带语言标识的围栏代码块所有文件路径必须用反引号包裹。这些约定看起来琐碎但在Agent自动解析时能省掉大量正则匹配的麻烦。2.3 40亿token的消耗结构拆解很多人看到40亿token的第一反应是这也太烧钱了但如果你拆开看会发现大部分消耗是可以通过架构设计优化的。根据这个项目的实践token消耗大致分布在以下几个环节消耗环节占比估算优化手段上下文加载与传递35%分层缓存、摘要压缩、按需加载代码生成与修改30%局部编辑、diff模式、模板复用任务规划与推理20%小模型预处理、规则引擎兜底错误重试与恢复10%精确错误分类、快速失败策略文档与注释生成5%批量处理、延迟生成上下文加载是大头因为Agent每次执行都需要知道当前项目的状态是什么。如果每次都把整个代码库塞进去token消耗会失控。这个项目的做法是维护一个分层的上下文索引顶层是项目结构和模块摘要中层是当前任务相关的文件列表底层是具体文件的完整内容。Agent默认只加载顶层和中层只有在需要修改某个文件时才加载底层内容。这个策略把上下文相关的token消耗压到了原来的三分之一左右。代码生成环节的优化关键在于不要重新生成整个文件。早期版本里Agent修改一个函数时会把整个文件重新输出一遍导致大量重复token。后来改成diff模式Agent只输出变更的部分骨架负责把diff应用到原文件上。这个改动直接让代码生成环节的token消耗下降了40%以上。3. 一个人怎么扛住20万行代码的工程复杂度3.1 模块划分让Agent能独立负责一个目录一个人写20万行代码如果所有模块都混在一起光是找文件就能把人逼疯。这个项目的做法是按功能域划分目录每个目录对应一组Skill每个Skill有独立的入口文件、配置文件和测试用例。骨架在加载Skill时只加载入口文件暴露的接口不关心内部实现。这种划分方式的好处是当你让Agent去修改某个功能时你可以把范围限定在一个目录内。Agent不需要理解整个项目只需要理解这个目录的约定和依赖关系。实测下来限定范围的Agent任务成功率比全局任务高出很多因为上下文更聚焦模型不容易跑偏。具体目录结构大概是这样的project/ ├── core/ # 骨架核心不常改动 │ ├── scheduler/ # 任务调度 │ ├── context/ # 上下文管理 │ └── recovery/ # 错误恢复 ├── skills/ # 能力模块频繁迭代 │ ├── codegen/ # 代码生成 │ ├── analysis/ # 代码分析 │ ├── docs/ # 文档处理 │ └── knowledge/ # 知识库交互 ├── configs/ # 模型路由、Skill注册 └── workspace/ # 运行时数据、Markdown产出每个Skill目录下必须有一个manifest.md用Markdown描述这个Skill的用途、输入输出格式、依赖的其他Skill。这个文件既是给人看的文档也是给骨架看的注册信息。Agent在决定调用哪个Skill时会先读这些manifest然后根据任务描述匹配。3.2 用Claude Code做主力开发工具的实操细节这个项目的开发过程中Claude Code是使用频率最高的终端Agent工具之一。它的优势在于直接跑在终端里能读写文件、执行命令、查看git状态和实际开发环境的贴合度很高。但要用好它有几个细节需要注意。第一是工作目录的隔离。Claude Code默认会读取当前目录下的文件作为上下文如果项目根目录太大它会加载过多无关内容。这个项目的做法是为每个Skill目录单独开一个终端会话把工作目录切到该Skill下这样Claude Code的上下文范围就被自然限定了。第二是提示词的结构化。不要给Claude Code发帮我改一下这个功能这种模糊指令而是按照固定模板来写先说明当前状态哪个文件、什么功能、已知问题再说明期望结果改成什么样、有什么约束最后说明验证方式怎么确认改对了。这个模板看起来啰嗦但能显著减少来回澄清的次数。第三是善用它的文件引用能力。Claude Code支持用文件名的方式引用具体文件这比让它自己去搜索要高效得多。在修改一个函数时直接引用该函数所在的文件和相关的测试文件模型能更快定位到需要改的地方。注意Claude Code在执行长时间任务时可能会因为上下文超限而中断。建议把大任务拆成小步骤每步完成后手动确认再继续。虽然多了一些交互但整体成功率更高。3.3 Obsidian作为知识底座的接入方式Obsidian在这个项目里扮演的是长期记忆的角色。Agent每天产生的任务记录、代码片段、决策日志最终都会以Markdown文件的形式归档到Obsidian的vault里。这样做的好处是当需要回溯三个月前为什么选了方案A而不是方案B时可以直接在Obsidian里搜索而不需要去翻git log或者聊天记录。接入方式很直接Obsidian的vault本质上就是一个Markdown文件夹骨架的workspace目录可以直接设置为vault的一个子目录。Agent写出的Markdown文件会自动出现在Obsidian里配合标签和双链语法可以形成知识网络。实际操作中有几个技巧值得分享。一是用统一的frontmatter格式每个归档文件头部加上日期、任务类型、相关Skill等元数据方便后续用Dataview插件做聚合查询。二是用双链把相关的任务记录连起来比如一个代码修改任务可以链接到它依赖的架构决策记录。三是定期用Agent对归档内容做摘要生成周报或月报这些摘要本身又成为新的知识节点。3.4 版本控制与回滚策略20万行代码、九个月迭代如果没有可靠的版本控制一次错误的批量修改就可能让项目倒退好几天。这个项目的做法是在git的基础上加了一层Agent操作日志。每次Agent执行修改前骨架会自动创建一个git stash或者临时分支记录当前状态。Agent完成修改后骨架会运行预设的验证脚本比如编译检查、单元测试只有验证通过才提交到主分支。如果验证失败自动回滚到修改前的状态并把失败原因写入日志。这套机制的关键在于验证脚本的设计。验证不能太重否则每次修改都要等很久也不能太轻否则错误会漏过去。这个项目采用的是分级验证语法检查秒级完成单元测试分钟级完成集成测试只在合并到主分支前运行。Agent日常修改只需要通过语法检查和相关单元测试即可。4. Agent开发中那些文档不会告诉你的坑4.1 上下文窗口不是越大越好刚开始做这个项目时我的直觉是上下文窗口越大Agent能看到的越多效果应该越好。但实测下来完全不是这么回事。当上下文超过一定长度后模型对中间部分的注意力会明显下降而且更容易被无关信息干扰。更麻烦的是长上下文意味着每次调用的成本成倍增加40亿token里如果有很大一部分是无效上下文那就是纯浪费。后来采用的策略是精确加载骨架维护一个文件依赖图当Agent需要修改某个模块时只加载该模块及其直接依赖的文件间接依赖只加载接口定义不加载实现。这个策略把平均上下文长度压到了原来的四成左右而任务成功率反而提升了因为模型看到的都是相关信息。4.2 错误处理不能只靠重试Agent执行失败时最简单的处理就是重试。但重试是有代价的每次重试都要重新加载上下文、重新推理token消耗和时间成本都不低。更糟糕的是有些错误是重试解决不了的比如输入格式根本不对、依赖的工具不可用、任务描述本身有歧义。这个项目把错误分成了三类每类有不同的处理策略瞬时错误网络超时、临时限流。策略是带退避的重试最多三次。可修复错误输出格式不对、缺少必要参数。策略是把错误信息反馈给模型让它修正后重新输出最多两次。致命错误依赖缺失、权限不足、任务描述矛盾。策略是立即停止把详细错误写入日志等待人工介入。分类的关键在于错误信息的解析。骨架会从工具返回的错误码、模型的输出内容、执行环境的日志中提取特征然后匹配预设的规则。规则覆盖不到的情况默认归为致命错误宁可停下来也不要盲目重试。4.3 Skill之间的依赖管理比想象中复杂当Skill数量超过二十个之后依赖关系会变得很难管理。A Skill依赖B Skill的输出B Skill又依赖C Skill的配置C Skill的配置来自D Skill的初始化结果。如果加载顺序不对就会出现工具还没准备好就被调用的问题。这个项目的解决方案是引入一个简单的依赖声明机制。每个Skill的manifest里必须声明它依赖哪些其他Skill骨架在启动时做拓扑排序按顺序初始化。如果检测到循环依赖直接报错拒绝启动而不是尝试运行时解决。另外Skill之间的数据传递统一走Markdown格式的任务上下文对象而不是直接传递Python对象或JSON。这样做的好处是任何Skill都可以独立测试——你只需要给它一个Markdown格式的输入就能验证它的行为不需要把整个系统跑起来。4.4 模型路由的性价比平衡40亿token如果全部用最贵的模型成本会高到不可持续。这个项目的做法是分级路由简单任务格式转换、摘要提取、模板填充走小模型复杂任务架构设计、代码生成、错误诊断走大模型。路由规则写在配置文件里根据任务类型和输入长度自动选择。实测下来大约60%的调用可以走小模型这部分成本只有大模型的十分之一左右。剩下40%的复杂任务虽然贵但因为是真正需要推理能力的环节花得值。整体算下来分级路由把月度成本压到了全用大模型的三成左右。路由配置的一个示例routes: - match: task_type format_convert or task_type summarize model: small-model max_tokens: 4096 - match: task_type code_generate or task_type debug model: large-model max_tokens: 16384 - default: model: medium-model max_tokens: 8192提示路由规则不要写得太复杂否则维护成本会超过节省的成本。建议先用简单的任务类型匹配跑一段时间后再根据实际数据调整。5. 从零复现这套工作流的关键步骤5.1 环境准备与基础工具链如果你也想尝试类似的工作流不需要一上来就搞全套Harness架构。可以先从最小可用的工具链开始跑通一个完整循环后再逐步扩展。基础环境需要这些东西一个终端环境macOS的Terminal、Windows的WSL、Linux的shell都行Node.js运行时很多Agent工具依赖它Python环境用于写Skill和验证脚本git版本控制以及一个Obsidian vault作为知识归档目录。Claude Code的安装按官方文档走即可装完后用claude命令启动首次运行会引导你完成配置。Obsidian直接下载安装创建一个vault把路径记下来后面骨架的workspace会指向这里。5.2 最小Harness骨架的搭建不要一开始就追求完整的架构。先写一个能跑通读取任务描述→调用模型→执行工具→写入结果这个循环的最小骨架大概两三百行代码就够了。核心组件只有三个一个任务队列可以用简单的列表实现一个模型调用封装统一处理重试和错误分类一个工具注册表用字典存储Skill名称到函数的映射。任务描述和结果都用Markdown文件存储放在workspace目录下。这个最小骨架跑通后你会对Agent执行的基本流程有直观感受。然后再逐步加入上下文管理、依赖解析、分级路由这些高级功能。每加一个功能都要有对应的测试用例确保不会破坏已有的流程。5.3 第一个Skill的编写与注册选一个最简单的任务作为第一个Skill比如读取指定Markdown文件并生成摘要。这个Skill的输入是一个文件路径输出是一段摘要文本。编写时注意几点入口函数只做参数校验和结果格式化具体逻辑放在独立的模块里所有输入输出都用Markdown或纯文本不要引入复杂的数据结构在manifest.md里写清楚用途、输入格式、输出格式、依赖项。注册就是把Skill目录路径加到配置文件的列表里骨架启动时会自动扫描并加载。加载失败时要有明确的错误提示告诉你哪个文件、哪一行出了问题。5.4 验证与迭代节奏每加一个新Skill或修改骨架逻辑后跑一遍回归测试。测试用例不用多覆盖主要路径即可。关键是养成习惯改完就测不要攒着一起测。迭代节奏建议按周为单位。每周定一个小目标比如这周把上下文加载优化到只加载相关文件完成后记录效果数据token消耗、任务成功率、平均耗时下周基于数据决定下一步优化什么。九个月听起来很长但拆成三十多个周目标后每个目标都是可完成的。6. 这套模式适合谁、不适合谁说实话这套工作流不是万能的。它适合的场景有几个特征任务可以被清晰拆解成步骤、每一步的输入输出可以用文本描述、有明确的验证标准、迭代周期长且需要持续维护。如果你做的是探索性研究、需要大量人际沟通、或者任务边界模糊那Agent能帮上的忙会有限。另外40亿token/月的消耗意味着这是一套有成本门槛的方案。虽然分级路由能压成本但绝对值仍然不低。如果你的项目规模还没到需要这种强度的程度从简单的AI辅助编码开始就好不必追求全套架构。我在实际使用中体会最深的一点是Harness架构的价值不在于它有多智能而在于它把不确定性关进了笼子里。模型会出错、工具会失败、任务会变化这些都是常态。好的架构不是消除这些不确定性而是让它们在可控范围内发生并且发生后能快速恢复。一个人能扛住20万行代码靠的不是自己写得多快而是让系统在大部分时候能自己运转人只在关键决策点介入。
返回列表