DeepSeek Harness 的 Cordis 插件架构

DeepSeek Harness 把模型、工具、会话、沙箱、文件系统、Agent 循环、调度和 UI 都交给插件。

系统没有一块承载业务能力的固定内核,Cordis 只维护上下文、服务注册、事件分发、依赖激活和副作用回收;Agent 能做什么,由启动时挂入的一棵插件树决定。

传统的插件有注册容易,撤销困难;初始化容易,失败回滚困难;全局单例容易,局部覆盖困难等问题,而 Cordis 把这些困难收进了运行时语义,Harness 再用 session、agent 和 preset 的业务约束补齐它。

今天聊了一下 DeepSeek Harness 的核心 Cordis 架构以及 Cordis 所谓「时空可组合」,究竟怎样落到代码里,又为何适合 Agent Harness 这类持续变化的运行时。

插件的历史

插件架构的历史并不短。Eclipse 在二十多年前已经把 IDE 拆成插件、扩展点和扩展,宿主声明可扩展位置,其他插件通过清单贡献菜单、编辑器和处理逻辑。

OSGi 更进一步,给 bundle 配置安装、启动、停止、更新和卸载生命周期,再通过共享服务注册表完成发布、查找和绑定。服务离开注册表时,依赖方必须跟着处理动态变化。

今天看 Cordis,能找到这些设计的清晰痕迹:动态服务、生命周期、注册表、声明式依赖、事件通知都已有成熟先例。

Cordis 没有发明插件系统。

它简化并重新组合了这些概念,让它们适合 TypeScript 应用内的细粒度组装。OSGi 的部署单元是 bundle,模块、生命周期、服务和安全各有一层;Cordis 的执行单元是 Fiber,一个函数或一个 Service 子类就能成为插件。Eclipse 的扩展点通常由宿主定义结构化清单,Cordis 让服务和类型化事件直接成为扩展面。Agent 运行时里的工具、提示词片段、模型适配器和审批策略变化频繁,如果每次扩展都要引入重量级模块边界,团队很快会绕过框架,重新写回几个全局数组。

Cordis 官方仓库把自己定位为「Meta-Framework of Spatiotemporal Composability」。

从源码看,「元框架」表示它不规定 Agent、Web 服务或机器人该有什么组件,只提供构造框架所需的基础语义。DeepSeek Harness 在其上定义 sessionstoolsllmagents 等服务,又用这些服务组装产品。Cordis 与 Harness 的关系更接近运行时机制和领域框架,而非通用插件平台与插件集合。

DeepSeek Harness 还把 Cordis 源码直接放进 vendor/,固定在明确的上游提交,并将包名重映射到 @deepseek-ai 命名空间。

项目维护的本地修改包含 Fiber 重入卸载加固、配置更新事务、HMR 精确监听、延迟配置解析等。这增加了维护成本,也换来了框架层的可审计性。Agent 能执行 Shell、修改文件、访问网络,插件生命周期出错会留下进程、监听器、终端模式或权限状态。

此时依赖一个本地的代码白盒,会让人放心一些。

调用链路图

简单的调用图如下所示:

最小内核

Cordis 的根对象是 Context。它同时承担依赖容器、插件挂载入口和事件入口。

服务通过稳定名称出现在 ctx 上,例如 ctx.toolsctx.llmctx.sessions。插件声明 inject 后,Cordis 只有在所需服务可用时才激活它;服务消失,依赖插件也会进入卸载或等待状态。配置文件中条目的先后顺序因此不承担启动顺序,依赖关系才承担。

这种处理解决了传统插件系统的第一个顽疾:隐含初始化顺序。

常见实现会遍历插件数组,依次调用 init。当工具依赖文件系统、文件系统依赖沙箱、UI 又依赖会话时,数组顺序就变成一套没有类型、没有诊断的依赖图。后来插入一个插件,顺序约束可能跨越几十个文件。

Cordis 把需求写进 inject,Fiber 会为每项依赖保存当前实现;缺少依赖时保持 PENDING,依赖齐备后进入 LOADINGACTIVE。启动失败则进入 FAILED。状态机至少让故障有了准确位置。

Context 还是一个代理对象。

插件直接读取 ctx.tools 时,反射层会检查它是否声明过依赖,并沿 Fiber 父链解析实现。未声明就读取会抛错;声明了但当前上下文不可用,也会抛出另一类错误。这种约束防止依赖藏在任意函数深处。源码里依然提供 ctx.get(name) 读取可选服务,区别在于调用方显式接受服务可能不存在。

这里的「服务定义、服务提供方、消费方」三种角色被完整建模。比如文件系统能力不能只写一个接口,也不能只挂一个本地实现。定义方稳定调用协议,提供方接入本地目录或远程沙箱,消费方把能力变成模型可见工具。Harness 把这组关系称为 capability seam。替换沙箱时,Shell、PTY、LSP 只要依赖同一能力面,就能整体迁移到新的执行环境,消费方无需知道提供方运行在本机还是远端。

「一切皆插件」有一个前提是:一切产品能力皆由插件贡献,Cordis 自身仍保留插件得以存在的机制。上下文代理、Fiber 状态机、服务存储、事件总线和 Loader 属于元层。

这并不是系统里没有内核,更准确的说,应该是,内核不拥有模型、工具和循环等产品特权。

空间组合

同一个进程里,服务名称必须稳定,实例又不能全局唯一。两个 Agent 可能选择不同模型、不同工具集、不同 persona 和不同沙箱。如果 ctx.llm 永远指向一个全局对象,插件替换只发生在进程级,无法满足多会话并存。

Cordis 的 Context.isolate 为指定服务名创建一个 realm 标签。服务注册和查找都以该标签定位,同名服务可以在不同子上下文里各自存在。两个 isolate 调用传入同一标签时会加入同一 realm;使用新标签时彼此隔离。子上下文通过原型继承父上下文,隔离映射按层遮蔽,因此局部配置不需要复制整棵容器。

这解释了「空间可组合」的第一层:组合具有位置。插件挂在哪个上下文,决定它提供的服务、注册的监听器和持有的副作用在哪个范围可见。传统依赖注入容器也有 singleton、request、session 等 scope,Cordis 的差异在于作用域与插件树、事件过滤、资源所有权使用同一个 Context 表达。开发者无需在四套 API 之间同步身份。

DeepSeek Harness 又增加了 dsh-scope。它用不透明对象作为 scope key,维护父子关系,并创建带路由身份的事件 receiver。注册视图沿父链向下继承:Agent 能看到所属 preset 的提示词和工具。事件则沿链向上接纳:preset 级监听器能收到其下 Agent 的事件,兄弟 Agent 互不串线。这是第二层空间结构,解决的是领域身份,而非单个服务名的 realm。

为何需要两层?isolate 处理「哪个 tools 服务实例」,dsh-scope 处理「同一工具注册表里,哪些注册项对当前 Agent 可见」。前者适合替换提供方,后者适合对注册内容分层。把所有差异都做成独立服务实例,会放大内存和初始化成本;把所有差异都塞进一个全局注册表,又会让过滤规则散落在调用点。这两层模型把实例隔离和内容路由分开了。

Agent preset 是空间组合的完整应用。一个 preset 的 Cordis 配置会挂在一个长期存在的 scope 下,Agent 创建时把自己的 scope key 绑定到该 preset。相同 preset 的并发首次使用通过 single-flight 共享一次挂载,后续 Agent 复用这份组合。文件变化后,新会话加入新一代组合,已有会话保留原来的代际。这个策略避免会话运行中途突然更换工具或提示词,代价是旧代际要保留到整棵运行时退出,配置频繁变化时内存会按代际增长。

源码还专门审计 preset 子树是否把服务发布到了 root realm。发生这种泄漏时,第二个会话挂载同一 preset 会与第一个冲突,所谓会话级组合也会退化成进程全局状态。DeepSeek Harness 在发布 Agent 前拒绝这种配置。空间隔离若只靠约定,迟早会被一个没有 isolate 的 provider 穿透;运行时审计比文档警告可靠。

这套空间模型存在认知成本。插件作者要同时理解 Context 父链、服务 realm、业务 scope 父链和事件过滤方向(当然,在当前 Vibe Coding 盛行的时代,也可以作者不理解,直接让 AI 来搞)。

在认知不清楚的时候,常见的错误往往表现为某项能力「看不见」或意外泄漏,类型系统无法证明运行时挂载位置。DeepSeek Harness 用 preset 挂载审计、包级 invariant 和真实组合测试降低风险,却没有消除模型复杂度。团队若只需要单进程单 Agent,直接引入整套空间语义会显得过重。

时间组合

插件能挂载只是静态组合。运行期间服务上线、配置改变、插件失败或上下文销毁,系统还要回到一致状态。Cordis 用 Fiber 和 effect 管理这条时间轴。

每次插件应用都会产生一个 Fiber。Fiber 记录父上下文、原始配置、解析后配置、依赖实现快照、生命周期状态和 disposables。插件调用 ctx.effect() 注册副作用,effect 的执行结果返回 disposer。事件监听、服务提供、子插件和访问器最终都进入这套所有权体系。Fiber 卸载时按注册的逆序执行清理,并等待异步清理达到静止状态。

