如果只看最近几个热点,Agent Harness 像是突然从幕后走到了台前。

Claude Code 的一次 source map 泄露,公开报道里出现了约 50 万行代码、近 2000 个文件。人们没有看到一个可以复制的神奇 prompt,看到的是一个成熟 Coding Agent 背后的产品工程。

DeepSeek 先开源了 DSH,把模型、工具、Session、沙箱、压缩和交互做成可替换、可组合的插件。

更晚公开的 Codex Harness 走了另一条路。OpenAI 解释了共享 agent loopApp 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

如果把它当成普通工具失败,最自然的做法是重试,或者让用户再试一次。但网络结果未知,不等于外部写入没有发生;两种处理都可能制造重复账。

摆在我面前的其实是三个产品选项:

  1. 自动重试:用户最快得到结果,但最容易重复写入;
  2. 直接报失败,让用户重发:系统实现最简单,却把重复风险转嫁给用户;
  3. 保留“结果未知”并继续对账:反馈更慢,状态更多,但不会把未知伪装成失败或成功。

我选择第三个。这里的价值排序很明确:对记账来说,正确性优先于即时反馈,可恢复的等待优先于一个看起来干脆的错误结论。

最后我把一次写操作拆成更长的状态链:

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 至少拆成四种角色:

  1. 批准:动作发生前,决定允许或拒绝;
  2. 仲裁:两条系统证据冲突时,选择下一步;
  3. 补证:系统无法读取事实源时,人记录自己看见了什么;
  4. 纠错:动作已经完成,但展示或分类需要追加修正。

这四种角色不能共享一个“人工确认”布尔值。人的观察也不能覆盖机器证据。最终我让 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 的死胡同。

因此我把完成证据分成六层:

  1. 本地测试覆盖已经枚举的离线路径;
  2. 安装产物说明用户运行的是被验证过的代码;
  3. 服务证据说明真实部署组合存在;
  4. 设备证据说明真机链路可用;
  5. 外部回执说明真实副作用发生,并与原操作绑定;
  6. 恢复演练说明未知状态和进程重启后仍能收敛。

这六层不是检查项越多越好,而是每一层回答一个不同问题:代码是否覆盖已知路径、运行的是否是那份代码、真实服务是否按预期组合、设备是否真的走完整条链、外部动作是否与原请求绑定、系统坏掉以后是否还能回来。上一层不能替下一层签字。

独立审查横跨这些证据层。它不能证明系统正确,只能降低实现与验证共享同一错误假设的风险。

这个决定最昂贵。它意味着一次本地绿灯之后,我仍可能因为真机、备份或恢复失败而推翻“已经完成”;也意味着验收会受到设备、网络和外部服务限制,无法像单元测试一样随时重跑。但如果产品连接了真实账本,我宁可让进度被证据推翻,也不愿让一份漂亮的测试报告替真实世界签字。

通用 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”,而会问六个更具体的问题:

  1. 这一层已经是稳定的公共能力了吗?
  2. 它的生命周期是模型回合,还是跨回合的业务操作?
  3. 这个工具允许造成的最小业务后果是什么?
  4. 这条事实应该进入审计、UI、模型上下文,还是长期记忆?
  5. 人在动作前批准,还是在动作后补证?
  6. 最终完成由哪个领域权威签字?

这六个问题现在也构成了我扩展 Personal Agent 的决策顺序:先找事实源,再画副作用边界;先定义最小动作和失败方向,再决定模型可见性;最后补上人的角色与完成证据。模型、框架和工具选型都排在这些产品合同之后。

公共 Harness 已经稳定提供的能力,我没有必要重复维护。Personal Agent 真正需要保留的,是与个人数据和真实行动直接相关的产品合同。

模型可以提出下一步行动,公共 Harness 可以提供可靠执行所需的通用机制。

但一件事到底算不算做完,仍然是产品必须亲自承担的判断。


参考资料

文中 Personal Agent 的过程、事故与数字,来自项目当前状态、一期 Vibe Coding 复盘、真机验收与记账恢复记录,以及 Agent Eval 方法论。