如果只看最近几个热点,Agent Harness 像是突然从幕后走到了台前。
Claude Code 的一次 source map 泄露,公开报道里出现了约 50 万行代码、近 2000 个文件。人们没有看到一个可以复制的神奇 prompt,看到的是一个成熟 Coding Agent 背后的产品工程。
DeepSeek 先开源了 DSH,把模型、工具、Session、沙箱、压缩和交互做成可替换、可组合的插件。
更晚公开的 Codex Harness 走了另一条路。OpenAI 解释了共享 agent loop与 App Server:同一套 Harness 怎样服务终端、IDE、Web 和桌面端,怎样用 Item、Turn、Thread 表达会话,怎样处理审批、流式事件与重连;相关实现位于公开的 Codex 仓库中。
这些事件很容易诱导出一种选型式讨论:Claude Code、Codex、DSH,谁的 Harness 最强?我的 Personal Agent 是否也算一个 Harness?既然通用方案已经公开,我之前做的东西是不是应该推倒?
我最初也试图这样比较,后来发现维度错了。
Claude Code 与 Codex 首先是 Coding Agent 产品,DSH 更接近可组合的 Harness 框架。Personal Agent 则是一个面向我自己的产品:iPhone 提供随时可用的对话与确认入口,后端持有状态、策略、MCP、审计和高权限凭据,让个人数据能够被读取、整理和行动。它们不在同一条产品赛道上,不能拿功能多少直接比较。
截至目前,Finance 是 Personal Agent 已经打通的第一条纵向链路:一句“午饭 45”可以经过真实 iPhone、公网服务和 Finance MCP,安全写入真实飞书账本;遇到超时和重启,系统不会直接重试制造重复账,也不能拿模型生成的“记好了”冒充成功。其他个人工作流仍是后续扩展,不属于本文的已完成范围。
Finance 之所以适合作为第一块压力测试,是因为钱、外部写入和真实设备,会最快暴露一个 Agent 到底只是会回答,还是已经能对行动负责。
因此我不再按项目名称比较,而是把问题拆成六层:
| 层 | 公共 Harness 已经提供什么 | Personal Agent 仍需定义什么 |
|---|---|---|
| 1. 产品与框架 | 可复用的模型运行、会话和产品框架 | 哪些能力应该复用,哪些产品责任不能交给框架 |
| 2. 运行循环 | 模型、工具与事件怎样推进一个模型回合 | 一次真实业务操作怎样跨回合、跨网络并最终收敛 |
| 3. 工具边界 | 工具接入、沙箱、审批与执行机制 | 产品愿意让模型造成哪些最小业务后果 |
| 4. 上下文与记忆 | 历史怎样持久化、压缩和重放 | 哪些事实进入模型,哪些只留给审计和状态机 |
| 5. 权限与人机协作 | 动作前怎样允许、拒绝或暂停 | 人在副作用前后分别以什么身份介入 |
| 6. 证据与恢复 | 任务怎样持久化、恢复并呈现结果 | 由哪个领域权威宣布真实事情已经完成 |
这六层不是一套新的通用 Harness 规范,而是帮助我划清自建边界:公共运行机制成熟了就复用;真实业务后果仍由产品自己负责。
先把我真正做过的六个决定摆在桌面上:
| 我必须回答的问题 | 我的选择 | 我主动放弃了什么 | 我接受的代价 |
|---|---|---|---|
| 产品应该写进框架多深 | ADK 只做模型运行时,业务治理留在框架外 | 把状态与策略直接写进 ADK;自建一个通用 Harness | 多维护一层适配和显式合同 |
| 提交结果未知时怎么办 | 不自动重试,保留可恢复状态并回到事实源对账 | 把超时当失败;让用户重新提交 | 状态机更复杂,最终反馈可能更慢 |
| 模型能看到哪些工具 | 只开放窄业务动作,不暴露通用飞书写接口 | 用 prompt 约束一个万能 MCP | 每扩展一个领域,都要重新定义工具合同 |
| 连续体验与模型上下文如何兼得 | 用户保持一条时间线,服务端切分模型会话并治理上下文投影 | 让用户自己开新聊天;把全部历史塞进一个会话 | 增加会话路由与投影逻辑 |
| 人的结论能否改写机器状态 | 人工观察与机器证据并存,冲突不覆盖 | 用一个“人工确认”布尔值直接收尾 | 状态与 UI 更复杂,矛盾会被保留下来 |
| 什么才算完成 | 用安装、服务、设备、外部回执和恢复形成证据链 | 用测试通过、HTTP 200 或系统自报替代完成 | 验收更慢、更贵,也更容易推翻进度 |
下面六层要解释的,不只是这些选择是什么,更是我为什么没有选择另外一边。
第一层:产品与框架——通用层越强,自建层越应该薄
Claude Code 与 Codex 要把 Agent 做成每天可用的完整产品:读写代码库、执行命令、申请权限、管理上下文、恢复任务,再把过程持续呈现给用户。
DSH 的位置不同。它更接近一个可组装的 Harness 内核:能力形成插件树,服务接口、具体实现与使用方彼此分离;模型、Session、工具、沙箱等基础能力都可以在组合时替换。
Personal Agent 不需要复刻成熟 Coding Agent 的全部交互,也不需要让任意开发者组装任意 Agent。它需要做的是为自己的工作流选择公共能力,并把业务责任留在框架之外。
我很早做过一个现在看来仍然正确的决定:把 Google ADK 当作当前模型运行时,而不是把整个产品写进 ADK。
当时其实有三条路。第一条最快:沿着 ADK 的对象和生命周期直接写业务状态、工具策略与会话逻辑,先把功能跑通。第二条最“平台化”:从零做一套框架无关的通用 Harness,把模型、工具和会话全部重新抽象。第三条位于中间:接受 ADK 已经提供的模型循环,但在它外面保留自己的业务状态机和治理边界。
我选择了第三条。判断标准不是哪套架构看起来最完整,而是两件事:容易变化的运行时必须可替换,不能变化的业务责任必须有稳定归属。 模型供应商、Adapter 和会话框架都可能变化;一笔账怎样避免重复、谁有权写入、什么证据算成功,却不能因为换框架而重新解释。
模型适配器可以替换,MCP Client 可以演进,未来也可以借用更成熟的会话或运行循环;但业务策略、工具合同、操作状态、审计和外部回执不能一起焊进模型框架。调用关系始终是:
Agent 提出工具意图 → 业务策略判断 → MCP Client 执行 → Connector 访问事实源 → 系统验证结果。
这个选择有真实代价。我需要维护框架输出到业务意图的转换、把工具结果重新翻译成领域状态,还要为两侧分别写测试。ADK 新增一项能力,也不会自动变成产品能力。但我愿意承担这层摩擦,因为它让我可以替换模型运行时,却不用迁移已经发生过的账、回执和审计语义。
Codex 共用一套 Harness、DSH 允许基础能力按组合替换,反而让我更确定这条边界的价值:公共层进化越快,我越不应该长期维护重复实现,更不应该把产品责任绑定在某一个框架里。
这一层的结论:通用能力越强,自建的通用层越应该薄;真正不能外包的是产品对结果的定义。
第二层:运行循环——模型回合结束,业务操作未必结束
通用 agent loop 的基本形状已经稳定:组装上下文和工具,调用模型;模型要么回答,要么发出工具调用;工具结果进入下一次推理,直到模型把控制权交还给用户。Codex 用 Item、Turn、Thread 把这个过程开放给不同客户端,DSH 则用追加式 SessionEvent 记录会话事件。
Personal Agent 的一次真实事故提醒我:模型回合的生命周期和业务操作的生命周期不是一回事。
一期里,一笔支出已经写入飞书,Finance 也完成了写后回读,但 MCP HTTP Client 继承了 5 秒读超时。最终响应返回前连接被切断,Agent 只知道“没有收到结果”,于是把操作显示成 source_commit_unknown。
如果把它当成普通工具失败,最自然的做法是重试,或者让用户再试一次。但网络结果未知,不等于外部写入没有发生;两种处理都可能制造重复账。
摆在我面前的其实是三个产品选项:
- 自动重试:用户最快得到结果,但最容易重复写入;
- 直接报失败,让用户重发:系统实现最简单,却把重复风险转嫁给用户;
- 保留“结果未知”并继续对账:反馈更慢,状态更多,但不会把未知伪装成失败或成功。
我选择第三个。这里的价值排序很明确:对记账来说,正确性优先于即时反馈,可恢复的等待优先于一个看起来干脆的错误结论。
最后我把一次写操作拆成更长的状态链:
interpret → policy → resolve → commit → verify → reconcile。
提交前先持久化操作身份与幂等键;提交后不能只凭 HTTP 返回宣布成功;超时进入可恢复的未知状态,不直接重试,而是带着原操作身份回到 Finance 和飞书对账。只有拿到与原操作绑定、经过写后回读的权威回执,系统才能把结果呈现为成功。
这个决定又带出三条具体规则:越过提交边界后,取消不能再显示成“未提交”;恢复过程必须复用原来的操作身份,不能创建一笔“补偿性新请求”;Finance 暂时查不到记录,也只能说明证据尚未到达,不能立刻证明没有写入。
这改变了系统里的权威关系:
- 模型只能提出意图,不是完成权威;
- Agent 数据库保存操作状态,不是账本权威;
- 飞书保存外部事实;
- 与原操作绑定的回执,才是完成证据。
代价是一次写入不再只有成功和失败两个状态。UI 要能解释“仍在核对”,服务端要有恢复 Worker,运维要知道哪些中间状态可以接管,哪些仍可能有活跃请求。但这套复杂度不是为了追求架构漂亮,而是把重复记账的风险留在系统里处理,而不是留给用户猜。
公共 Harness 可以组织一个模型回合,Personal Agent 还必须管理跨回合、跨网络、甚至跨进程重启的业务操作。
这一层的结论:不要把 agent loop 当成业务状态机。模型回合可以结束,副作用留下的不确定性必须继续活着。
第三层:工具边界——先决定模型可以造成什么后果
Claude Code、Codex 与 DSH 都提供了丰富的工具接入、沙箱、审批和执行机制。但接入一项能力,不等于一个具体产品应该把它交给模型。
Personal Agent 一期做过一个看似保守、其实非常关键的决定:不把通用飞书 MCP 直接暴露给模型。
如果模型拿到“创建任意多维表记录”“修改任意字段”这样的通用能力,一句话理解错,就可能写错表、写错列,或者绕过产品规则。于是我只开放业务命名的窄工具:记支出、记收入、查询账目、处理家庭基金。模型表达业务意图,Finance MCP 再负责字段映射、幂等、重复检测和回读验证。
这里同样有三个选择。最省事的是直接开放通用飞书 MCP,再用 system prompt 告诉模型应该写哪张表。稍微收紧一点,是保留通用写接口,在服务端做字段 allowlist。我的选择更窄:模型只能提出“记一笔支出”这样的业务动作,连飞书表、列名和记录结构都不应该知道。
原因是 prompt、schema 和授权分别解决不同问题。Prompt 影响模型怎么想,schema 检查参数长什么样,授权才决定这一次操作能否发生。把三件事混在一个通用工具里,相当于让模型既提出动作,又携带证明自己应该被允许的材料。
后来这条边界继续被拆细:
- 模型可见能力与服务端真实能力不是同一集合;
- 只读解析与产生副作用的提交不是同一步;
- UI 可以提供确定性操作,但不代表这个操作必须暴露给模型;
- 同一个工具 schema 通过,也不代表它获得了本次业务操作的授权。
从此以后,我给新领域接工具时会按同一条链路决策:先确定权威事实源,再定义最小业务动作;列出歧义时必须拒绝的情况;定义成功回执;最后才决定这项能力是否对模型可见。工具注册反而是最后一步。
代价也很直接:通用飞书 MCP 已经有的能力,我仍然要为 Finance 再写一层领域工具;未来每接一个领域,都要重新做这套定义。扩展速度会变慢。但换来的不是“模型更听话”,而是一次理解错误最多只能落在预先定义的业务半径内。
这些设计与公共 Harness 的能力接口、沙箱和审批机制可以互相借鉴,但问题的起点不同。通用框架要回答“怎样安全提供这项能力”;具体产品还要先回答“这项能力是否应该存在,以及最坏能造成什么后果”。
对一个单用户系统,危险不一定来自漏掉一条权限校验,也可能来自一开始就给了模型一个过于通用的动词。
这一层的结论:工具不是模型的 API 菜单,而是产品愿意承担后果的最小动作单元。能靠提示词约束的,不等于应该靠提示词约束。
第四层:上下文与记忆——事实源不等于模型输入
Codex 把 Item、Turn、Thread 做成协议原语,并处理上下文组装与压缩;DSH 保存追加式 SessionEvent,再从事件派生模型可见消息。这些公开设计都说明:上下文不是一串聊天文本,而是 Harness 的核心状态。
Personal Agent 在生产环境里为这件事付过一次很具体的学费。
我的 UI 只有一条连续时间线。我不希望用户为了模型上下文上限,自己维护十几个“新聊天”。服务端原本应该在连续时间线下自动切分模型会话,但生产配置没有真正切开。从 7 月 31 日起,正常对话、故障演练、重复卡片、失败结果和恢复记录,78 条以上内容进入了同一个 Session。
这里最初的产品选择也不是天然正确。我可以完全照搬聊天产品,让用户通过“新建会话”管理上下文;也可以让一条用户时间线永远对应一个模型会话,换取最连续的记忆体验。我选择第三种:用户只感知一条连续时间线,系统在背后切分模型会话,每次调用再从事实中构造最小充分上下文。
我不想把模型的技术限制转嫁成用户的信息架构,也不愿意为了“永远记得”把未经筛选的历史都交给模型。连续体验与连续上下文,本来就是两件事。
演练中,模型曾经对记账请求直接回复文字,没有调用工具。更糟的是,系统把这个直接回答包装成 state=succeeded 的操作结果,又作为成功历史喂回模型。
模型没有从历史中看到“上次漏调工具”,而是看到“上次这样回答成功了”。几轮之后,同一个模型、同一句“咖啡 18 个人支出”,在干净会话里会调用工具,在污染会话里却直接声称已经记录。
这次事故迫使我把几种容易混在一起的东西拆开:
- 用户时间线:用户看到的连续经历;
- 模型会话:一次任务与对话历史的语义边界;
- 模型上下文:某次调用真正可见的内容投影;
- 长期记忆:跨会话保留、经过筛选的稳定偏好;
- 结构化事实:金额、外部记录 ID、幂等键、回执、审批与失败原因。
结构化事实不能因为上下文压缩而被改写成自然语言摘要。同一个事件也可以永久留在审计中、显示给用户,却不进入模型;即使进入模型,也必须携带正确的失败语义。
我的修复不是再写一段更强的 prompt,而是关闭污染会话,并增加确定性门禁:只要输入同时具有金额与记账语义,却没有发生写工具调用,直接回答就不能被标为成功。
这个选择要求我自己维护会话边界、上下文投影和失败语义,成本显然高于保存完整聊天记录。但事故证明,如果没有这层治理,错误不仅会留在历史里,还会被下一轮模型当成示例继续放大。我要的长期记忆不是“保留最多”,而是“只保留有资格影响未来决策的内容”。
这一层的结论:事件日志是事实源,不是 prompt。上下文是一种受治理的投影;没有投影规则的“长期记忆”,很容易只是长期污染。
第五层:权限与人机协作——把人放进回路,还要设计出口
通用 Harness 已经能处理很多动作前权限:命令是否进入沙箱、文件是否可写、网络是否可用、何时弹出审批、拒绝后任务怎样继续。Codex App Server 还把审批暂停表达成跨客户端协议事件,DSH 则把审批与权限作为可组合能力。
但真实业务系统还要处理动作发生之后的人机协作。
真机验收时,一笔账的首次提交被打断,Agent 很快进入 needs_manual_review。4 秒后,Finance 已经用同一幂等身份完成对账,确认只写入一次,回执也验证通过。但 Agent 状态已经冻结,iPhone 上只有“请人工核对”:没有操作按钮,没有结论写回接口,当时甚至没有打开飞书账本的深链。
我把人拉进了回路,却没有设计他如何离开。
修复时同样有几条看起来合理的路:给用户一个自由文本备注;增加“确认成功/确认失败”两个按钮并直接改写状态;或者把人的观察作为一条独立事实,留在机器状态旁边。前两种实现快,但会让人最后一次点击覆盖系统此前证明过的内容。
我选择第三种。这里的原则不是“不相信人”,而是人和机器拥有不同的证据能力:机器可以证明自己读到了哪个回执,人可以证明自己此刻在账本里看见了什么。两种证据可能冲突,但任何一方都不应该悄悄消灭另一方。
这件事让我把 Human-in-the-loop 至少拆成四种角色:
- 批准:动作发生前,决定允许或拒绝;
- 仲裁:两条系统证据冲突时,选择下一步;
- 补证:系统无法读取事实源时,人记录自己看见了什么;
- 纠错:动作已经完成,但展示或分类需要追加修正。
这四种角色不能共享一个“人工确认”布尔值。人的观察也不能覆盖机器证据。最终我让 manual_resolution 与系统状态并存:机器记录自己证明了什么,人只记录“账本里有”或“账本里没有”;同一个人之后给出相反结论,系统拒绝静默覆盖,因为冲突本身就是新事实。
为了让这个决定可执行,我又给它加了约束:人工结论只能取闭集值;只有真正停在人工核对的操作才能写入;第二个相反结论不能覆盖第一个;纠正分类这种确定性动作可以由 UI 执行,但执行入口继续对模型隐藏。
后来做分类纠正时,我又进一步区分“人可以执行”与“模型可以执行”。iOS 上的确定性选择器可以追加一条经过验证的时间线事实,让纠正结果在重启后重放;但这个执行能力仍然对模型隐藏。
代价是状态不会因为人点了一次按钮就变得“干净”。冲突会继续存在,UI 要解释机器结论与人工观察的区别,恢复逻辑也要知道人工结论能影响什么、不能影响什么。但保留矛盾比制造一个虚假的统一答案更诚实,也更可追责。
这一层的结论:不要只问“有没有人在回路里”,要问人在副作用边界的哪一侧、以什么身份出现、他的结论写入哪一种事实。
第六层:证据与恢复——完成不能只靠系统自报
如果只看本地开发数据,Personal Agent 一期似乎很扎实:从 7 月 23 日到 8 月 7 日,201 个 commit、1995 项自动化测试、38 份 evidence 文档。Google ADK、Finance MCP、iOS、ECS、推送、备份与恢复都做了。
但真正改变我判断的,不是这些数字,而是它们一次次不够用。
一期开发过程中,我需要决定到底用什么宣布“完成”。最宽松的标准是测试通过;更接近生产的标准是服务部署成功、接口返回 200;我最终采用的是第三种:不同层级分别提供证据,最后由业务事实源闭环。原因很简单——前三次“完成”都可能是真的,却仍然没有证明用户要的那件事发生了。
两次独立审查都在绿灯背后找出六个 blocker。真实备份部署时,暂存目录、setgid、UMask=0077 和硬编码 0600 组合在一起,导致备份用户可以列出文件却无法读取;原来的验证脚本只检查目录是否可见,没有真的以备份用户身份读一个文件,所以 55 项检查仍然全绿。
第一次异机恢复也“成功”了,后来审查才发现所谓只读探针组合了正常 Finance 运行时,理论上仍能接触写路径与飞书能力。修复后,备份账号退出两个服务组,恢复检查改成专用只读入口,验证项从 55 条增加到 66 条,再在另一台 Mac 上重新跑完整流程。
最终验收同样不是“后端接口返回 200”。真实链路是:
真实 iPhone → 公网 HTTPS → ECS → Agent → Finance MCP → 真实飞书账本 → 写后回读 → 手机状态回写。
推送验收里,Apple 接受通知不是结束;17 秒后真机写回 reviewed_at,才说明通知到达、用户操作和服务端状态形成闭环。即使如此,那次验收仍暴露了 needs_manual_review 的死胡同。
因此我把完成证据分成六层:
- 本地测试覆盖已经枚举的离线路径;
- 安装产物说明用户运行的是被验证过的代码;
- 服务证据说明真实部署组合存在;
- 设备证据说明真机链路可用;
- 外部回执说明真实副作用发生,并与原操作绑定;
- 恢复演练说明未知状态和进程重启后仍能收敛。
这六层不是检查项越多越好,而是每一层回答一个不同问题:代码是否覆盖已知路径、运行的是否是那份代码、真实服务是否按预期组合、设备是否真的走完整条链、外部动作是否与原请求绑定、系统坏掉以后是否还能回来。上一层不能替下一层签字。
独立审查横跨这些证据层。它不能证明系统正确,只能降低实现与验证共享同一错误假设的风险。
这个决定最昂贵。它意味着一次本地绿灯之后,我仍可能因为真机、备份或恢复失败而推翻“已经完成”;也意味着验收会受到设备、网络和外部服务限制,无法像单元测试一样随时重跑。但如果产品连接了真实账本,我宁可让进度被证据推翻,也不愿让一份漂亮的测试报告替真实世界签字。
通用 Harness 可以提供持久化、恢复、沙箱和事件机制,但无法替我的产品定义“什么证据足以完成一笔账”“一次外部成功由谁签发”“恢复后必须验证哪些业务关系”。这些是领域完成合同。
Personal Agent 仍然不解决多租户平台的全部攻击面,不拥有 Codex 的通用沙箱,也没有 Claude Code 的成熟交互。但在 Finance 这条真正接入的链路里,它必须把“完成”证明到该领域的权威事实源,而不是让 Agent 自己宣布成功。
这一层的结论:本地绿灯只能说明代码满足了已知假设;真实完成需要由独立于执行自报的权威证据闭环。
六层之后,我如何重新理解这些公共 Harness
重新放回这六层,Claude Code、Codex、DSH 和 Personal Agent 不再是一张竞品表,而是处在不同抽象层的系统。
Claude Code 的泄露快照让我看到,把 Agent 做成每天可用的完整产品,需要多厚的产品工程。但泄露源码不是规范,看见约 50 万行代码,不代表我的系统应该继承所有复杂度。
DSH 让我看到,上层产品不必再造一个全能 Agent,也可以在稳定的能力接口上替换模型、工具、存储和沙箱实现。
更晚公开的 Codex Harness 与 App Server 让我看到,一套高质量通用 Harness 可以成为跨界面的公共运行时。未来如果它能替我承担更多模型循环、流式事件和会话基础设施,我应该乐于删除自己的重复实现。
Personal Agent 要继续回答的是另一组问题:模型何时不得调用工具,外部写入何时才算成功,失败历史怎样不污染下一轮,人怎样在副作用之后补证,最终由哪个事实源宣布完成。
它们之间有两种主要关系:
- 公共能力优先复用:模型循环、会话、工具接入、沙箱、审批和持久化机制,成熟方案优先;
- 领域责任必须自定义:幂等、权威事实源、外部回执、人工补证、恢复与完成标准,由具体产品负责。
所以 DSH 与 Codex Harness 相继公开之后,我不再问“还要不要自己做 Harness”,而会问六个更具体的问题:
- 这一层已经是稳定的公共能力了吗?
- 它的生命周期是模型回合,还是跨回合的业务操作?
- 这个工具允许造成的最小业务后果是什么?
- 这条事实应该进入审计、UI、模型上下文,还是长期记忆?
- 人在动作前批准,还是在动作后补证?
- 最终完成由哪个领域权威签字?
这六个问题现在也构成了我扩展 Personal Agent 的决策顺序:先找事实源,再画副作用边界;先定义最小动作和失败方向,再决定模型可见性;最后补上人的角色与完成证据。模型、框架和工具选型都排在这些产品合同之后。
公共 Harness 已经稳定提供的能力,我没有必要重复维护。Personal Agent 真正需要保留的,是与个人数据和真实行动直接相关的产品合同。
模型可以提出下一步行动,公共 Harness 可以提供可靠执行所需的通用机制。
但一件事到底算不算做完,仍然是产品必须亲自承担的判断。
参考资料
- Claude Code source map 泄露事件的公开报道(Axios)(约 50 万行代码、近 2000 个文件;报道同时说明没有客户数据或凭据暴露)
- Unrolling the Codex agent loop — OpenAI(Codex Harness 的 agent loop、上下文与工具组装)
- Unlocking the Codex harness: how we built the App Server — OpenAI(共享 Harness、App Server,以及 Item、Turn、Thread 协议)
- openai/codex(Codex CLI 与 Rust 核心开源仓库)
- deepseek-ai/deepseek-harness(DeepSeek Harness 开源仓库)
- DeepSeek Harness Architecture(Cordis 插件树、会话与基础能力组合)
- DSH capability seams(能力接口、实现与使用方之间的依赖关系)
文中 Personal Agent 的过程、事故与数字,来自项目当前状态、一期 Vibe Coding 复盘、真机验收与记账恢复记录,以及 Agent Eval 方法论。