逆序本身不是一个特别要讲的事情,因为这是必须的。

若插件先启动子进程,再注册输出监听,最后暴露服务,销毁时应先撤销服务,停止新请求,再移除监听,最后结束进程。资源创建顺序的反向通常就是依赖安全的拆卸顺序。

传统插件常给出一个 deactivate() 钩子,把所有清理责任推给作者;漏掉一个定时器或事件监听,热加载几次便出现重复执行。Cordis 让每次注册同时产生撤销动作,框架持有所有权。

DeepSeek Harness 的 vendor 版本进一步处理了重入场景:effect 在执行 setup 前先登记所有者包装;插件发布事件时,观察者可能同步卸载它;异步 cleanup 已经开始后,其他调用者仍能等待同一次清理;Fiber 处于 UNLOADING 时拒绝创建新 effect。这些代码看起来全是繁琐的边界处理,但为了保证生命周期并发的安全,一个都不能少。否则插件的热卸载就只是个玩具,没法真正落地。

依赖变化也属于时间组合。某个服务被提供后,反射层通知所有声明该依赖的 Fiber 重新检查;条件满足便激活。服务撤销后,依赖方会卸载并回到等待。OSGi 早已采用动态服务注册表,Cordis 的新意主要在于把动态依赖、作用域上下文和 effect 所有权压进一个很小的进程内模型。代价同样继承自 OSGi:任何持有服务引用越过生命周期的代码,都可能在提供方撤销后继续调用过期对象。Cordis 保存加载时的实现快照,却无法替业务代码管理逃逸引用。

Loader 把时间组合延伸到配置。DeepSeek Harness 的 profile 由多层 bundle patch 叠加:基础 bundle、模式 bundle、profile patch、home patch、命令行 overlay。patch 按 id 定位条目,配置采用整块替换。整块替换要求用户重述保留字段,使用上稍显笨重,却避免深度合并规则在数组、表达式和删除语义上制造歧义。

配置热更新时,Loader 先导入候选插件,再卸载旧实例并应用候选;候选失败会恢复旧插件或旧配置。Group 对一批子条目并发启动,收集全部结果,只要一项失败就删除新增项并重建旧配置。Include 读取候选文件、在副本上应用 patch、完成树协调后才提交缓存。用户 patch 语法错误或新插件启动失败时,最后一棵可用树继续运行。

这已经接近配置事务,却不能等同于数据库事务。

插件 effect 可能调用外部 API、创建远端资源、发送消息;disposer 只能做补偿,无法保证外部世界回滚。Loader 能保证自己管理的树和服务注册恢复,无法撤回所有不可逆副作用。插件开发规范仍要限制初始化期行为,把外部写入延迟到真正的业务请求,或者设计幂等键和补偿路径。

时间组合还带来可观的测试面积。挂载成功只是第一条路径,还要覆盖依赖晚到、依赖撤销、初始化失败、卸载重入、异步清理、热更新失败、回滚再次失败。DeepSeek Harness 要求注册项证明 disposal,产品可见插件还要通过真实 Loader 组合测试。这个成本无法靠框架消失,只能被框架集中暴露。相比线上出现幽灵监听器和半更新状态,还是愿意支付这部分测试费用。

事件契约

插件之间只靠服务调用,会把所有扩展都变成接口方法。宿主每增加一项策略,就要修改服务定义。Cordis 同时提供类型化事件,并区分 emitparallelserialbailwaterfall

DeepSeek Harness 最依赖的是 waterfall。它的监听器拿到 next,调用后把控制权交给下一层,不调用就截断链条。模型请求、工具执行和轮次控制都能被插件包裹。审批策略可以在工具执行前拒绝,重试插件可以包围模型流,日志插件可以观察前后状态。它与 Koa 一类中间件链相似,但事件名和 Context 过滤让同一个分发器覆盖多个能力域。

waterfall 也最容易出错。一个只想记录日志的监听器忘记调用 next(),整条能力链便被短路。DeepSeek Harness 把「必须调用 next()」写进项目级规则,并在事件文档里记录 dispatch mode。类型能保证参数,却很难保证 continuation 一定执行。代码评审和组合测试仍是主要防线。

