一个人九个月二十万行代码:Harness架构与Markdown Agent实践

发布时间:2026/9/30 18:40:33
一个人九个月二十万行代码:Harness架构与Markdown Agent实践 1. 一个人九个月二十万行代码这件事到底在说什么先把标题里的数字拆开看。一个人意味着没有团队分工没有前后端联调没有产品经理帮你砍需求也没有测试帮你兜底。九个月大约是270天如果按每周工作六天算有效开发时间大概在230天左右。20万行代码平均下来每天要净增将近900行有效代码。这个数字放在传统手写业务代码的场景里几乎不可能完成因为人一天能真正想清楚并写对的逻辑通常也就两三百行。所以这20万行里必然有相当比例是结构化配置、声明式描述、自动生成的胶水层以及大量可复用的模板。而每个月烧掉40亿以上的token说明这个项目在开发过程中重度依赖大模型能力不是偶尔问几句而是把模型当成了日常生产工具嵌进了编码、重构、文档、测试的每一个环节。这个标题真正想表达的不是“我有多能熬”而是一种新的工程范式Harness架构应用。Harness这个词在工程语境里指的是把底层能力封装成可编排、可替换、可观测的骨架让具体的业务逻辑像插件一样挂上去。你可以把它理解成一套“插座系统”——墙里的电线怎么走你不用每次重接你只需要把不同的电器插上去。在这个项目里Harness负责的是Agent的调度、上下文的组织、工具链的接入、状态的持久化而真正干活的部分是Markdown文档、是Agent技能、是Obsidian里的知识网络。为什么是Markdown因为Markdown是人和模型都能读的中间格式。人写起来不累模型解析起来不费劲版本管理还特别友好。为什么是Obsidian因为它把Markdown文件组织成了一个双向链接的知识图谱天然适合做Agent的长期记忆和上下文检索。为什么是Claude Code这类工具因为它们把模型能力直接拉到了终端和编辑器里减少了复制粘贴的摩擦。这几个东西凑在一起就形成了一个闭环你在Obsidian里写MarkdownAgent读取这些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依赖BB又依赖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用来调APImarkdown-it-py用来解析Markdownwatchdog用来监控文件变化。不需要装太多东西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每次都是因为跑通之后发现了更好的做法。如果一开始就追求完美可能第一次重构都不会发生。