1. 一个人九个月二十万行代码,这件事到底在说什么
先把标题里的数字拆开看。一个人,意味着没有团队分工,没有前后端联调,没有产品经理帮你砍需求,也没有测试帮你兜底。九个月,大约是270天,如果按每周工作六天算,有效开发时间大概在230天左右。20万行代码,平均下来每天要净增将近900行有效代码。这个数字放在传统手写业务代码的场景里几乎不可能完成,因为人一天能真正想清楚并写对的逻辑,通常也就两三百行。所以这20万行里,必然有相当比例是结构化配置、声明式描述、自动生成的胶水层,以及大量可复用的模板。而每个月烧掉40亿以上的token,说明这个项目在开发过程中重度依赖大模型能力,不是偶尔问几句,而是把模型当成了日常生产工具,嵌进了编码、重构、文档、测试的每一个环节。
这个标题真正想表达的,不是“我有多能熬”,而是一种新的工程范式:Harness架构应用。Harness这个词在工程语境里,指的是把底层能力封装成可编排、可替换、可观测的骨架,让具体的业务逻辑像插件一样挂上去。你可以把它理解成一套“插座系统”——墙里的电线怎么走你不用每次重接,你只需要把不同的电器插上去。在这个项目里,Harness负责的是Agent的调度、上下文的组织、工具链的接入、状态的持久化,而真正干活的部分,是Markdown文档、是Agent技能、是Obsidian里的知识网络。
为什么是Markdown?因为Markdown是人和模型都能读的中间格式。人写起来不累,模型解析起来不费劲,版本管理还特别友好。为什么是Obsidian?因为它把Markdown文件组织成了一个双向链接的知识图谱,天然适合做Agent的长期记忆和上下文检索。为什么是Claude Code这类工具?因为它们把模型能力直接拉到了终端和编辑器里,减少了复制粘贴的摩擦。这几个东西凑在一起,就形成了一个闭环:你在Obsidian里写Markdown,Agent读取这些Markdown作为上下文,通过Harness调度执行任务,再把结果写回Markdown。整个过程中,token在燃烧,但人的重复劳动在被压缩。
这篇文章适合谁看?如果你是一个独立开发者,正在琢磨怎么用Agent放大自己的产出,那这里面的思路你可以直接抄。如果你是一个小团队的技术负责人,想知道怎么把Markdown和知识库变成工程资产,那Harness的拆分方式值得参考。如果你只是好奇“一个人九个月20万行”到底怎么做到的,那我会把里面的取舍、踩坑、以及那些看起来不起眼但极其关键的细节都摊开讲。我不打算把这个项目包装成什么神话,它本质上是一套工程方法加上大量枯燥的重复劳动,只不过重复劳动的部分被模型接走了。
2. 整体架构设计:为什么是Harness而不是普通脚本堆叠
2.1 Harness架构的核心思路与选型逻辑
普通脚本堆叠的做法是:写一个Python文件,里面调API、读文件、写文件,跑完就完事。这种做法的死穴在于,当你的任务从3个变成30个,从单步变成多步,从无状态变成有状态,脚本之间的依赖会变成一团乱麻。你今天改了一个读取Markdown的函数,明天发现另一个脚本也在用类似的逻辑但参数不一样,后天又有一个脚本需要在前两步的基础上加一个校验。最后你会有十几个版本的“读Markdown”函数,每个都略有不同,谁也不敢删。
Harness架构要解决的就是这个问题。它的核心思路是分层:最底层是能力层,封装原子操作,比如读文件、写文件、调用模型、解析Markdown、执行搜索。中间层是编排层,定义任务怎么串起来,什么条件下走哪个分支,失败了怎么重试。最上层是接口层,也就是Agent实际调用的入口,它不关心底层怎么实现,只关心“我要完成什么”。这三层之间通过明确的契约通信,底层换实现不影响上层,上层加需求不需要改底层。
我选择Harness而不是直接写脚本,还有一个很实际的原因:可测试性。当你把读Markdown封装成一个独立能力后,你可以单独给它写测试,喂进去各种奇怪的Markdown格式,看它能不能正确解析。如果你把它和业务逻辑混在一起,测试就变成了端到端测试,跑一次要烧token,跑十次就心疼了。Harness的分层让大部分逻辑可以在不调模型的情况下验证,只有真正需要模型判断的环节才走API。这一点在每个月40亿token的消耗下,省下来的都是真金白银。
另一个考量是可替换性。模型会更新,工具会换代,今天用Claude Code,明天可能换别的。如果模型调用散落在几十个脚本里,换一次要改几十处。Harness把模型调用收敛到一个Provider层,换模型只需要改一个配置。同理,Obsidian的读取逻辑、Markdown的解析逻辑,都收敛在各自的能力层里。这种收敛带来的维护成本下降,在九个月的周期里体现得特别明显。
2.2 Markdown作为核心数据格式的深层原因
这个项目里,Markdown不只是文档格式,它是数据格式。Agent的配置、任务的定义、上下文的组织、结果的输出,全部用Markdown。为什么不用JSON或者YAML?因为JSON和YAML是给机器读的,人读起来费劲。当你需要频繁地人工检查和修改这些文件时,可读性就是生产力。Markdown的标题层级天然对应任务的嵌套关系,列表对应步骤,代码块对应可执行片段,表格对应结构化数据。模型对Markdown的理解能力也远强于对自定义格式的理解,因为训练数据里有海量的Markdown。
但Markdown作为数据格式有一个坑:它太灵活了。同样一个列表,可以用-、*、+,缩进可以用两个空格、四个空格、或者Tab。如果你不约定一套严格的规范,解析器就会在各种边界情况上翻车。我的做法是定一套内部规范:标题只用#到####,列表统一用-,缩进统一用两个空格,代码块必须标注语言,表格必须对齐。这套规范写在Harness的校验层里,任何不符合规范的Markdown在进入流程前就会被拦下来。这看起来是小事,但省掉了后面无数次的解析异常排查。
还有一个关键点是Markdown的换行。标准Markdown里,单个换行不会产生新段落,要两个换行才行。但模型在生成内容时,经常只给一个换行,导致解析出来的结构和预期不符。我的处理方式是在Harness的解析层里做归一化:先把所有换行统一成\n,然后根据上下文判断是段落内换行还是段落间换行。这个逻辑写起来不复杂,但如果没有,你会经常遇到“为什么这个列表被合并成了一段”的问题。
2.3 Obsidian在架构中的角色定位
Obsidian在这个项目里扮演的是知识底座的角色。它本身只是一个Markdown编辑器加双向链接,但当你把Agent的配置、技能、记忆、输出全部放在一个Obsidian仓库里时,它就变成了Agent的外部大脑。双向链接让不同的Markdown文件之间产生关联,Agent在检索上下文时,可以顺着链接找到相关的内容,而不是只靠关键词匹配。
具体来说,我在Obsidian里建了几个核心目录:/skills放Agent的技能定义,每个技能是一个Markdown文件,里面写清楚这个技能做什么、输入是什么、输出是什么、依赖哪些工具。/memory放长期记忆,比如项目决策记录、常见问题的解决方案、用户的偏好设置。/tasks放任务队列,每个任务是一个Markdown文件,包含任务描述、状态、执行日志。/output放Agent生成的结果,按日期和任务类型归档。
这种组织方式的好处是,所有东西都是纯文本。你可以用Git做版本管理,可以用任何文本编辑器打开,可以用命令行工具搜索。不依赖任何专有格式,也不怕某个工具停止维护。Obsidian的插件生态还能提供额外的便利,比如Dataview可以用类SQL的方式查询Markdown里的元数据,Templater可以快速生成标准格式的任务文件。但这些插件都是锦上添花,核心的Markdown文件本身不依赖它们。
3. 核心细节解析:Agent调度、上下文管理与Token消耗控制
3.1 Agent调度的实现要点与避坑指南
Agent调度听起来很玄,拆开看就是三件事:什么时候调、调什么、调完怎么处理。在Harness里,我用一个调度器来管这三件事。调度器本身不执行具体任务,它只负责读任务定义、判断依赖是否满足、选择合适的Agent、把上下文传进去、拿到结果后决定下一步。
任务定义用Markdown写,格式大概是这样:标题是任务名,下面用列表写依赖、输入、输出、执行者。调度器解析这个Markdown,构建一个有向无环图,然后从入度为0的节点开始执行。每个节点执行完后,更新图的状态,把新的入度为0的节点加入队列。这个逻辑用Python写大概两百行,但它是整个Harness的心脏。
这里有一个坑:循环依赖。如果任务A依赖B,B又依赖A,调度器会死循环。我的做法是在构建图的时候做一次拓扑排序,如果发现环就直接报错,并且把环上的任务列出来。这个检查必须在执行前做,不能等到运行时才发现。另一个坑是任务超时。有些任务调模型可能卡住,如果不设超时,整个调度就挂在那里。我给每个任务设了默认超时,超时后标记为失败,然后走失败分支或者跳过。
Agent的选择也有讲究。不是所有任务都需要最强的模型。简单的格式转换、文件读写,用便宜的小模型或者干脆用规则引擎就够了。只有需要理解、推理、生成的环节才调大模型。我在Harness里做了一个模型路由层,根据任务的类型和复杂度自动选择模型。这个路由规则也是用Markdown配置的,改起来不用动代码。实测下来,这个路由层能省掉大概六成的token消耗,因为很多任务其实不需要大模型。
3.2 上下文管理的策略与Markdown的组织方式
上下文管理是Agent应用里最容易被低估的部分。模型的能力再强,如果你喂给它的上下文是乱的、缺的、或者过量的,输出质量都会崩。我的策略是分层组织上下文:最上层是任务描述,告诉模型要做什么。中间层是相关技能,告诉模型怎么做。最下层是参考资料,给模型提供事实依据。这三层用Markdown的标题层级区分,模型解析起来很自然。
具体实现上,每个任务执行前,Harness会从Obsidian仓库里检索相关的Markdown文件。检索方式有两种:一种是基于双向链接的图遍历,从任务文件出发,找到直接链接和间接链接的技能和记忆文件。另一种是基于关键词的全文搜索,用简单的倒排索引实现,不依赖外部服务。两种方式的结果合并后,按相关度排序,取前N个文件的内容拼进上下文。N的大小根据模型的上下文窗口和任务的复杂度动态调整。
这里的关键是控制上下文的体积。40亿token一个月,平均每天一亿多,如果每次调用都塞满上下文,消耗会非常恐怖。我的做法是给每个Markdown文件算一个“信息密度”分数,密度低的文件只取摘要,密度高的文件取全文。摘要也是用模型生成的,但只在文件第一次被检索时生成一次,之后缓存起来。这样既保证了上下文的质量,又控制了体积。
还有一个细节是上下文的顺序。模型对上下文里靠前和靠后的内容更敏感,中间的内容容易被忽略。所以我把最重要的信息放在最前面,比如任务目标和关键约束。次重要的放在最后,比如输出格式要求。参考资料放在中间,因为模型只需要从中提取事实,不需要记住全部。这个顺序调整看起来微不足道,但实测对输出质量的提升很明显。
3.3 Token消耗的监控与优化手段
每个月40亿token,如果不做监控,你根本不知道钱花在哪里了。我在Harness里加了一个Token账本,每次模型调用都记录:时间、任务ID、模型名称、输入token数、输出token数、耗时、是否成功。这些记录写进一个Markdown表格,按天归档。然后用Obsidian的Dataview插件做一个仪表盘,随时能看到当天的消耗趋势、哪个任务最费token、哪个模型性价比最高。
优化手段有几个层面。第一层是缓存。同样的输入如果之前调过模型,结果直接复用,不重复调。这个缓存用文件系统实现,key是输入的哈希,value是模型的输出。缓存命中率在项目后期能达到三成左右,因为很多任务是重复的或者相似的。第二层是批处理。把多个小任务合并成一个大任务,一次调用完成。比如有十个文件需要做同样的格式转换,不要调十次模型,而是把十个文件的内容拼在一起,让模型一次性输出十个结果。第三层是模型降级。对于格式检查、简单分类这类任务,用规则引擎或者小模型替代大模型。我在Harness里写了一个规则引擎,能处理大概四成的任务,完全不调模型。
还有一个容易被忽略的点是输出长度的控制。模型有时候会啰嗦,明明一句话能说清楚,非要写三段。我在Prompt里明确要求输出简洁,并且在Harness里对输出做后处理,去掉重复的、无关的内容。这个后处理也是用规则做的,不调模型。实测下来,输出token能压缩两成左右。
4. 实操过程:从零搭建一个Harness架构应用的完整步骤
4.1 环境准备与基础工具链配置
第一步是装工具。你需要一个终端,推荐用iTerm2或者Windows Terminal。需要一个编辑器,VS Code或者Cursor都行,关键是能装插件。需要Obsidian,去官网下载安装,建一个仓库,仓库路径不要有中文和空格,不然后面脚本处理起来容易出问题。需要Python 3.10以上,因为要用到一些新的语法特性。需要Git,做版本管理。
Claude Code的安装看你的系统。macOS和Linux用命令行安装,Windows建议用WSL2,因为很多工具链在Windows原生环境下会有路径和权限的坑。安装完后,配置API Key,测试一下能不能正常调用。这一步不要跳过,我见过太多人卡在环境配置上,后面写代码的兴致全没了。
Obsidian的配置有几个关键点。第一,关闭“安全模式”,启用社区插件。第二,安装Dataview和Templater,前者用来查询Markdown元数据,后者用来生成标准格式的文件。第三,设置附件目录和模板目录,保持仓库结构清晰。第四,开启“自动保存”,避免内容丢失。这些设置都在Obsidian的设置面板里,点几下就好。
Python环境建议用venv或者conda建一个独立环境,不要污染系统Python。依赖包主要就是requests或者httpx用来调API,markdown-it-py用来解析Markdown,watchdog用来监控文件变化。不需要装太多东西,Harness的核心逻辑应该尽量少依赖第三方库,这样出问题的时候好排查。
4.2 Harness核心模块的代码实现与配置
Harness的核心模块分四个:解析器、调度器、执行器、记录器。解析器负责把Markdown文件读进来,转成Python对象。调度器负责根据任务依赖构建执行图。执行器负责调用模型或者规则引擎完成任务。记录器负责把执行过程和结果写回Markdown。
解析器的实现要点是容错。Markdown文件是人写的,难免有格式不规范的地方。解析器不能因为一个列表符号用错了就整个报错。我的做法是先用markdown-it-py做标准解析,如果失败,再用正则做降级解析。降级解析只提取标题和代码块,忽略其他格式。这样即使文件格式很乱,至少能提取出核心内容。
调度器的实现要点是状态持久化。任务执行到一半,如果程序崩了,重启后要能从上次的状态继续,而不是从头再来。我把每个任务的状态写在一个Markdown文件里,状态包括:待执行、执行中、成功、失败、跳过。调度器启动时先读所有任务文件,恢复状态,然后继续执行。这个机制在长时间运行的任务里特别重要,因为模型调用可能很慢,程序跑几个小时很正常。
执行器的实现要点是超时和重试。模型调用设30秒超时,超时后重试一次,再超时就标记失败。重试的时候要换一个模型或者调整参数,不要用同样的参数重试,否则大概率还是失败。记录器用Markdown表格记录每次调用的详情,表格的列包括:时间、任务、模型、输入token、输出token、耗时、状态。这个表格用Dataview查询起来很方便。
配置方面,我建议把所有可调参数放在一个config.md文件里,用Markdown表格写。比如模型名称、超时时间、重试次数、缓存路径、日志级别。这样改配置不用动代码,非技术人员也能改。解析器读这个文件,把表格转成字典,供其他模块使用。
4.3 从Markdown到Agent任务的完整流转示例
假设你要做一个任务:把一篇Markdown文章里的所有表格转换成Excel文件。这个任务在Harness里的流转过程是这样的。
首先,在/tasks目录下创建一个Markdown文件,标题是“表格转Excel”,内容里写清楚输入文件路径、输出目录、依赖的技能。然后,调度器读到这个任务,发现它依赖“解析Markdown表格”这个技能,于是去/skills目录找对应的技能文件。技能文件里写清楚了怎么识别表格、怎么提取数据、怎么生成Excel。执行器按照技能文件的描述,先调解析器把Markdown里的表格提取出来,转成二维数组。然后调模型或者用规则引擎把二维数组写成Excel文件。最后,记录器把执行结果写回任务文件,状态标记为成功,输出文件路径写在结果里。
整个过程里,人只需要写两个Markdown文件:任务文件和技能文件。剩下的解析、调度、执行、记录都是Harness自动完成的。这就是Harness架构的价值:把人的精力集中在定义“做什么”和“怎么做”上,而不是“怎么调通”上。
如果任务失败了,比如Markdown里的表格格式不标准,解析器会报错,记录器会把错误信息写进任务文件。你打开任务文件,看到错误信息,去修改源Markdown文件,然后重新触发任务。Harness会从失败的地方继续,不会重复已经成功的步骤。这个体验比从头跑一遍要好得多。
5. 常见问题与排查技巧实录
5.1 Agent执行中断与插件加载失败的排查
Agent执行中断最常见的原因是上下文超长。模型有上下文窗口限制,如果你塞进去的内容超过了限制,调用会直接失败。排查方法是看记录器里的输入token数,如果接近或者超过模型的窗口大小,就是这个问题。解决方法是精简上下文,去掉不必要的内容,或者换一个窗口更大的模型。
插件加载失败在Obsidian里比较常见,尤其是当你装了很多社区插件的时候。表现是Obsidian启动时报错,或者某个插件功能不工作。排查步骤是:先禁用所有插件,然后一个一个启用,看是哪个插件导致的。常见的原因是插件版本和Obsidian版本不兼容,或者插件之间有冲突。解决方法是更新插件到最新版,或者换一个功能类似的插件。
还有一个坑是文件路径问题。Obsidian仓库的路径如果有中文、空格、特殊字符,某些插件或者脚本会处理不了。我的建议是仓库路径只用英文字母、数字、下划线、连字符。文件名也一样,尽量用英文,避免用空格,用连字符代替。这个规范看起来麻烦,但能省掉后面无数的路径解析问题。
5.2 Markdown格式异常导致解析失败的修复方法
Markdown格式异常是解析失败的头号原因。常见的异常包括:标题层级跳跃(比如从#直接到###)、列表缩进不一致、代码块没有闭合、表格列数不匹配。这些异常在人看来可能无所谓,但解析器会懵。
我的修复方法是先规范化再解析。规范化脚本做几件事:把所有标题层级补全,比如#后面直接跟###,就在中间插入一个##。把所有列表符号统一成-,缩进统一成两个空格。检查代码块是否闭合,没闭合的补上。检查表格的列数,不匹配的用空单元格补齐。这个脚本用Python写,大概一百行,但能解决八成的解析问题。
如果规范化之后还是解析失败,那就需要人工检查了。我会把出问题的Markdown文件单独拿出来,用最小的例子复现问题。比如把文件内容逐步删减,直到找到导致失败的那一段。这个方法很笨,但很有效。找到问题后,把修复逻辑加进规范化脚本,下次就不会再犯。
5.3 Token消耗异常的定位与优化
Token消耗异常通常有三种表现:总量突然飙升、某个任务消耗特别大、输出token远大于输入token。总量飙升一般是某个循环任务出了问题,比如调度器陷入死循环,反复调模型。排查方法是看记录器里的时间线,找到消耗集中的时间段,然后看那个时间段在执行什么任务。某个任务消耗大,可能是上下文塞得太多,或者输出没有限制。输出token远大于输入,说明模型在“自由发挥”,需要加更严格的输出约束。
优化手段前面提过,这里补充一个Prompt压缩的技巧。同样的意思,用更短的Prompt表达,能省不少输入token。比如“请把下面的内容转换成JSON格式,确保字段名和值都正确”可以压缩成“转JSON,字段值准确”。模型能理解,但token少了一半。这个技巧需要反复试验,找到既简洁又不影响效果的表达方式。
还有一个技巧是分步调用。一个大任务拆成几个小任务,每个小任务的上下文更小,输出更可控。虽然调用次数多了,但总token可能更少,因为每次调用的上下文和输出都更精简。这个取舍要看具体情况,我的经验是:如果一个大任务的上下文超过模型窗口的一半,就考虑拆分。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| Agent执行中断 | 上下文超长 | 查看输入token数 | 精简上下文或换大窗口模型 |
| 插件加载失败 | 版本不兼容或插件冲突 | 逐个禁用插件排查 | 更新插件或替换插件 |
| Markdown解析失败 | 格式不规范 | 用规范化脚本预处理 | 补全标题层级、统一列表符号 |
| Token消耗飙升 | 循环任务或上下文过大 | 查看记录器时间线 | 修复循环、压缩上下文 |
| 输出质量差 | 上下文顺序不对 | 检查重要信息的位置 | 重要信息放前或放后 |
| 任务卡住不结束 | 模型调用超时未处理 | 查看任务状态 | 设超时和重试机制 |
| 文件路径报错 | 路径含中文或空格 | 检查仓库路径 | 改用英文和连字符 |
这张表是我九个月里踩过的坑的浓缩版。每一个问题都真实发生过,每一个解决方案都验证过。你可以把它打印出来贴在显示器旁边,遇到问题先查表,能省不少时间。
6. 九个月里那些没人告诉你的经验
6.1 关于模型选择的真实体会
模型不是越强越好,而是越合适越好。我一开始什么都用最强的模型,结果token消耗爆炸,而且很多任务根本不需要那么强的能力。后来我做了模型路由,简单任务用便宜模型,复杂任务用强模型,消耗降了六成,效果几乎没变。这个经验告诉我,不要用大炮打蚊子。
另一个体会是,模型的稳定性比能力更重要。有些模型能力很强,但输出格式不稳定,有时候给JSON,有时候给Markdown,有时候给纯文本。这种不确定性在自动化流程里是灾难,因为你的解析器要处理各种格式。我后来选模型的时候,把格式稳定性作为第一优先级,能力只要够用就行。这个取舍在长期项目里特别重要,因为维护解析逻辑的成本远高于模型能力提升带来的收益。
6.2 关于Markdown作为数据格式的边界
Markdown适合做人和模型都能读的中间格式,但它不适合做精确的结构化数据存储。如果你需要严格的字段类型、嵌套结构、查询能力,Markdown会力不从心。我的做法是:Markdown做输入和输出的载体,中间处理用Python对象,需要持久化的时候再转回Markdown。这样既保留了Markdown的可读性,又避免了它的局限性。
还有一个边界是文件大小。单个Markdown文件不要超过一万行,否则解析和编辑都会变慢。如果内容太多,就拆成多个文件,用双向链接关联起来。Obsidian的图谱视图能帮你看到文件之间的关联,如果某个文件的链接特别多,说明它承担了太多职责,需要考虑拆分。
6.3 一个人做长周期项目的节奏管理
九个月一个人做项目,最大的敌人不是技术难题,是节奏。我试过连续几天高强度编码,然后接下来一周都不想碰代码。后来我调整了节奏:每天固定时间写代码,不超过六小时,剩下的时间用来写文档、整理思路、测试。这样虽然每天产出少了,但可持续,九个月下来反而比突击式开发完成得多。
另一个体会是定期回顾。每周花半天时间,把这一周写的Markdown文件过一遍,看看哪些技能可以合并,哪些任务可以自动化,哪些上下文可以精简。这个回顾习惯帮我发现了不少重复劳动,也让我及时调整了架构。如果没有这个习惯,我可能会在错误的路上走很远才发现。
6.4 最后分享几个小技巧
第一个技巧:用Git做版本管理,但不要频繁提交。我一开始每改一个文件就提交一次,结果Git历史乱七八糟,想回滚都找不到合适的点。后来改成每天提交一次,提交信息写清楚当天完成了什么。这样历史清晰,回滚也方便。
第二个技巧:给每个Markdown文件加一个元数据块。用YAML front matter写文件的创建时间、修改时间、标签、状态。Dataview可以用这些元数据做查询,比如“找出所有状态为待办的任务”。这个习惯让Obsidian仓库从一堆文件变成了一个可查询的数据库。
第三个技巧:定期备份。Obsidian仓库虽然可以用Git管理,但Git仓库本身也可能出问题。我每周会把整个仓库打包压缩,存到另一个地方。这个习惯救过我一次,硬盘出问题的时候,备份让我只损失了一天的进度。
第四个技巧:不要追求完美。Harness架构可以无限优化,但你的时间是有限的。我见过很多人卡在“把架构设计得更优雅”上,结果项目永远停留在设计阶段。我的做法是:先跑通,再优化。跑通之后,你才知道哪里是真正的瓶颈,哪里值得优化。九个月里,我重构了三次Harness,每次都是因为跑通之后发现了更好的做法。如果一开始就追求完美,可能第一次重构都不会发生。