拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Harness架构+Agent:单人九个月二十万行代码的工程实践

Harness架构+Agent:单人九个月二十万行代码的工程实践 1. 一个人九个月二十万行代码这件事到底在说什么先把标题里的数字拆开看。一个人九个月二十万行代码每个月四十亿以上的 token 消耗最终产物是一款基于 Harness 架构的应用。这几个数字放在一起任何一个写过代码的人都会先愣一下——不是因为二十万行有多夸张而是因为“一个人”和“九个月”这两个限定词。正常来说二十万行代码对应的是一个十人左右的团队干一年半的量级而四十亿 token 的月消耗意味着这个项目几乎把模型当成了编译器在用每天都有海量的生成、校验、重写循环在跑。这件事的核心不是“卷”而是它验证了一条路径当 Agent 足够可靠、Harness 架构足够清晰时单人开发的产能边界可以被推到什么位置。Harness 在这里不是某个具体产品名而是一类架构思路——把模型能力、工具调用、状态管理、上下文组织这几层解耦用一个稳定的“挽具”把 Agent 的行为约束住让它能长时间、可恢复、可观测地执行任务。你可以把它理解成给 Agent 套上一副马具让它拉车的时候不会乱跑也不会因为一次颠簸就把整车货掀翻。我之所以对这个标题感兴趣是因为它踩中了当下几个热词的交汇点Harness、Agent、Claude Code、Obsidian、Markdown。这几个词单独看都不新鲜但组合在一起指向的是一套完整的个人开发工作流——用 Markdown 做知识底座用 Obsidian 做项目管理台账用 Claude Code 这类 Agent 工具做执行引擎用 Harness 架构做调度和容错最后把二十万行代码的产出压缩进九个月的单人周期里。这篇文章适合谁看如果你正在做 Agent 开发或者想用 Agent 辅助自己完成一个中型以上项目又或者你已经在用 Obsidian 管理笔记但还没想清楚怎么把它和代码项目打通那这篇内容会对你有直接参考价值。我会从架构思路、核心细节、实操流程、问题排查四个层面把这套东西拆开讲清楚包括那些文档里不会写的坑。2. Harness 架构到底解决了什么问题2.1 为什么裸用 Agent 做长任务一定会崩先说一个我自己的观察。很多人第一次用 Agent 做长任务比如“帮我把这个模块重构完”结果往往是前二十分钟很惊艳半小时后开始胡言乱语一小时后彻底跑偏。这不是模型不行而是裸用 Agent 缺少三样东西状态持久化、任务边界约束、失败恢复机制。Agent 在执行过程中会不断产生中间状态——读了哪些文件、改了哪些行、当前任务进行到哪一步、哪些假设已经被验证、哪些还没。如果这些状态只存在于对话上下文里一旦上下文被截断或者模型开始“遗忘”整个任务就会从某个点开始崩塌。更麻烦的是Agent 往往会“自信地犯错”它不会告诉你“我忘了刚才改了什么”而是继续基于错误的记忆往下写。Harness 架构的第一个价值就在这里它把 Agent 的执行状态从对话上下文里抽出来落到外部存储里。这个存储可以是一个 Markdown 文件、一个 JSON 状态机、一个 SQLite 表形式不重要重要的是状态必须可读、可写、可恢复。每次 Agent 开始新一轮执行前先从状态存储里读当前进度每完成一个子任务把结果写回去。这样即使中间某次调用失败下一次也能从断点继续而不是从头再来。2.2 Harness 的三层解耦调度层、执行层、状态层我理解的 Harness 架构核心是把系统分成三层每层职责单一层与层之间通过明确的接口通信。调度层负责决定“下一步做什么”。它接收一个高层目标把它拆成可执行的子任务序列然后按顺序或按依赖关系派发给执行层。调度层不关心具体怎么改代码只关心任务队列和依赖关系。这一层通常由模型驱动但需要配合规则引擎做约束比如“同一个文件在未验证前不能连续修改超过三次”。执行层负责“具体怎么做”。它接收一个明确的子任务调用工具读写文件、运行命令、搜索代码产出结果。这一层是模型能力最集中的地方也是 token 消耗的大头。执行层需要被严格约束——每次只做一件事做完就返回不要自作主张扩展任务范围。状态层负责“记住做到哪了”。它记录任务队列、每个子任务的状态、已修改文件的快照、验证结果、失败原因。状态层是 Harness 架构的基石没有它前两层就是空中楼阁。这三层解耦之后好处非常明显调度层可以换模型执行层可以换工具状态层可以换存储互不影响。更重要的是每一层都可以单独测试和调试。当任务失败时你能快速定位是调度拆错了、执行做错了、还是状态记错了而不是面对一个黑盒干瞪眼。2.3 为什么选 Markdown Obsidian 做状态底座状态层用什么存这个选择很关键。我试过 JSON、SQLite、甚至直接用一个 Python 字典序列化最后发现 Markdown 文件加 Obsidian 的组合最顺手原因有三个。第一Markdown 对人友好。Agent 写进去的状态我自己随时能打开看不需要写查询语句。当我觉得 Agent 行为异常时直接翻它的状态文件往往一眼就能看出问题——比如某个子任务被标记为“完成”但实际没做或者某个假设被写成了事实。第二Obsidian 的双链和标签体系天然适合做任务追踪。每个子任务可以是一个笔记用[[ ]]链接到它依赖的文件和它产出的结果。Obsidian 的图谱视图能直观看到任务之间的依赖关系哪个任务卡住了、哪个任务被跳过了一目了然。我还会用 Obsidian 的 Dataview 插件做任务看板把状态文件里的字段渲染成表格比翻原始文件高效得多。第三Markdown 的纯文本特性让版本控制变得简单。状态文件直接进 Git每次 Agent 更新状态就是一次 commit出问题随时回滚。这一点比数据库强太多——数据库的回滚需要额外的迁移脚本而 Markdown 文件回滚就是git checkout一条命令。提示状态文件不要写得太细否则 Agent 每次读写都要消耗大量 token。我的经验是每个子任务的状态控制在三到五行只记录“做了什么、结果如何、下一步是什么”细节放在对应的产出文件里。3. 二十万行代码是怎么被“管”出来的3.1 任务拆解的粒度控制为什么是“一个函数”而不是“一个模块”任务拆解的粒度直接决定 Harness 架构能不能跑起来。拆得太粗比如“重构用户模块”执行层一次要处理太多东西容易跑偏拆得太细比如“把变量名从 a 改成 b”调度层会陷入琐碎token 消耗爆炸。我实测下来最舒服的粒度是“一个函数或一个类的一个方法”。这个粒度下执行层一次调用能完成产出可验证失败可回滚。比如“给parse_config函数加上类型注解并补充 docstring”这就是一个合格的子任务。它足够具体执行层不需要做额外决策又足够完整做完之后有明确的产出可以验证。调度层拆任务的时候我会让它先输出一个任务列表每个任务包含任务描述、依赖任务、预期产出、验证方式。这个列表先写到状态文件里我人工过一遍确认拆解合理后再让执行层开始跑。这一步人工介入很关键因为模型拆任务时经常会把“修改”和“验证”混在一起或者漏掉依赖关系。3.2 上下文注入每次只给 Agent 看它需要的那部分二十万行代码的项目不可能每次调用都把整个代码库塞进上下文。Harness 架构里上下文注入是执行层的关键环节。我的做法是每个子任务只注入三类内容——任务描述、相关文件的当前内容、相关接口的定义。相关文件怎么确定靠调度层在拆任务时标注。比如“修改parse_config函数”调度层会标注这个函数所在的文件、它调用的其他函数所在的文件、以及调用它的地方。执行层拿到这些文件的内容加上任务描述就足够了。其他无关代码一律不注入避免干扰。这里有个细节注入的文件内容要带行号。Agent 修改代码时经常需要引用具体行带行号能减少它“数错行”的概率。另外如果文件太长只注入函数所在的那一段前后各留二十行上下文足够 Agent 理解这个函数在做什么。3.3 验证闭环怎么判断 Agent 改对了Agent 改完代码不能直接信。Harness 架构必须有一个验证环节而且这个环节要自动化。我的验证分三层第一层是语法验证。改完的文件先跑一遍语法检查Python 用python -m py_compileJavaScript 用node --check语法不过直接打回让执行层重做。这一层能拦掉大概三成的低级错误。第二层是单元测试。每个函数对应的单元测试必须跑通跑不通就打回。这一层能拦掉大部分逻辑错误。如果项目本身没有单元测试那就让 Agent 在改之前先补一个——这本身也是一个子任务。第三层是人工抽查。不是每个改动都看但关键模块的改动我会抽看。抽查的重点不是代码风格而是Agent 有没有偷偷改它不该改的东西。我遇到过好几次Agent 在修改一个函数时顺手把旁边一个不相关的函数也“优化”了结果引入 bug。所以验证环节要加一条规则改动范围必须和任务描述一致超出范围的改动一律回滚。注意验证不通过时不要直接把错误信息丢回给执行层让它重试。先让调度层分析失败原因判断是任务拆解有问题还是执行有问题。如果是拆解问题重新拆如果是执行问题把错误信息和相关代码一起注入再让执行层重做。这个区分很重要否则会在错误的方向上反复重试浪费大量 token。3.4 Token 消耗的分布与优化每个月四十亿 token听起来吓人但拆开看其实有规律。我统计过自己项目的消耗分布大致是这样的环节占比说明执行层代码生成45%真正写代码的部分无法压缩调度层任务拆解20%可以通过缓存任务模板来降低验证与重试18%通过提高首次生成质量来降低状态读写10%通过精简状态格式来降低上下文注入7%通过精准注入来降低优化空间最大的是调度层和验证重试。调度层方面我把常见的任务类型比如“加类型注解”“补 docstring”“重构函数”做成模板调度层遇到类似任务时直接套模板不需要每次重新推理这一项省了大概三成调度 token。验证重试方面关键是提高首次生成的质量——把任务描述写得更具体、把相关代码注入得更精准、把约束条件写得更明确首次通过率能从六成提到八成以上。4. 从零搭一套 Harness 工作流的实操记录4.1 环境准备Obsidian 仓库结构与插件选型先建一个 Obsidian 仓库专门用来管这个项目。仓库结构我建议这样分project-vault/ ├── 00-状态/ │ ├── 任务队列.md │ ├── 执行日志.md │ └── 失败记录.md ├── 01-任务/ │ ├── 任务-001.md │ ├── 任务-002.md │ └── ... ├── 02-代码快照/ │ └── 按模块分文件夹 ├── 03-接口定义/ │ └── 按模块分文件 └── 04-验证结果/ └── 按任务编号分文件插件方面核心装三个Dataview用来做任务看板Templater用来生成任务模板Git用来做状态版本控制。Dataview 的查询语句我放在任务队列文件里实时渲染当前所有任务的状态比手动翻文件快得多。Claude Code 的安装和配置这里不展开网上教程很多。重点说一个配置项把工作目录设成项目根目录但把状态文件的读写权限单独控制。我的做法是让 Claude Code 只能通过一个封装好的脚本读写状态文件而不是直接操作文件系统。这样能防止 Agent 在状态文件里乱写也能在脚本里加校验逻辑。4.2 任务模板设计让调度层有章可循任务模板是 Harness 架构里最容易被忽视但最影响效率的部分。一个好的任务模板应该包含这些字段--- 任务编号: 001 任务类型: 代码修改 依赖任务: [] 预期产出: parse_config 函数带类型注解和 docstring 验证方式: py_compile 单元测试 test_parse_config 状态: 待执行 --- ## 任务描述 给 parse_config 函数加上类型注解补充 docstring说明参数含义和返回值。 ## 相关文件 - src/config.py函数所在文件 - src/utils.py函数调用的工具函数 ## 约束条件 - 只修改 parse_config 函数不改动其他函数 - 类型注解使用 Python 3.10 语法 - docstring 使用 Google 风格这个模板的好处是调度层拆任务时直接填字段执行层读任务时直接按字段执行验证层按字段验证。字段固定之后整个流程的确定性大幅提高。4.3 执行循环一次完整的任务从派发到归档一个完整的执行循环大概是这样跑的调度层读任务队列找到下一个“待执行”且依赖已满足的任务。调度层把任务状态改为“执行中”写入执行日志。执行层读任务文件注入相关代码调用模型生成修改。执行层把修改写回代码文件同时生成代码快照存到02-代码快照/。验证层跑语法检查和单元测试结果写入04-验证结果/。如果验证通过任务状态改为“已完成”调度层继续下一个任务。如果验证不通过任务状态改为“失败”失败原因写入00-状态/失败记录.md调度层决定是重试还是重新拆解。这个循环跑起来之后我基本上只需要每天早上花半小时过一遍失败记录调整一下拆解策略剩下的时间就是看它自己跑。九个月里真正需要我深度介入的大概只有前两周的架构搭建和后面每周一次的复盘。4.4 代码快照与回滚出问题时的救命稻草代码快照这个环节我一开始觉得多余后来发现是救命稻草。Agent 改代码时有时候会改出一些“看起来对但实际错”的东西验证环节不一定能拦住。这时候如果没有快照回滚就只能靠 Git但 Git 的粒度是整个 commit而快照的粒度是单个任务。我的做法是每个任务执行前把相关文件复制一份到02-代码快照/任务编号/下。任务完成后如果发现问题直接从这个目录恢复不影响其他任务的改动。快照文件不进 Git避免仓库膨胀但保留最近三十天的快照足够覆盖大部分回滚需求。提示快照目录要加进.gitignore但快照的元数据哪个任务对应哪个快照要进 Git。这样即使换了机器也能知道每个快照对应什么任务。5. 踩过的坑与排查实录5.1 Agent 陷入循环同一个错误反复重试这是最常见的问题。Agent 改一个函数验证不通过重试还是不过再重试连续五六次都在同一个地方栽跟头。原因通常是任务描述有歧义或者注入的上下文缺少关键信息。排查思路先看失败记录如果连续三次失败原因相同说明不是执行层的问题而是任务本身有问题。这时候要停下来人工检查任务描述和注入的上下文找出歧义点。我的经验是大部分循环都是因为任务描述里用了模糊词汇比如“优化一下”“改进性能”“让它更健壮”。把这些词换成具体指标比如“把时间复杂度从 O(n²) 降到 O(n log n)”循环基本就消失了。5.2 状态文件被写坏Agent 把状态当代码改有一次 Agent 在更新状态文件时把整个任务队列的格式改乱了导致后续任务全部读不出来。原因是状态文件也是 MarkdownAgent 分不清“状态文件”和“代码文件”的区别看到 Markdown 就按自己的理解重写了。解决办法状态文件的读写必须走封装脚本不能让 Agent 直接操作。脚本里加格式校验Agent 提交的状态更新先过校验格式不对直接拒绝返回错误信息让它重写。另外状态文件的模板要固定Agent 只能填字段值不能改字段名和结构。5.3 上下文注入过多导致 token 爆炸有一次我让 Agent 改一个核心模块调度层把整个模块的文件都注入了结果单次调用消耗了上百万 token而且 Agent 因为信息过载改出来的东西质量很差。教训是上下文注入要精准不是越多越好。后来我加了一条规则单次注入的代码行数不超过五百行超过就拆任务。另外注入的内容要按相关性排序最相关的放前面Agent 读前面就能理解任务后面的内容它自己会判断要不要看。5.4 常见问题速查表问题现象可能原因排查动作解决方式Agent 反复重试同一错误任务描述模糊检查任务描述是否有具体指标把模糊词换成可量化指标状态文件读不出来格式被改坏检查状态文件结构走封装脚本读写加格式校验单次 token 消耗异常高上下文注入过多统计注入行数限制单次注入行数拆任务改动范围超出任务描述约束条件不明确对比改动文件和任务描述加“只修改指定函数”约束验证通过但实际有 bug验证覆盖不足检查验证用例补充边界用例增加人工抽查任务依赖顺序错乱依赖关系漏标检查任务依赖字段调度层拆解时强制标注依赖5.5 几个让我少走弯路的实操心得第一个心得先跑通一个最小闭环再扩展。我一开始就想搭一套完整的 Harness结果卡在状态层设计上两周没进展。后来退回去先用一个 Markdown 文件做状态手动派发任务跑通一个“改函数-验证-归档”的闭环再逐步加自动化。这个顺序很重要先有闭环再有优化。第二个心得失败记录比成功记录更有价值。我每天花时间最多的地方是看失败记录因为成功的方式大同小异失败的原因千奇百怪。把失败原因分类整理慢慢就能总结出哪些任务类型容易出问题提前在任务模板里加约束。第三个心得不要追求全自动。九个月里我从来没有让 Harness 完全无人值守跑过。每天至少人工过一遍任务队列和失败记录每周做一次复盘调整策略。全自动听起来很美但 Agent 的可靠性还没到那个程度人工介入是必要的安全阀。6. 这套东西还能怎么扩展跑通基础流程之后我陆续加了一些扩展效果不错。一个是把 Obsidian 的 Dataview 看板接进每日复盘自动生成当天任务完成率、失败率、token 消耗统计省去手动整理的时间。另一个是把常见任务模板做成 Templater 模板调度层拆任务时直接调用减少重复推理。还有一个方向是多 Agent 协作。目前是单 Agent 串行执行我在试验让两个 Agent 并行跑不冲突的任务比如一个改前端一个改后端通过状态层的锁机制避免冲突。这个还在早期等跑稳了再单独写一篇。最后分享一个小技巧状态文件里加一个“假设”字段。Agent 在执行任务时经常会做一些隐含假设比如“这个函数不会被其他地方调用”。把这些假设显式写出来验证环节可以针对性检查。我加了“假设”字段之后因为隐含假设错误导致的失败少了很多。这个字段不占多少 token但价值很高。
返回列表