事件的持久性也被刻意分层。agent/*tools/* 事件用于活跃运行时的拦截;会话事件追加到日志,承担恢复、fork、UI 回放和模型历史投影。DeepSeek Harness 规定「模型可见即已记录」:进入模型请求的输入必须能从 session log 重建。插件若偷偷修改 prompt 却不产生会话事实,重放结果会漂移,问题也无法审计。

这条约束说明,一切皆插件并不意味着一切皆事件。直接能力调用放进 Service,策略和拦截放进实时事件,需要持久化的事实进入 session log。三类通信各自承担调用、扩展和历史。将它们混成一个全局 event bus,短期代码更少,长期会失去时序语义和数据权威。

Agent 适配

Agent Harness 比普通 Web 服务更需要动态组合。模型供应商会变,工具权限随工作区变化,子 Agent 的执行环境可能与父 Agent 不同,UI 还要消费同一份流式过程。如果主循环直接 import 每个能力,任何替换都会改循环;循环逐渐成为依赖最多、风险最高的文件。

DeepSeek Harness 的默认 agent loop 仍然存在,但它也是一个服务提供方。循环读取 session log 生成历史,组装插件贡献的 prompt 和 tool schema,通过 agent/request 进入模型适配器,再经过工具流水线记录结果。扩展点围绕步骤、请求、工具和停止阶段布置。新功能通常挂到这些事件或注册表,项目规则甚至要求修改 agent loop 时同步更新架构文档。

我赞成这种限制。循环承担控制流,频繁承载产品策略后会迅速腐化。压缩上下文、重试、审批、工具超时、计划模式、子 Agent 调度都可以有自己的生命周期与配置。它们进入循环的接口必须少且稳定。插件化不会自动获得解耦,真正起作用的是 DeepSeek Harness 为能力选择了明确的 seam,并拒绝消费方特有逻辑污染定义层。

UI 作为插件也有实际意义。Web 和 headless profile 在共享 base bundle 上增加不同条目,后者可以完全不启动服务器。UI 驱动 ctx.agents,订阅 session/event 渲染状态,没必要成为循环的一部分。服务端、CLI、ACP 和浏览器界面可以共享会话及 Agent 语义,同时保留自己的传输和展示逻辑。

插件树还提供自省基础。Fiber 保留名称、状态、依赖和 effect 元数据,DeepSeek Harness 能实现查看、挂载、卸载自身插件的能力。对 Agent 而言,这比普通应用更敏感:模型可以通过工具改变运行时,错误配置可能直接扩大权限。源码中的 sandbox 和 approval 仍是独立能力,插件架构没有天然安全性。自修改必须受工具权限、配置校验和作用域审计约束。

架构代价

第一项代价是启动与运行时开销。每个插件产生 Fiber,服务访问经过 Proxy 和反射解析,事件分发要执行作用域过滤,注册项还要保存 disposer 与诊断元数据。对于 LLM Agent,单次模型请求通常以百毫秒到秒计,这些 JavaScript 调度开销很难成为主要瓶颈;高频 token chunk、文件扫描或终端字节流若全部穿过通用事件总线,成本会被放大。DeepSeek Harness 把流式模型输出定义为能力语义,但大量数据处理仍应留在具体 provider 内,事件只承载必要扩展点。

第二项代价是故障面扩大。插件初始化可以同步抛错、异步拒绝、等待缺失服务,也可能在 disposer 中失败。Group 并发启动缩短时间,却会带来多个兄弟同时失败的 AggregateError。DeepSeek Harness 的 boot 会等待 Loader 结算,审计所有启用条目是否加载与激活,失败时先 dispose 部分构造的上下文,再退出。少做任何一步,都可能让终端停留在 raw mode,或者让后台 watcher 继续运行。

第三项代价是配置成为编程接口。cordis.yml 支持 !!js 表达式,条目可以按环境禁用,配置会在依赖激活后求值。灵活性很高,静态分析能力随之下降。表达式读取服务时,求值时机与上下文位置都会影响结果。DeepSeek Harness 只允许 configdisabled 使用插值,其他元数据保持字面量,并让错误尽早暴露。这仍要求运维人员理解插件依赖和 patch 覆盖语义,配置文件已经超出普通 YAML 参数表的复杂度。

第四项代价是生态兼容。Cordis 的服务名和事件类型提供了源码级协议,插件版本升级仍可能修改配置、事件 payload 或生命周期假设。DeepSeek Harness 目前处于预发布阶段,仓库规则允许直接拒绝旧磁盘格式,也没有承诺 session 格式兼容。现在的可组合性主要服务于同一发行版内的替换和扩展,尚不能推导出跨版本插件 ABI 稳定。

第五项代价是组织治理。一切都能成为插件后,团队容易把每段十几行逻辑都拆成包,形成依赖图膨胀、文档分散和测试启动缓慢。DeepSeek Harness 用 package 分组、Service Definition/Provider/Consumer 角色、每包 README、运行时 invariant 和真实组合测试控制边界。这些规范本身就是成本。小团队若没有维护扩展生态或多 profile 的需求,模块化函数加显式依赖可能更合适。

历史对照

从 OSGi 看 Cordis,动态服务和生命周期联动已有先例。服务注册、发现、撤销后触发依赖变化,这条主线几乎一致。Cordis 值得学习的新点,是把资源清理统一成 effect,并让子插件、服务、监听器都归属于同一个 Fiber。OSGi 的 bundle 生命周期更完整,也更重;Cordis 选择应用内细粒度对象,失去了类加载隔离、标准版本解析和安全层,获得了低门槛组合。

从 Eclipse 看 DeepSeek Harness,profile 与 bundle patch 很像部署时组装,Service 和事件类似扩展点。差异在于 Eclipse 扩展通常围绕稳定宿主能力,DeepSeek Harness 连 agent loop 和 UI 都可替换,核心插件与第三方插件共享同一挂载机制。这个平权减少了特权内核,风险也更集中到协议治理:循环能被替换,不代表任何替代循环都遵守 session log、权限和工具时序约束。

从依赖注入容器看,Cordis 的服务解析并不陌生。新的组合来自 DI 与生命周期的绑定。普通容器负责构造对象,定时器、监听器和子进程仍由业务代码清理;Cordis 把注册动作收敛成 effect。空间 scope 与时间 ownership 同时存在,插件才能在某个 Agent 范围内挂载,并随该 Agent 完整撤销。

从微内核看,DeepSeek Harness 的内核确实很轻,但其 Loader 和配置协调已经承担不少平台职责。它要解析模块、等待依赖、处理 HMR、执行事务式更新、保存最后可用树、输出诊断。这提醒我,微内核减少的是业务特权,不会减少生命周期复杂度。能力越动态,内核对失败语义的要求越高。

Cordis 的贡献可以概括为一种紧凑的组合坐标:Context 决定插件位于哪里,Fiber 决定插件在何时有效,Service 负责直接能力,Event 负责横切协作,effect 负责撤销。DeepSeek Harness 在这个坐标系上加入 Agent scope、持久会话事件和配置事务,使其能承载多会话、多 profile 与热更新。

工程取舍

当我想要用类似架构时,会先确认三个条件,基于这三个条件判断来是否采用。

其一,能力确实需要独立替换,至少存在两个生产提供方或明确的外部扩展需求。

其二,同一进程需要多套组合并存,或者运行期间需要可靠更新。

其三,团队愿意为卸载、回滚和真实组合测试持续付费。

缺少这些条件,插件框架容易沦为复杂的工厂模式。

能力切分要从消费方开始。先列出谁调用、调用时需要哪些稳定语义,再设计 Service Definition;随后实现 provider,最后把面向模型的 schema、展示和错误文本留给 consumer。接口只有一个内部调用者时,保留私有闭包,不急着升格为公共 service。可替换性必须由真实替代者证明。

所有注册都要有明确所有者。注册工具、提示词片段、适配器、监听器时同步返回 disposer;卸载测试要观察注册项确实消失。涉及异步资源时,dispose 完成的定义要包含子进程退出、队列排空和 watcher 关闭,不能只发出取消信号。DeepSeek Harness 对 Fiber quiescence 的处理值得照搬。

配置更新要区分内存一致性和外部副作用。Loader 可以恢复旧树,插件初始化若已经创建云资源,回滚需要业务补偿。规则可更保守一些:挂载阶段只做校验和本地注册,外部写操作放到带幂等标识的运行阶段。确实要在初始化创建资源时,必须让 disposer 能识别部分完成状态。

作用域要控制在两种以内。实例隔离与注册可见性已经足够表达多数 Agent 场景,再增加租户、请求、工作区、角色四套独立 scope,调试成本会失控,需要根据实际业务需求和必要性来判断。当需要新增维度时,先判断它属于服务实例选择、注册过滤、持久数据权限还是请求参数。很多所谓新 scope,其实只是一次显式参数传递。

事件也要克制。需要唯一返回值的调用用 Service,需要持久恢复的事实写 session log,需要多个插件观察或包裹的阶段才用事件。waterfall 必须把短路当成公共协议,监听器顺序也要有测试。事件名虽然能降低导入耦合,却会增加时序耦合;后者通常更难排查。

小结

DeepSeek Harness 选择 Cordis,解决的主要矛盾是 Agent 能力变化速度与运行时一致性之间的冲突。模型、工具、沙箱和 UI 都可以替换并不稀奇;这些能力可以在局部作用域内共存,能随依赖变化启停,能在配置失败后保留旧树,卸载时还能回收监听器、服务和子插件,才构成可用的插件架构。

它也没有抹平工程风险。Proxy 隐藏了查找路径,双层 scope 增加理解成本,热更新无法回滚任意外部副作用,预发布阶段也缺少跨版本兼容承诺。团队采用这套思路时,应复制它的生命周期纪律和能力边界,别只复制「一切皆插件」的目录结构。

源码给我们一些启发,把插件设计的评审顺序倒过来:先问如何撤销,再问如何注册;先证明局部挂载不会泄漏,再讨论全局复用;先定义失败后保留哪一棵树,再讨论热更新速度。做到这些,时空可组合才是一组可以验证的运行时语义。

这套架构个人理解是想往 Agent 的实时「自生长」,通过 AI 的能力在使用过程中让自己更强大。

逼逼这么多,主要还是在这个过程中学习一下,说实话,有了 DeepSeek Harness,想自己开发一个专属的 Agent ,快捷方便了很多,eg且这是 MIT 协议的。

以上。

参考资料

 

从 Pi 到 DSH:Agent Harness 如何从「可扩展」走向「自生长」

大模型能力持续提升之后,Agent 系统的竞争焦点正在发生变化。

过去,我们主要关心模型能不能理解任务、调用工具、编写代码。现在,更重要的问题逐渐变成:模型之外的系统,能否为 Agent 提供一个可以持续变化的运行环境?

这个运行环境通常被称为 Agent Harness

Harness 负责把模型、提示词、工具、记忆、上下文、权限和外部环境连接起来。它决定模型能够看到什么、调用什么、保存什么,以及一次行动会产生怎样的系统影响。

从这个角度看,Agent 的能力并不只来自模型,用一个公式来表达,大概如下:

Agent 能力
=
模型能力
× 上下文组织
× 工具系统
× 运行时结构
× 生命周期治理

如果 Harness 是固定的,那么即使模型越来越强,Agent 的能力边界仍然主要由开发者预先决定。

如果 Harness 可以扩展,用户和开发者就能不断为 Agent 增加工具、技能和工作流。

但如果我们希望 Agent 进一步具备「自生长」能力,仅仅提供扩展接口还不够。系统还必须允许 Agent:

  1. 发现自己的能力缺口;
  2. 生成或引入候选能力;
  3. 在运行时安全地激活这些能力;
  4. 验证变化是否有效;
  5. 在失败时完整回滚;
  6. 将成功经验沉淀为可复用组件;
  7. 淘汰已经失效或长期无用的能力。

Pi 与 DSH 可以被视为这条演进路径上的两个阶段。

Pi 回答的是:

如何构建一个足够小、足够开放,可以持续扩展的 Agent Harness?

DSH 进一步追问:

如何让 Agent 不只是使用外部提供的扩展,而是安全地参与自身能力结构的形成?

这不是从一种插件格式切换到另一种插件格式,而是 Agent Harness 从开放扩展平台能力生长环境的转变。

1. 什么是 Agent Harness

一个最简单的 Agent,可以被表示为:

Model
  ↓
Prompt
  ↓
Tool Call
  ↓
Environment

模型接收上下文,生成工具调用,再根据工具结果继续推理。

但在真实系统里,Agent 还需要处理更多问题:

  • 当前有哪些工具可用?
  • 工具由谁注册?
  • 不同任务应该使用什么模型?
  • 哪些信息应该进入上下文?
  • 哪些记忆需要跨会话保存?
  • 如何限制文件、网络和进程权限?
  • 工具调用失败后如何恢复?
  • 新能力如何加载?
  • 组件移除后如何清理副作用?
  • 不同组件之间的依赖如何表达?

于是,Agent 的实际结构更接近于:

                ┌─────────────┐
                │    Model    │
                └──────┬──────┘
                       │
                ┌──────▼──────┐
                │ Agent Loop  │
                └──────┬──────┘
                       │
       ┌───────────────┼────────────────┐
       │               │                │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│    Tools    │ │    Memory   │ │   Context   │
└─────────────┘ └─────────────┘ └─────────────┘
       │               │                │
       └───────────────┼────────────────┘
                       │
                ┌──────▼──────┐
                │ Environment │
                └─────────────┘

Harness 并不是模型外面的一层简单包装。

它实际上规定了 Agent 的:

  • 能力边界;
  • 行为边界;
  • 状态边界;
  • 权限边界;
  • 演化边界。

因此,当我们讨论 Agent 是否可以扩展、重构甚至自生长时,本质上讨论的不是模型参数如何变化,而是 Harness 能否安全地改变自身结构

2. Pi:以最小内核换取最大扩展空间

Pi 的核心思想非常明确:

不要预先替用户决定完整工作流,而是提供足够小的基础能力,让用户自己构建需要的 Harness。

Pi 将自己定义为一个最小化的 Agent Harness,并通过 Extensions、Skills、Prompt Templates、Themes 和 Packages 提供扩展能力。它允许用户修改工具、命令、模型提供商、工作流、上下文处理和终端界面,也支持将这些资源打包后通过 npm 或 Git 分发。(pi.dev)

这背后是一种「原语优先」的设计哲学:

不是:
内置所有功能

而是:
提供少量原语
+
开放扩展接口
+
允许用户自行组合

2.1 最小化不是能力不足

传统 Agent 产品通常倾向于内置更多功能:

  • 规划模式;
  • 子 Agent;
  • 权限确认;
  • MCP 集成;
  • 后台任务;
  • 长期记忆;
  • 沙箱;
  • Todo 系统。

Pi 选择了另一条路线:这些能力不一定需要成为内核的一部分,它们可以通过扩展或外部环境实现。

Pi 当前提供文件读取、文件修改、内容搜索和命令执行等内置工具,同时允许通过命令行配置工具集合,也允许 Extension 注册新的工具或覆盖现有工具。(pi.dev)

这种设计的意义把稳定的内核和变化的能力分开:

稳定部分:
模型调用、Agent Loop、会话、基础工具、扩展 API

变化部分:
工具、技能、上下文、工作流、权限策略、界面、模型路由

只要扩展接口足够开放,功能就不必全部固化在核心代码中。

2.2 Pi 的四类扩展资源

从 Harness 的角度看,Pi 的可扩展性主要体现在以下几个层面。

2.2.1 Extensions:改变运行时行为

Extension 是可以进入 Agent 运行时的代码模块。

它可以:

  • 注册工具和命令;
  • 监听工具调用;
  • 拦截或修改行为;
  • 改变上下文;
  • 切换模型;
  • 增加权限检查;
  • 实现自定义 UI;
  • 覆盖内置工具;
  • 连接外部系统。

因此,Extension 改变的不只是模型“知道什么”,还可以直接改变 Harness“如何运行”。

2.2.2 Skills:注入按需能力

Skill 更接近一种可复用的过程知识。

它可以告诉 Agent:

  • 在什么情况下使用某种方法;
  • 如何执行特定工作流;
  • 如何调用外部命令;
  • 如何处理某类项目;
  • 如何检查输出质量。

Pi 将 Skill 设计为按需加载的能力包,使 Agent 不必在每次会话开始时把所有知识都放进上下文。(pi.dev)

2.2.3 Prompt Templates:固化交互模式

Prompt Template 将常见任务封装成可复用提示词,例如:

  • 代码审查;
  • 架构分析;
  • 发布检查;
  • 测试生成;
  • 故障排查。

它改变的是任务入口和交互结构。

2.2.4 Packages:分发完整能力

Package 可以将 Extensions、Skills、Prompt Templates 和 Themes 组合起来,通过 npm、Git 或本地路径安装。Pi 还支持临时加载 Package,使组件只对当前运行生效。(github.com)

这使 Pi 不只是一个 Agent,也成为一个 Harness 能力分发平台:

Pi Package
├── Extensions
├── Skills
├── Prompts
└── Themes

2.3 Pi 的关键突破:Agent 可以参与修改 Harness

Pi 最值得关注的地方,不只是「插件多」。

它的官方描述明确鼓励用户让 Pi 编写自己需要的扩展,并在修改后通过重新加载继续当前工作。(pi.dev)

这意味着一种新的工作方式正在出现:

Agent 发现缺少某项能力
        ↓
Agent 编写 Extension
        ↓
用户检查或确认
        ↓
重新加载 Harness
        ↓
Agent 使用新增能力继续任务

过去,Agent 与开发工具之间的关系通常是:

人类开发 Harness
Agent 使用 Harness

在 Pi 中,它开始变成:

人类定义目标
Agent 修改 Harness
Agent 使用修改后的 Harness

这是从「可扩展」走向「自生长」的重要一步。

但是,这还不等于完整的自生长。

3. 行为自治不等于结构自治

理解 Pi 与 DSH 的关系,需要区分两种不同的自治。

3.1 行为自治

行为自治指 Agent 可以在已有能力范围内自主决定:

  • 下一步做什么;
  • 调用哪个工具;
  • 如何拆解任务;
  • 是否修改文件;
  • 是否运行测试;
  • 如何根据结果调整计划。

此时,Agent 的选择空间是动态的,但能力集合通常是预先确定的。

固定工具集合
    ↓
Agent 自主选择工具
    ↓
完成任务

3.2 结构自治

结构自治指 Agent 可以改变自己的能力结构:

  • 创建新工具;
  • 安装新组件;
  • 替换 Memory;
  • 修改上下文策略;
  • 调整模型 Router;
  • 增加权限规则;
  • 组合新的执行流程;
  • 卸载无效组件。
当前能力结构
    ↓
Agent 发现能力缺口
    ↓
生成候选组件
    ↓
改变 Harness
    ↓
形成新的能力结构

Pi 已经为结构自治打开了入口。

但它的扩展机制主要回答的是:

如何把一项新能力加入系统?

真正的自生长还必须回答:

  • 新能力应该在什么条件下激活?
  • 它依赖哪些已有能力?
  • 它失败后能否完整退出?
  • 它注册的工具和监听器由谁撤销?
  • 如何判断变化带来了正收益?
  • 如何防止不同扩展相互冲突?
  • 如何把一次临时变化沉淀成长期能力?
  • 如何淘汰不再适用的组件?

这些问题将我们从扩展系统带到了组件运行时。

4. DSH:为 Harness 自生长建立运行时基础

本文讨论的 DSH,不只是另一个插件系统。

它试图将 Agent Harness 表达为一个可以在运行中不断组合和重组的组件系统。

其基本结构可以概括为:

Component
=
Context
+
Effect
+
Cleanup
+
Coeffects

这四个概念分别回答四个问题:

概念 回答的问题
Context 组件可以看到和使用什么?
Effect 组件加入系统后会产生什么影响?
Cleanup 组件退出时如何撤销影响?
Coeffects 组件依赖哪些外部能力或其他组件?

DSH 的目标不是让所有模块都能任意修改系统,而是让变化成为一种显式、可追踪、可逆的运行时操作

4.1 Context:为能力提供明确边界

一个组件不能直接访问所有系统状态。

它应该通过 Context 获取所需能力,例如:

Context
├── Model Registry
├── Tool Registry
├── Memory
├── Event Bus
├── Session
├── Logger
├── Storage
├── Permission
└── Environment

Context 的第一层作用是解耦。

组件不需要知道某个能力的具体实现,只需要知道运行环境向它提供了什么。

例如,一个模型路由组件可能只依赖:

Model Registry
Task Metadata
Usage Metrics

一个长期记忆组件可能只依赖:

Session Events
Embedding Service
Storage
Context Injector

一个工具组件则可能只依赖:

Tool Registry
Credentials
Network
Logger

Context 的第二层作用是限制能力边界。

如果组件只能通过 Context 访问环境,那么系统就可以决定:

  • 哪些资源向它开放;
  • 哪些能力只读;
  • 哪些调用需要权限;
  • 哪些状态不能跨组件共享;
  • 哪些操作只能在 Sandbox 中执行。

因此,Context 不只是依赖注入,也是一种能力安全模型:

组件能做什么
≈
Context 向它暴露什么

4.2 Effect 与 Cleanup:让变化可以被撤销

当组件加入 Harness 时,通常会产生副作用。

例如:

  • 注册一个工具;
  • 添加一个事件监听器;
  • 打开数据库连接;
  • 启动后台任务;
  • 修改模型路由;
  • 向上下文注入内容;
  • 挂载文件目录;
  • 注册快捷键;
  • 创建缓存;
  • 改变权限策略。

这些变化可以统一理解为 Effect:

Component
    ↓
Effect
    ↓
Harness 状态发生变化

问题在于,大多数插件系统只强调如何创建 Effect,却没有把撤销 Effect 当作同等重要的能力。

于是,组件卸载后可能出现:

  • 工具仍然留在 Registry 中;
  • 事件监听器重复注册;
  • 后台任务继续运行;
  • 网络连接没有关闭;
  • 临时文件没有删除;
  • Context 中残留失效状态;
  • 新旧路由规则同时生效。

在 DSH 中,Effect 应该返回对应的 Cleanup:

Effect(Context) → Cleanup

例如:

function activate(context{
  const unregister = context.tools.register(myTool)

  return function cleanup() {
    unregister()
  }
}

更复杂的组件可能产生多个副作用:

function activate(context{
  const stopTool = registerTool(context)
  const stopListener = registerListener(context)
  const stopWorker = startWorker(context)
  const closeConnection = connectDatabase(context)

  return async function cleanup() {
    await stopWorker()
    stopListener()
    stopTool()
    await closeConnection()
  }
}

这使组件具备完整生命周期:

创建
  ↓
激活
  ↓
运行
  ↓
停用
  ↓
清理

4.3 时间可组合性:让 Agent 有资格试错

Effect 与 Cleanup 提供的是一种时间可组合性

一个组件可以在某个时刻进入系统,也可以在另一个时刻完整退出:

t₀:Harness 原始状态

t₁:组件进入
    → 注册工具
    → 启动服务
    → 注入上下文

t₂:组件运行

t₃:组件退出
    → 移除工具
    → 停止服务
    → 撤销上下文

t₄:Harness 恢复到可预测状态

这对普通插件系统很重要,但对自生长系统更重要。

因为自生长必然包含试错。

Agent 创建的新工具、新路由器、新记忆策略,不可能每次都正确。如果一次失败的尝试不能被完整撤销,那么每次实验都会向系统中留下残余状态。

最终,所谓“生长”就会退化成不可控的复杂度积累。

因此,自生长的基本过程不应该是:

生成组件
  ↓
永久安装

而应该是:

生成候选组件
      ↓
执行受控 Effect
      ↓
验证运行结果
   ┌──┴──┐
 成功    失败
  │       │
保留   Cleanup

Cleanup 的意义不只是“支持热卸载”。

它赋予 Agent 一种更关键的能力:

在不永久污染系统的前提下试验新的自身结构。

4.4 Coeffects:让依赖关系成为运行时的一部分

时间可组合性解决的是组件何时进入、何时退出。

但 Agent Harness 的组件并不是彼此孤立的。

如果依赖关系只是组件内部的隐式假设,运行时就无法回答:

  • 组件现在能否启动?
  • 它缺少什么能力?
  • 某个依赖消失后,哪些组件应该停用?
  • 替换一个 Provider 会影响哪些组件?
  • 哪些组件可以被安全卸载?
  • 一个候选组件是否与当前环境兼容?

Coeffects 用于显式表达组件所需的外部条件:

Effect:
组件对系统做什么

Coeffects:
组件需要系统提供什么

例如:

const vectorMemory = {
  coeffects: [
    "embedding-model",
    "vector-storage",
    "session-events"
  ],

  effect(context) {
    // 激活 Memory
    return cleanup
  }
}

运行时可以根据当前 Context 判断组件是否具备激活条件:

依赖全部满足
    ↓
组件激活

依赖不完整
    ↓
组件保持未激活

依赖运行中消失
    ↓
执行 Cleanup

这使 Harness 不再只是一个插件列表,而是一个动态依赖图:

Credential
    ↓
GitHub Provider
    ↓
GitHub Tool
    ↓
Repository Agent

或者:

Embedding Model ─┐
                 ├→ Vector Memory → Context Injector
Vector Storage ──┘

4.5 空间可组合性:让生长不是无序堆积

Coeffects 提供的是一种空间可组合性

组件不是按照固定顺序硬编码在系统中,而是根据能力和依赖形成运行时拓扑。

当一个能力出现时,依赖它的组件可以被激活;当这个能力消失时,相关组件可以被停用:

Sandbox 出现
    ↓
Code Executor 激活
    ↓
Test Runner 激活
    ↓
Coding Agent 获得安全执行能力

如果 Sandbox 被移除:

Sandbox 消失
    ↓
Code Executor Cleanup
    ↓
Test Runner Cleanup
    ↓
相关工具从 Agent 能力集合中撤销

这与传统插件系统有一个关键区别。

传统插件系统更像:

安装了什么
=
系统拥有什么

而动态组件运行时更像:

当前可用能力
=
当前组件
× 当前依赖
× 当前环境
× 当前权限

这使 Harness 的结构可以随着任务和环境变化,而不是不断向全局系统中永久添加模块。

真正的生长,不是组件数量持续增加,而是系统能够形成更适合当前任务的能力结构。

4.6 从动态重组到自生长闭环

Context、Effect、Cleanup 和 Coeffects 为动态重组提供了基础。

但必须强调:

动态重组是自生长的必要条件,不是自生长的全部。

完整的自生长至少需要五个阶段。

4.6.1 第一阶段:发现能力缺口

Agent 首先要判断自己缺少什么。

例如:

  • 当前没有访问某类数据的工具;
  • 现有 Tool 无法处理特定协议;
  • Memory 无法支持长期任务;
  • 当前模型不适合某类输入;
  • 缺少项目专用的验证流程;
  • 当前执行环境不满足安全要求;
  • 工具调用成本或延迟过高。

能力缺口不能只由「工具调用失败」判断。

它还可能来自:

  • 任务规划;
  • 运行指标;
  • 用户反馈;
  • 测试结果;
  • 历史失败记录;
  • 现有能力清单;
  • 环境变化。

因此,自生长的起点不是生成代码,而是:

目标要求
-
当前能力
=
能力缺口

4.6.2 第二阶段:生成或引入候选能力

识别缺口后,Agent 可以选择不同方式补齐能力:

  • 编写新的 Tool;
  • 生成新的 Skill;
  • 安装已有 Package;
  • 包装外部 API;
  • 组合已有组件;
  • 替换 Memory Provider;
  • 调整模型 Router;
  • 创建新的工作流;
  • 启动一个专用子 Agent;
  • 为现有组件增加适配层。

这里的关键词是「候选」。

Agent 生成的组件不能因为能够编译或加载,就自动成为系统的永久能力。

生成成功
≠
能力有效
≠
适合长期保留

4.6.3 第三阶段:在受控环境中激活

候选组件应该先进入受限 Context。

系统需要明确:

  • 它可以访问哪些目录;
  • 可以调用哪些网络服务;
  • 是否可以读取凭证;
  • 可以注册哪些工具;
  • 能否修改全局状态;
  • 资源消耗上限是多少;
  • 运行时间是否受限;
  • 哪些副作用必须登记。

这一点尤其重要,因为扩展代码通常拥有较高权限。

以 Pi 为例,其官方安全文档明确说明,内置工具和 Extension 默认继承启动 Pi 的进程权限;第三方 Package 可能执行代码或指示模型执行操作,因此需要代码审查,并在需要时依赖容器或虚拟化提供真正的隔离边界。(github.com)

如果 Agent 开始自行生成和加载组件,权限问题只会更加突出。

所以,自生长必须建立在受控运行时上,而不能建立在「默认信任所有新代码」之上。

4.6.4 第四阶段:验证、保留或回滚

候选能力激活后,系统需要对它进行验证。

验证可以包括:

  • 能否完成目标任务;
  • 是否通过单元测试;
  • 是否破坏已有功能;
  • 是否产生未声明的副作用;
  • 是否扩大了不必要的权限;
  • 是否降低了任务成本;
  • 是否提高了成功率;
  • 是否造成明显的上下文膨胀;
  • 是否与现有组件冲突;
  • Cleanup 是否完整。

验证结果通常有三种:

验证通过
    ↓
保留或固化

部分通过
    ↓
修改后重新试验

验证失败
    ↓
Cleanup + 回滚

因此,自生长不是一次性的代码生成,而是一个选择过程:

Generate
→ Activate
→ Observe
→ Evaluate
→ Select

没有评估的生成,只是变化。

经过评估和选择的变化,才可能成为生长。

4.6.5 第五阶段:沉淀、复用与淘汰

一次任务中的成功,不代表组件值得永久保留。

系统还需要决定:

  • 这项能力是否跨会话有效?
  • 它适用于哪些任务?
  • 应该在什么条件下重新激活?
  • 它依赖哪些版本?
  • 它的权限范围是什么?
  • 它由谁生成?
  • 经过了哪些测试?
  • 是否允许自动升级?
  • 何时应该重新评估?
  • 何时应该被淘汰?

因此,一个可复用组件除了代码,还需要附带元数据:

Component
├── Implementation
├── Capability Description
├── Coeffects
├── Permissions
├── Validation Records
├── Version
├── Provenance
├── Metrics
├── Effect
└── Cleanup

真正的自生长闭环应当是:

发现缺口
  ↓
生成候选能力
  ↓
隔离激活
  ↓
观察与验证
  ↓
保留或回滚
  ↓
沉淀为可复用组件
  ↓
持续评估
  ↓
升级或淘汰

5. Pi 与 DSH 的核心差异

Pi 与 DSH 并不是简单的替代关系。

它们关注的是两个不同层次的问题。

维度 Pi DSH
核心目标 构建开放、可定制的 Agent Harness 构建支持动态变化的组件运行时
能力来源 Extensions、Skills、Prompts、Packages 运行时组件及其依赖关系
主要变化发起者 用户、开发者,也可以由 Agent 辅助 用户、系统或 Agent
组件组织方式 扩展资源与 Package 动态依赖图
激活方式 启动加载、临时加载或重新加载 根据 Context 与 Coeffects 激活
生命周期 以扩展加载和配置为中心 Effect 与 Cleanup 对称管理
依赖治理 主要由扩展代码和包依赖处理 将运行时能力依赖显式化
失败处理 依赖扩展实现、进程或会话边界 通过 Cleanup 支持组件级回滚
演化方向 从固定产品走向开放平台 从开放平台走向受控自生长
核心价值 让 Harness 容易被改变 让 Harness 可以安全地持续改变

可以把两者的关系概括为:

Pi:
稳定核心
+
开放扩展点
+
能力分发机制

DSH:
组件化 Context
+
显式 Coeffects
+
可逆 Effect
+
动态生命周期

Pi 解决了「能力能否加入」的问题。

DSH 更关注「能力如何进入、如何协作、如何退出,以及如何在运行时形成新的结构」。

6. 从可扩展到自生长,中间还差什么

可以把 Agent Harness 的演进分为三个阶段。

6.1 第一阶段:固定能力

模型
+
固定提示词
+
固定工具

系统能够执行任务,但能力边界在设计阶段已经确定。

如果缺少能力,只能由开发者修改产品代码并重新发布。

6.2 第二阶段:开放扩展

稳定核心
+
Extensions
+
Skills
+
Packages
+
开放 API

用户和开发者可以持续增加能力。

Agent 也可以帮助编写扩展,但扩展的发现、安装、验证和维护通常仍然依赖人工流程。

Pi 代表了这一阶段的重要方向:Harness 不再是一个封闭产品,而成为可以根据个人工作流持续定制的平台。

6.3 第三阶段:受控自生长

稳定内核
+
能力缺口识别
+
候选组件生成
+
动态组件运行时
+
显式依赖
+
可逆副作用
+
隔离验证
+
自动回滚
+
能力沉淀
+
持续淘汰

Agent 不再只是等待外部为它安装能力。

它可以根据任务识别不足,提出新的能力结构,在受控环境中试验,并根据结果决定保留、修改还是撤销。

这时,Agent 的角色从能力消费者变成了能力形成过程的参与者。

7. 自生长不等于无限增长

「自生长」并不是 Agent 不断为自己增加工具和代码。

无限增加不是生长,而是熵增。

一个只会添加能力、不会清理能力的系统,最终会出现:

  • 工具数量不断膨胀;
  • 相似能力重复实现;
  • 组件版本相互冲突;
  • 权限范围持续扩大;
  • 上下文被大量描述占据;
  • 路由规则越来越难以理解;
  • 无效监听器和后台任务持续运行;
  • Agent 无法判断应该使用哪个工具;
  • 系统行为逐渐不可预测。

因此,自生长必须同时包含四种能力:

增加
+
选择
+
清理
+
遗忘

可以进一步表示为:

可持续生长
=
生成新能力
-
淘汰无效能力
+
重组已有能力

这也是 Cleanup 和生命周期管理的重要性所在。

真正成熟的 Agent 不仅要知道「我还需要什么」,还要知道:

  • 什么已经不需要了;
  • 什么正在产生负面影响;
  • 什么应该暂时停用;
  • 什么可以被更简单的组件替代;
  • 什么经验只适用于过去的环境。

遗忘不是生长的反面。

受控遗忘本身就是生长的一部分。

9. Pi 与 DSH 不是替代,而是演进

Pi 的价值在于证明了一件事:

Agent Harness 不需要是一个功能固定的封闭产品。

通过开放 Extension、Skill、Prompt 和 Package,Harness 可以被用户重新塑造,也可以让 Agent 辅助编写自身需要的扩展。

这已经比传统 Agent 产品向前迈出了一大步。

DSH 所代表的方向,则是在此基础上进一步抽象:

如果 Agent 可以创建新能力,那么这些新能力应该如何成为运行时的一等公民?

答案不能只是把更多代码放进扩展目录。

系统还需要:

  • 明确组件所处的 Context;
  • 显式声明组件依赖的 Coeffects;
  • 管理组件产生的 Effect;
  • 在组件退出时执行 Cleanup;
  • 根据环境变化重新计算能力拓扑;
  • 对候选能力进行验证;
  • 将有效能力沉淀下来;
  • 将失效能力安全淘汰。

因此,二者可以形成一种递进关系:

Pi:
让 Harness 可以被修改

        ↓

DSH:
让修改具有显式生命周期

        ↓

自生长 Agent:
让 Agent 可以发起、验证并沉淀修改

从这个角度看,DSH 不是对 Pi 的否定。

它是在 Pi 所代表的开放扩展思路上,将“变化”进一步提升为运行时的核心抽象。

10. Harness 正在成为 Agent 的生长环境

如果说 Pi 的核心贡献,是把 Agent 从一个封闭产品变成一个可编程、可扩展的平台,那么 DSH 所探索的下一步,就是让 Agent 逐渐成为自身能力变化的参与者。

它不只是使用已有工具,还可以:

  • 发现能力缺口;
  • 创建候选组件;
  • 调整能力组合;
  • 在受控环境中试验;
  • 根据结果保留或回滚;
  • 将成功经验沉淀为长期能力。

但自生长不等于无限增加。

一个只会安装新工具、写入新记忆、叠加新策略的 Agent,最终只会被自己积累的复杂度拖垮。

真正可持续的生长,必须同时包含:

发现
→ 生成
→ 激活
→ 验证
→ 选择
→ 清理
→ 沉淀
→ 淘汰

Pi 回答了:

如何让 Agent Harness 可以被持续扩展?

DSH 则进一步追问:

如何让 Agent 安全地参与自身能力的形成?

从 Pi 到 DSH,变化的不只是扩展机制,而是 Harness 的角色。

它正在从承载工具与插件的工程框架,转变为支持能力试验、筛选、重组和沉淀的运行环境。

未来的 Agent Harness 可能不再只是模型与工具之间的连接层。

它还将成为 Agent 管理自身变化的基础设施:

  • 允许新能力出现;
  • 阻止错误变化扩散;
  • 保存经过验证的经验;
  • 清除已经失效的结构;
  • 在保持稳定内核的同时持续重组外围能力。

只有当 Agent 既能增加能力,也能验证、回滚、清理和遗忘时,它才真正具备从「可扩展的软件」走向「可持续生长的系统」的可能。

以上。

历史总是在重演,AI 时代的我们应该做些什么

很多公司以为自己接入了几个大模型,采购了一批智能体工具,员工就获得了某种近乎无限的生产力。

换个角度看:每个人突然多出了一百万名能力不稳定、缺乏上下文、不会主动澄清目标、偶尔虚构进度、还能高速消耗预算的下属。

这些下属不需要工位,也不会请假。它们一天可以提交上千次结果,看起来永远在工作。管理者很容易被这种吞吐量迷惑,误把生成速度当成有效产出,误把调用次数当成自动化程度,误把一段流畅的回答当成任务已经完成。

AI 把执行成本压得很低,同时把管理问题放大了。

今天的智能体系统可以被看成一种新型组织。模型是员工,token 是人力预算,上下文是岗前培训,工具权限是组织授权,工作流是汇报关系,评估体系是绩效制度,人工验收则对应管理责任。

按照这个视角观察,很多 AI 项目的失败也是情里之中。它们把模型能力当成唯一变量,却没有建立与之配套的管理系统。结果类似一家突然扩张到百万人的公司:招聘极快,职责模糊,没有培训,没有绩效标准,管理者还希望它自行涌现出秩序。

历史上,这类问题出现过。

铁路旧事

十九世纪三十年代的铁路热潮带来了前所未有的运输能力。铁路解决了距离和速度问题,也制造了规模、调度、协作和安全问题。

单条铁路容易运营。线路变长、车次增加、岗位分化以后,系统复杂度迅速上升。信息必须跨地区传递,车辆必须统一调度,责任必须分层,事故必须被记录和复盘。原有的管理方式无法支撑新基础设施的吞吐量,事故随之发生。

铁路公司最终补上的,是现代管理体系。

技术增加了系统能力,管理制度把能力组织成可持续的产出。铁路后来成为第一个十亿美元产业,支撑它的并非轨道和机车本身,还包括围绕大规模协作建立的组织结构。

AI 正在重演类似的路径。

过去的软件系统严格服从预定义逻辑。输入符合条件,程序进入对应分支。工程团队可以通过代码结构、类型系统、测试和运行指标逐层收紧系统行为。

智能体的行为边界宽得多。它可以搜索、分析、生成、调用工具、保存记忆、继续拆解任务。它也可能误解任务、忽略约束、重复调用、把猜测写成事实,在结果看似完整时掩盖中间步骤的错误。

当模型调用规模扩大,问题便从「它会不会回答」变成「我们怎样管理一支由概率模型构成的劳动力」。

许多企业仍在用采购软件的思路处理这件事:买更强的模型,开放更多上下文,增加 token 预算,再连接更多工具。

这很容易走向失控。

人海复现

遇到复杂任务就增加 token,和过去遇到项目延期就增加人手,逻辑高度相似。

管理者看到结果不好,第一反应往往是扩大上下文窗口,让智能体多思考几轮,再增加几个子智能体并行搜索。系统图越来越复杂,调用链越来越长,账单同步上涨。

问题可能出在任务定义上。

一个智能体收到「分析这家公司是否值得投资」,它需要自行猜测分析期限、风险偏好、数据范围、估值框架和输出用途。另一个智能体收到「优化系统性能」,它也要猜瓶颈位于吞吐量、尾延迟、内存占用、启动时间还是硬件成本。

人类员工面对这种指令,会追问。智能体更可能直接开始工作。它会用流畅语言填补任务中的空白,把未经确认的假设埋入推理过程,最终交付一份结构完整、目标偏离的结果。

给它更多 token,只会让偏离过程更长。

这和组织扩张中的人海战术没有本质差异。需求尚未稳定时增加开发人员,沟通路径随人数增长,信息损耗也随之增长。新增人员首先要理解问题,再理解已有方案,然后处理方案之间的冲突。最后,大量时间花在协调新增人力本身。

多智能体系统也会出现同样的问题。

一个规划智能体拆任务,多个执行智能体并行处理,汇总智能体负责合并,审查智能体再做验证。架构图确实很漂亮。但实际运行时,规划阶段的歧义被复制到所有分支,各个执行智能体基于不同假设工作,汇总阶段只能用更多 token 消解冲突。

最终系统花费大量计算资源,证明自己已经花费了大量计算资源。

智能体反复自我调用的循环尤其典型。表面上看,它在持续反思和改进;深入观察调用轨迹,常常会发现它没有获得新的外部信息,也没有引入新的判定标准,只是在几个近似答案之间改写措辞。

这种循环不会自然收敛。缺少验收条件时,「继续思考」没有终点。

工程上必须为循环设置退出规则:哪些指标达到后停止,连续多少轮没有新增信息后停止,成本超过多少后转人工,发生哪些工具错误后禁止重试。退出规则缺失,智能体就会把预算消耗当成工作进展。

上下文债务

几乎没人会给 AI 恰当的语境。

这句话听起来像提示词问题,实际涉及企业知识怎样存在、怎样更新、怎样被验证。

当一个人在公司里工作几年,会形成大量隐性知识。他知道某个字段名与实际含义不同,知道某项流程在文档里有三步、落地时有七步,知道某个客户的特殊约定,知道哪些数据能够使用,哪些结论虽然算得出来却不能直接发布。

这些知识分散在人的记忆、聊天记录、会议结论、历史工单和代码注释中。公司过去可以容忍这种状态,因为熟练员工承担了知识路由器的角色。谁遇到问题,找对人问一句即可。

AI 无法稳定获取这种语境。

把所有资料塞进上下文也解决不了。上下文越长,噪声越多;资料之间可能相互冲突;旧制度和新制度可能同时出现;局部例外可能被模型理解成通用规则。检索系统可以减少输入量,却无法自动判断哪份知识具备权威性。

上下文工程因此要处理四类问题。

第一类是范围。智能体完成当前任务究竟需要哪些信息,哪些材料只会分散注意力。范围太窄会漏掉约束,范围太宽会增加延迟和成本,也会提高错误引用的概率。

第二类是时效。文档必须带有版本、适用日期和失效条件。缺少这些元数据,模型很难区分现行规则与历史记录。

第三类是权威级别。制度文件、团队约定、个人经验和历史案例不能拥有相同权重。检索结果需要体现来源优先级,冲突时还要触发澄清或人工审批。

第四类是任务绑定。知识必须与执行步骤建立关系。把二十份文档交给智能体,让它自行判断何时使用,等于把一摞制度放到新员工桌上,然后要求他当天独立处理高风险业务。

上下文管理本身就是管理工作。它对应人类组织中的培训、制度建设和岗位说明。

企业在这里还会遇到政治阻力。

熟练员工掌握的独门知识,往往构成他在组织中的议价能力。让他把这些知识结构化地交给 AI,等于要求他亲手降低自身稀缺性。员工可能口头支持,实际只提交表层流程;关键判断仍保留在脑中,文档写得正确却无法操作。

靠宣传无法解决这种冲突。

知识沉淀必须进入正式职责,并与岗位价值重新绑定。贡献高质量规则、案例和评估集的人,需要获得与业务产出相当的认可。否则,组织一边要求员工共享知识,一边继续按照「不可替代程度」奖励个人,制度会驱动所有人保留信息。

AI 转型推进到深处,技术阻力通常会下降,组织阻力会持续上升。

冗员变形

传统组织里的冗员不一定完全不工作。他可能参加会议、整理材料、同步状态、反复确认,把时间消耗在缺少业务增量的活动上。

智能体也能制造同类繁忙。

大量 token 用于复述任务、生成计划、解释已经执行的步骤、重写已有答案。输出篇幅持续增长,新增信息却接近于零。调用记录看起来很丰富,真正影响最终决策的内容只占很小一部分。

「八成 token 什么都没做」虽然带有夸张色彩,却很接近许多智能体工作流的结构性问题。

衡量 token 效率不能只看输入输出数量。更合适的单位是有效决策增量:一次调用是否增加了新证据,是否排除了一个错误方向,是否完成了可验证的执行动作,是否降低了后续人工判断成本。

如果一轮调用没有完成以上任何一项,它大概率属于计算冗员。

企业很容易用平均调用成本掩盖这个问题。单次模型调用足够便宜时,团队会觉得多跑几轮无所谓。规模扩大后,浪费会在三个方向累积:直接推理成本、任务延迟,以及人工审查负担。

第三项经常被忽略。

智能体每多生成一份结果,人就多承担一份潜在验收工作。机器吞吐量可以在短时间扩大几十倍,人的审查能力没有同步扩张。最终形成新的队列:模型输出堆积,员工随机抽查,错误在未被发现的情况下进入下游。

这时再增加智能体,只会继续扩大待验收库存。

需要被放大的,是能够显著改变结果的「100 倍 token」。某次调用拿到了决定性证据,识别了关键矛盾,修正了任务边界,或者发现了会导致整条工作流失败的风险。这类调用数量很少,贡献却远高于普通生成。

寻找它们依赖调用追踪和结果归因。

每个工作流至少要记录:调用目的、输入来源、使用工具、产生的新信息、对最终结果的影响、失败类型以及人工修改位置。没有这套记录,团队只能看到账单和最终文本,无法分辨哪些调用有效,哪些调用只是增加长度。

这类似分析一个大型团队。只看员工人数和工作时长,无法判断组织效率。必须知道关键决策在哪里发生,哪些岗位形成瓶颈,哪些汇报关系制造重复劳动。

评估先行

管理智能体最有效的抓手是评估标准,也就是 evals。

编程成为 AI 当前最成功的应用场景,与代码天然具备可评估性有很大关系。程序可以编译或运行,测试可以通过或失败,性能可以测量,回归可以复现。模型生成的内容即使风格不同,也能被放入相对稳定的验证框架。

大量业务任务没有这种便利。

「研究一家企业」怎样算完成?

「生成高质量销售线索」中的高质量由谁定义?

「完成合同审查」需要发现哪些类型的风险?

「总结客户需求」允许遗漏哪些内容,又绝不能遗漏哪些内容?

企业如果不能回答这些问题,智能体也无法稳定交付。

很多团队先建设工作流,最后才补评估。他们把模型、检索、工具调用和记忆系统连接起来,跑出一批结果,然后组织专家凭感觉打分。几个版本之后,专家标准发生变化,历史结果无法比较,模型优化也失去方向。

评估应该早于大规模自动化。

任务进入智能体系统之前,先定义成功条件、失败类型和风险边界。评估集需要覆盖常规案例、边界案例、冲突信息、缺失信息以及必须拒绝处理的情况。只有理想样本的评估集,会把系统训练成演示环境里的优秀员工。

一个可用的评估体系至少分成四层。

第一层检查任务完成度。要求提取的字段是否齐全,要求执行的动作是否发生,输出格式是否符合下游接口。

第二层检查事实和证据。结论能否追溯到输入材料,引用是否支持对应判断,模型有没有用常识填补数据缺口。

第三层检查业务质量。结果是否符合领域规则,是否覆盖关键风险,是否达到能够进入下一流程的标准。

第四层检查运行效率。完成任务使用了多少 token、多少工具调用、多少时间,发生了多少次重试,人工修改比例是多少。

只评结果质量,系统可能通过十倍成本换取一点点效果提升。只评成本,系统又会通过减少分析步骤制造隐蔽错误。质量、成本和风险必须放在同一套评估里。

评估标准也不能永久固定。业务规则会变化,模型行为会变化,工具返回的数据结构也会变化。每次线上事故都应转化成新的评估案例。没有进入评估集的事故,后续很可能以相似形式再次出现。

这与人类团队的管理方式一致。OKR、质量标准、事故复盘和晋升要求,共同塑造员工行为。只给方向、不定义验收,最后只能依赖主管逐项盯人。

智能体同样需要制度化约束。

思想主权

AI 编程带来的另一个变化,是程序员的工作重心开始从逐行控制代码迁移到控制软件背后的想法。

模型一天能够生成数千行代码。开发者逐行审查全部输出,时间上已经不可行。即使坚持审完,也容易陷入局部细节:命名是否顺眼,函数是否拆得足够小,某段逻辑是否符合个人风格。

这些问题仍有价值,只是优先级下降了。

大模型擅长局部实现。给出清晰边界后,它可以完成函数、接口和测试,也能按照反馈快速调整。它对整体设计的把握更不稳定,尤其容易在多个局部合理选择之间拼出一个整体失衡的系统。

程序员必须控制设计意图。

数据结构为什么这样选择,状态由谁拥有,哪些不变量必须维持,失败后怎样恢复,性能目标是什么,哪些路径允许降级,哪些行为属于系统禁区。这些内容如果只存在于开发者脑中,AI 会用自己的概率偏好填补空白。

结果可能能运行,也可能通过局部测试,却难以演进。

控制思想要求开发者拥有足够深的系统理解。他需要判断某种设计会怎样影响内存布局、并发行为、故障边界和长期维护成本。只会写提示词无法完成这项工作。

vibe coding 最大的问题不在生成速度,而在责任链断裂。使用者描述一个模糊目标,模型生成实现,程序恰好能够运行,于是产物被视为完成。系统内部怎样工作、错误会在哪里积累、性能上限由什么决定,没有人掌控。

这种开发方式可以用于低风险原型。进入长期维护或高性能系统后,代价会快速暴露。需求变化一次,开发者无法判断应该修改哪一层;性能下降时,也不知道问题来自算法、数据结构还是调用关系。每次修改只能继续依赖模型猜测,系统逐步变成无人理解的代码堆积。

antirez 在开发本地 LLM 推理软件 DwarfStar 时,面对的是大量细微且会累积的错误。这类项目不能只看单个函数是否合理。一个局部误差、一处内存选择、一个数据结构上的偏差,都可能沿执行路径放大。

AI 在严谨设计和测试中可以提供很大帮助。前提是人掌握系统意图,知道该要求 AI 验证什么,也知道哪些结果不能接受。

代码审查也需要重新分配精力。

对于 Redis 这类大量用户会直接阅读和修改源码的项目,保持代码质量是对用户的尊重。审查仍然有意义。但如果开发者把全部时间用在检查每一行 AI 输出,QA、性能分析和下一步设计就会被挤压。

有限的人力应该优先投入故障模式、系统不变量、边界条件、性能退化和可维护性。代码风格可以交给自动化工具,重复性实现可以交给模型,局部错误可以由测试覆盖。设计责任无法外包。

设计文档

DESIGN.md 在 AI 编程环境中的地位会持续上升。

过去很多设计文档写于项目启动阶段,代码落地后很快过期。开发者最终以代码为准,文档只保留历史价值。AI 参与开发后,设计文档需要变成持续维护的系统接口。

它应该描述每个数据结构承担什么职责,关键实现技巧服务于什么目标,模块之间怎样协作,哪些不变量不能破坏,以及修改某部分时需要同步检查哪些路径。

模型阅读这样的文档,才能在修改代码前获取设计约束。当我们接手项目时,也可以先理解系统思想,再进入具体实现。

这并不意味着文档越长越好。把代码换成自然语言复述,价值很低。有效文档集中解释代码无法自行表达的内容:为什么选择这个方案,放弃了哪些方案,哪些行为依赖隐含前提,哪些性能特征必须保持。

文档还要进入变更流程。

如果代码改动影响数据结构、状态模型或失败处理,设计文档必须同步更新。评审时需要检查实现与文档是否一致。否则,AI 会依据旧设计修改新代码,错误会披着「遵循项目规范」的外衣进入系统。

设计文档可以视为给未来开发者和未来智能体准备的上下文。它承担组织记忆,减少关键思想只存在于少数人脑中的风险。

企业业务流程也需要同类文档。

流程说明不能停留在步骤列表。它要写清每一步的目标、输入、输出、判断依据、异常路径、升级条件和验收标准。只有这样,智能体才有可能承担稳定执行。

很多所谓 AI 落地难题,本质上暴露了企业从未把业务讲清楚。过去依赖熟练员工临场判断,这些模糊地带被人的经验掩盖。模型进入流程后,所有未定义部分同时暴露出来。

AI 没有制造这些混乱。它提高了混乱被复制的速度。

人的边界

AI 可以承担高吞吐的搜索、执行、分析和记忆管理。

搜索适合机器,因为它能在大量材料中并行查找候选信息;执行适合机器,因为重复流程可以被稳定调用;分析可以由机器生成多个假设和比较维度;记忆管理则可以通过检索、归档和关联降低人脑负担。

人需要保留四项责任:问题定义、歧义处理、风险判断和最终验收。

问题定义决定系统优化什么。目标写错,后续所有高效执行都会扩大偏差。

歧义处理决定何时允许模型自行推断,何时必须暂停并提问。完全禁止推断会让工作流频繁中断,完全开放推断又会把隐含假设带入结果。工程上应根据风险分级:低风险、可逆操作可以允许模型补全;高风险、不可逆操作必须要求显式确认。

风险判断需要结合业务后果。模型可以列出风险,却无法替组织承担责任。同样的错误,在内部草稿里可能只需重新生成,在合同、资金、生产系统和对外承诺中,影响完全不同。

最终验收意味着人要对结果负责。验收不能退化成快速扫一眼。模型输出量超过人的审查能力时,系统应限制吞吐量、增加自动评估或降低自动化范围。堆积大量无人验收的输出,没有形成生产力,只是形成了信息库存。

人机分工也不应按照「模型能做什么」设计。这个问题会随着模型升级不断变化。更稳定的划分方式是看责任和可验证性。

可验证、可回滚、低风险的任务,可以给智能体更大自治权。

结果可验证但动作难以回滚时,应增加审批点。

结果难以完整验证、错误影响又高的任务,AI 更适合作为分析和建议工具。

这套边界需要写进系统权限。只靠提示词要求模型谨慎,约束强度远远不够。工具层要限制可调用范围,工作流层要设置审批,运行层要保留审计记录,评估层要持续检查越权行为。

提示词属于沟通机制。权限系统才承担治理责任。

年轻工程师

我对年轻程序员是有些担心的,并非担心他们使用 AI,而是他们在建立心智模型之前就把实现过程全部交给 AI。

一个人没有亲手实现过解释器、数据库或哈希表,很难判断模型生成的实现在哪些地方存在结构性问题。他可能读得懂语法,也能运行测试,但无法推演数据怎样流动、状态怎样变化、性能为什么退化。

核对 AI 输出不能替代基础训练。

逐行检查一段自己并不理解的代码,只会制造虚假的参与感。更有效的训练方式是亲手实现规模受控的系统,遇到内存、并发、解析、索引和错误恢复问题,再用 AI 帮助验证思路、补充测试、寻找遗漏。

基础能力的价值还会上升。

代码生成越来越便宜以后,企业不会继续为普通代码行支付高溢价。能够定义系统、识别风险、设计评估、分析性能和承担技术决策的人,会掌握更大的杠杆。

年轻工程师如果只学习怎样让模型快速产出,能力会紧贴当前工具。模型更新一次,原有技巧可能迅速贬值。数据结构、操作系统、网络、数据库和编程语言建立的心智模型变化慢得多,它们决定一个人能否判断 AI 给出的方案。

高级工程师也不能因此停留在旧习惯里。

坚持所有代码都必须人工逐行编写,会把大量时间消耗在机器已经可以低成本完成的工作上。经验应该投入架构、约束、验证和复杂问题拆解。继续用代码行数证明专业性,和管理者用团队人数证明影响力没有太大差别。

管理重构

企业引入 AI 时,组织结构也要调整。

过去,一个经理管理十几个人已经接近沟通上限。未来,每个员工都可能拥有大量智能体。管理对象数量突然扩张,个人需要具备过去只有管理者才需要的能力:拆解任务、定义结果、提供上下文、设置检查点和处理异常。

工程师会越来越像技术经理。经理则需要理解评估、权限、成本和模型行为,单靠资源协调很难管理智能体劳动力。

团队流程也会变化。

需求评审需要增加可评估性检查。一个需求如果无法写出验收条件,进入智能体工作流后只会制造更多不确定输出。

架构评审需要关注智能体权限、上下文来源和失败路径。模型调用成功不等于业务任务成功,工具返回正常也不等于动作合理。

上线评审需要检查成本上限和停止条件。任何允许递归、自我调用或动态扩展任务的系统,都必须有预算约束。

事故复盘需要分析模型为什么做出对应行为,输入中缺少了什么,评估为什么没有拦截,以及权限设计是否允许错误继续扩大。把问题归结为「模型偶尔会犯错」,相当于铁路事故后只说机车存在风险。

采购更强的模型可能提升基线能力,却不会补齐管理缺口。模型越强,执行范围越大,错误的影响半径也越大。

AI 项目的成熟度最终会体现在几个问题上:

  1. 团队能否精确描述智能体负责的任务?
  2. 能否量化什么叫完成?
  3. 能否追溯结论来自哪些输入?
  4. 能否限制循环、重试和预算?
  5. 能否知道哪些 token 改变了结果?
  6. 能否在高风险动作前强制人工介入?
  7. 能否把线上失败转化成评估案例?
  8. 能否让设计思想脱离个人记忆,进入可维护的组织资产?

这些问题回答不出来,再复杂的智能体架构也只是一次昂贵的演示。

铁路时代的企业最终学会了管理速度、规模和复杂性。AI 时代需要处理的是概率行为、无限执行能力和有限人类判断之间的冲突。

我们已经拥有了数量近乎无限的数字员工。它们勤奋、便宜、速度极快,也缺少组织记忆、责任意识和稳定判断。

接下来的工作落在人身上:把问题讲清楚,把标准写出来,把权限收紧,把思想保存下来,对最后的结果签字。

这些都是基于当前模型能力和个人认知所作出的判断,随着时间的推移,也可能会涌现其它我当前所认知不到的,那又将是另一个世界。

以上。