☰
Harness架构+Agent:单人九个月二十万行代码的工程实践
2026/9/29 11:30:04 网站建设 项目流程

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_compile,JavaScript 用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 执行循环:一次完整的任务从派发到归档

一个完整的执行循环大概是这样跑的:

  1. 调度层读任务队列,找到下一个“待执行”且依赖已满足的任务。
  2. 调度层把任务状态改为“执行中”,写入执行日志。
  3. 执行层读任务文件,注入相关代码,调用模型生成修改。
  4. 执行层把修改写回代码文件,同时生成代码快照存到02-代码快照/。
  5. 验证层跑语法检查和单元测试,结果写入04-验证结果/。
  6. 如果验证通过,任务状态改为“已完成”,调度层继续下一个任务。
  7. 如果验证不通过,任务状态改为“失败”,失败原因写入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 在更新状态文件时,把整个任务队列的格式改乱了,导致后续任务全部读不出来。原因是状态文件也是 Markdown,Agent 分不清“状态文件”和“代码文件”的区别,看到 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,但价值很高。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询