
1. 先搞清楚这个标题到底在说什么一个人、九个月、20 万行代码、每个月烧掉 40 亿 token——这组数字放在一起第一反应是夸张第二反应是好奇到底是什么样的应用需要这么大的代码量和 token 消耗答案落在两个关键词上Harness 架构和Agent。先把概念说清楚。Harness 在这里不是指某个具体的商业产品而是指一类以编排层为核心的 Agent 应用架构。你可以把它理解成一个总调度台底下挂着各种模型、工具、记忆存储、文件系统上面跑着用户的真实任务中间那一层负责决定这一步该调谁、下一步该传什么、失败了怎么重试、上下文怎么裁剪。Claude Code、DeepSeek Harness、Pi Agent 这类工具本质上都是 Harness 思路的不同实现。为什么一个人九个月能写出 20 万行因为 Harness 架构的应用有个特点核心逻辑不复杂但边界情况极多。一个 Agent 循环可能只有几百行但要让它稳定处理 Markdown 解析、Obsidian 笔记导入、表格转换、数学公式渲染、插件加载失败、执行中断恢复……这些边角料加起来代码量会指数级膨胀。至于每月 40 亿 token换算下来大概是每天 1.3 亿左右如果按一次复杂任务消耗 5 万 token 估算相当于每天跑 2600 次完整任务——这个量级对于一个有真实用户的产品来说并不离谱。这篇文章适合三类人看正在做 Agent 应用、被上下文和稳定性折磨的开发者想理解 Harness 架构到底解决什么问题、值不值得投入的技术决策者以及好奇一个人怎么扛住这种规模项目的独立开发者。我会把架构选型、token 消耗的真实构成、Markdown 与 Obsidian 这类具体场景的坑、以及一个人维护 20 万行代码的工程方法全部拆开讲。2. Harness 架构到底解决了什么问题2.1 从直接调模型到编排层的必然演进早期做 Agent很多人的做法是写一个 while 循环把用户输入丢给模型模型返回工具调用就执行执行完把结果塞回去循环到模型说我完成了为止。这个模式在 demo 阶段很好用几十行代码就能跑通。但一旦上真实场景问题立刻暴露。第一个问题是上下文爆炸。模型每轮都要看到完整历史工具返回的结果动辄几千 token跑十几轮之后上下文就满了。第二个问题是错误恢复。工具调用失败、模型输出格式不对、网络超时这些在 demo 里可以忽略在生产里必须处理。第三个问题是状态管理。用户中途关掉页面、任务跑到一半需要人工确认、多个任务并发——这些都需要一个独立于模型之外的状态层。Harness 架构的核心价值就是把这些模型之外的事全部收拢到一个编排层里。模型只负责决策编排层负责执行、记录、裁剪、重试、持久化。这个分工一旦确立整个系统的可维护性会有质的提升。2.2 Harness 的四个核心组件一个典型的 Harness 架构我把它拆成四块组件职责常见实现调度器决定下一步动作管理循环状态机 / 事件驱动上下文管理器裁剪、压缩、检索历史滑动窗口 摘要 向量检索工具执行层调用外部能力处理超时重试沙箱 超时 幂等封装持久化层保存会话、任务、中间状态文件系统 / 数据库这四块里上下文管理器是最烧脑也最烧 token 的。很多人以为 token 消耗主要在模型推理其实不然——大量的 token 花在把历史重新喂给模型上。一个跑了 30 轮的任务如果每轮都把完整历史塞进去token 消耗是 O(n²) 级别的。这就是为什么 40 亿 token 这个数字在 Harness 架构里并不夸张。2.3 为什么是Harness而不是Framework这里有个容易混淆的点。Framework框架通常提供一套约定你按它的方式写代码Harness挽具/编排层更像是套在模型外面的壳它不规定你怎么写业务逻辑只负责把模型和外部世界连接起来。打个比方Framework 像是给你一套乐高积木和说明书你得按它的接口拼Harness 像是给一匹野马套上缰绳和马鞍马还是那匹马模型但你现在能控制它往哪跑、跑多快、什么时候停。Claude Code 就是典型的 Harness——它不改变 Claude 模型本身而是在外面套了一层文件操作、命令执行、上下文管理的壳。理解了这一点就能明白为什么 Harness 架构的应用代码量会这么大它要处理的是模型和真实世界之间的所有摩擦而真实世界的摩擦是无穷无尽的。3. 每月 40 亿 token 到底花在哪了3.1 token 消耗的四个大头我把一个 Harness 应用的 token 消耗拆成四部分按占比排序历史上下文重放约 45%每轮对话把之前的消息重新发给模型。这是最大头也是最容易优化的。工具返回结果约 25%文件内容、命令输出、搜索结果这些原始数据往往很长。系统提示词约 15%工具定义、行为规范、few-shot 示例每次请求都要带。模型实际推理约 15%真正思考消耗的部分反而占比最小。看到这个分布优化方向就很清楚了别在推理上抠要在上下文上省。3.2 上下文裁剪的三种策略我在项目里试过三种裁剪策略效果差异很大策略一滑动窗口。只保留最近 N 轮对话。实现最简单但问题是模型会失忆——用户前面提到的关键信息几轮之后就被挤掉了。适合短任务。策略二摘要压缩。把旧对话用模型总结成一段话替换掉原始消息。这个方案省 token 效果明显但每次摘要本身也要消耗 token而且摘要会丢失细节。适合中等长度任务。策略三分层记忆。把信息分成工作记忆当前任务相关、短期记忆本次会话、长期记忆跨会话存向量库。每轮只把工作记忆和检索到的相关长期记忆喂给模型。这个方案最省 token但实现复杂度最高。实测下来分层记忆 滑动窗口的混合方案性价比最高。具体做法是最近 5 轮用原始消息5 轮之前的内容做摘要跨会话的信息走向量检索。这样既保证了近期上下文的完整性又控制了总量。3.3 一个真实的 token 账单拆解假设一个任务跑了 20 轮每轮工具返回平均 2000 token系统提示词 3000 token不做任何优化第 n 轮输入 3000 2000×n 历史累积20 轮总计约80 万 token滑动窗口保留 5 轮每轮输入稳定在 3000 2000×5 1.3 万20 轮总计约26 万 token分层记忆每轮输入约 800020 轮总计约16 万 token从 80 万降到 16 万省了 80%。这就是为什么同样功能的应用token 消耗能差好几倍。40 亿 token 听起来吓人但如果没做优化可能实际有效推理只占其中一小部分。提示token 优化不要一上来就上最复杂的方案。先用滑动窗口跑通观察真实消耗分布再针对性优化。过早引入向量检索往往带来的是调试成本而非收益。4. Markdown、Obsidian 这些场景为什么是重灾区4.1 Markdown 解析的隐藏复杂度热词里出现了大量 Markdown 相关词条markdown 换行、markdown 语法、markdown 表格转换 excel、markdown 数学公式插件、markdown 表格复制、markdown 阅读器。这不是偶然——只要你的 Agent 应用涉及文档处理Markdown 就是绕不开的坎。Markdown 看起来简单实际上是个方言极多的格式。CommonMark、GFM、各种扩展语法换行规则、表格对齐、数学公式、嵌套列表每个都有坑。比如换行有的解析器要求行尾两个空格才换行有的直接换行就生效。你的 Agent 生成的内容在不同渲染器里显示效果可能完全不同。我在项目里踩过最深的坑是表格转换。用户想把 Markdown 表格转成 Excel听起来简单但表格里如果有合并单元格、多行内容、特殊字符转换逻辑就会变得极其复杂。而且 Markdown 表格本身不支持合并单元格用户往往用 HTML 标签硬塞解析器一遇到就崩。4.2 Obsidian 集成的真实难点Obsidian 是本地优先的笔记工具数据以 Markdown 文件形式存在本地。把 Agent 和 Obsidian 结合看起来是天然搭配实际做起来有几个硬骨头第一文件监听与并发写入。Obsidian 会实时监听文件变化你的 Agent 如果也在写文件两边容易打架。我遇到过 Agent 写入一半Obsidian 触发重新索引导致文件锁冲突的情况。解决方案是写入时用临时文件 原子替换并且给文件加一个短暂的写入标记。第二双链和标签的解析。Obsidian 的[[双链]]和#标签是它的核心特性但这些不是标准 Markdown。你的解析器必须单独处理否则会把它们当成普通文本。更麻烦的是双链可能指向不存在的文件标签可能有层级结构。第三插件生态的兼容性。热词里有harness failed to load plugins和obsidian插件推荐说明插件加载失败是高频问题。Obsidian 插件运行在 Electron 环境里和你的 Agent 进程是隔离的。如果 Agent 想调用插件能力只能通过文件系统或本地 API 间接通信延迟和可靠性都是问题。4.3 Zotero 笔记导入 Obsidian 的典型链路热词里有个很具体的问题如何将 zotero 的笔记导入 obsidian。这是个典型的 Agent 应用场景我拆一下完整链路Zotero 导出笔记为 Markdown 或 BibTeX解析导出文件提取标题、作者、年份、笔记正文转换成 Obsidian 友好的格式加 frontmatter、处理双链、规范化标签写入 Obsidian vault 的指定目录触发 Obsidian 重新索引每一步都有坑。第 2 步Zotero 导出的 Markdown 格式不统一不同版本差异很大。第 3 步frontmatter 的字段命名要和用户的 Dataview 查询匹配否则导入的笔记在用户的看板里显示不出来。第 5 步Obsidian 的索引有延迟如果导入后立即查询可能查不到。这类看起来简单、做起来全是细节的场景正是 Harness 应用代码量膨胀的根源。每一个场景单独看都不难但几十个场景叠加起来就是 20 万行代码。5. 一个人维护 20 万行代码的工程方法5.1 模块边界比代码质量更重要一个人写 20 万行最大的敌人不是写不出来而是改不动。三个月后回头看自己写的代码如果模块边界不清晰改一个功能要动五个文件项目就死了。我的做法是按变化频率划分模块。变化最频繁的业务逻辑、提示词放一层变化中等的工具封装、解析器放一层几乎不变的调度器核心、持久化放一层。层与层之间只通过明确的接口通信禁止跨层直接调用。这样划分的好处是改提示词不会碰到调度器加新工具不会影响解析器。每一层的测试也可以独立进行。5.2 用类型系统当第二大脑20 万行代码靠脑子记是不现实的。我用 TypeScript 的严格模式把所有核心数据结构都定义成类型。模型返回的结果、工具调用的参数、会话的状态全部有明确的类型约束。这不是为了代码优雅而是为了让编译器帮我记住东西。当我改了一个字段名编译器会告诉我所有受影响的地方。当我忘了处理某个状态类型检查会报错。一个人维护大项目类型系统就是你的结对程序员。5.3 日志和可观测性不能省Agent 应用最怕的是跑着跑着不对了但不知道为什么。模型是黑盒工具是黑盒中间还有网络和文件系统。没有完善的日志排查问题就是大海捞针。我在项目里做了三层日志请求级每次模型调用的输入输出、token 数、任务级一个完整任务的步骤序列、耗时、状态变化、系统级资源占用、错误率、重试次数。三层日志分开存储排查时按需查看。特别要记的是模型的原始输出。很多时候模型返回的 JSON 格式不对或者工具调用参数有误如果你只记了解析后的结果就丢失了排查线索。原始输出一定要落盘。5.4 测试策略重点测边界不测 happy path20 万行代码全量测试不现实。我的策略是happy path 靠手动验证边界情况靠自动化测试。具体来说模型正常返回、工具正常执行这种顺利路径我手动跑几次确认就行。但像模型返回空、工具超时、文件不存在、上下文超长、并发写入冲突这些边界必须写自动化测试。因为这些情况在开发时很难复现但上线后一定会遇到。我专门建了一个故障注入测试集模拟各种异常网络中断、模型返回乱码、工具返回超大结果、磁盘满、权限不足。每次重构后跑一遍确保错误处理逻辑没被破坏。6. Agent 并发与稳定性那些文档不会告诉你的事6.1 并发不是多开几个线程那么简单热词里有ai agent 怎么扛并发这是个真问题。Agent 的并发和普通 Web 服务的并发完全不同。普通服务是无状态的请求之间互不影响Agent 是有状态的每个任务都有自己的上下文、工具调用序列、中间文件。我试过几种并发方案方案一进程级隔离。每个任务起一个独立进程。隔离性最好但资源消耗大启动慢。适合任务重、数量少的场景。方案二协程级并发。用异步 IO 在单进程内跑多个任务。资源效率高但一个任务阻塞比如同步文件操作会影响其他任务。需要严格控制阻塞操作。方案三任务队列 工作池。任务进队列固定数量的 worker 消费。可控性最好但需要处理任务优先级和超时。实测下来方案三最适合 Harness 应用。因为 Agent 任务的执行时间差异极大有的几秒有的几分钟用固定 worker 池能避免资源被少数长任务占满。关键是给每个任务设超时超时的任务强制中断并清理资源。6.2 中断恢复Agent 应用的必修课热词里有agent execution terminated due to error这是每个 Agent 开发者都会遇到的。任务跑到一半挂了用户希望从断点继续而不是从头再来。实现中断恢复的关键是状态快照。每完成一个步骤就把当前状态已执行的动作、工具返回、上下文摘要持久化。恢复时从最后一个快照加载继续执行。但这里有个陷阱不是所有操作都可重放。比如发送邮件这种副作用操作重放会导致重复发送。所以状态快照里要记录哪些操作已完成且不可重放恢复时跳过这些。我的做法是给每个工具调用打一个幂等键工具执行前先检查这个键是否已执行过。这样即使恢复时重放也不会产生副作用。6.3 模型输出的不确定性怎么兜底同一个提示词模型两次输出可能不一样。这在 demo 里无所谓在生产里是灾难。用户今天用得好好的明天同样的操作就失败了。兜底策略有三层第一层格式校验 重试。模型返回的 JSON 解析失败自动重试最多 3 次。重试时把错误信息也带上让模型知道上次错在哪。第二层降级方案。如果模型连续失败切换到更简单的处理逻辑。比如本来要模型生成结构化数据降级成让模型输出纯文本由代码来解析。第三层人工介入。关键操作如果自动处理失败暂停任务通知用户手动确认。宁可慢一点也不能出错。注意重试不是万能的。如果模型是因为提示词本身有歧义而失败重试多少次都一样。这时候要改的是提示词不是重试次数。7. 从 0 到 1 的实操路线建议7.1 第一周跑通最小闭环别一上来就设计完美架构。第一周的目标是用户输入 → 模型决策 → 工具执行 → 返回结果这个闭环能跑通就行。工具先只做一个比如读文件。调度器用最简单的 while 循环。上下文不做裁剪全量塞。持久化先不做内存里存着。这个版本可能只有几百行但它验证了核心链路。7.2 第二到四周加工具、加持久化闭环跑通后开始加工具。每加一个工具就思考一个问题这个工具失败了怎么办。超时、返回格式错误、权限不足每种情况都要有处理。同时把持久化加上。会话状态、任务状态、中间结果全部落盘。这时候你会发现持久化的设计会反过来影响调度器的设计——因为调度器要能从任意状态恢复。7.3 第二个月上下文优化前一个月你可能已经烧了不少 token。这时候开始做上下文优化先上滑动窗口观察效果再上摘要压缩最后考虑向量检索。优化的顺序很重要从简单到复杂。每上一个方案都要有数据支撑——优化前多少 token优化后多少 token效果差多少。没有数据支撑的优化往往是过度设计。7.4 第三个月起稳定性和可观测性功能基本齐了之后重心转向稳定性。补日志、补测试、补错误处理。这个阶段很枯燥但决定了你的应用能不能真正给用户用。我个人的经验是稳定性投入应该占整个项目时间的 40% 以上。很多人把 80% 时间花在功能上结果应用一上真实场景就崩返工的成本更高。8. 一些踩过的坑和真实体会第一个坑是过早优化上下文。项目初期我就上了向量检索结果调试成本极高检索不准导致模型拿到无关信息反而更容易出错。后来退回到滑动窗口简单可靠效果反而更好。向量检索是在数据量和场景都稳定之后才加回来的。第二个坑是低估了 Markdown 解析的复杂度。我以为用现成的解析库就行结果发现每个库对边缘语法的处理都不一样而且用户的实际文档里充满了各种非标准但常见的写法。最后我自己写了一个容错解析器遇到不认识的语法就原样保留而不是报错。第三个坑是没有给工具调用设超时。有个工具调用外部服务服务挂了但连接没断任务就一直卡在那里。后来所有工具调用都强制加超时超时就中断并返回错误。第四个坑是日志记太少。早期为了省事只记了任务的成功失败没记中间过程。结果用户报任务失败了我完全不知道失败在哪一步。后来补了详细日志排查效率提升了一个数量级。关于 token 消耗我的体会是别被大数字吓到要看单位价值。40 亿 token 听起来多但如果每个 token 都在解决真实问题这个投入是值得的。真正浪费的是那些重复的、无意义的上下文重放。把这块优化好token 消耗能降一半以上而用户体验几乎不受影响。最后说一个心态上的体会。一个人做这种规模的项目最大的挑战不是技术是孤独和坚持。九个月里会有无数次想放弃的时刻尤其是遇到那种看起来简单但就是搞不定的 bug。我的方法是把大目标拆成小目标每完成一个就给自己一点正反馈。20 万行代码不是一天写完的是一天几百行、几百行堆出来的。