从一次代码任务说起
假设你正在使用一个编码 Agent 修复仓库中的失败测试。你发出指令,Agent 读取文件、运行测试、修改代码、再次运行测试,直到通过。这个过程中,模型只是生成意图,真正执行动作的是 harness——围绕模型构建的运行时。如果 harness 是单体式的,工具执行、会话状态和推理循环都挤在同一个进程里,那么一次容器重启就会丢失整个会话,更换模型需要重启所有内容,水平扩展也会因为每个实例持有过多状态而受阻。
Cordis 是一个元框架,它不直接提供 Agent 能力,而是提供一种组织运行时的方式:通过插件、共享 context、可逆 effects 和 reactive coeffects,把系统拆成可替换、可组合的部件。DeepSeek Harness 正是基于 Cordis 构建的 Agent 运行时,它把模型适配器、工具注册表、会话日志、agent loop、审批/沙箱、遥测等都组织成可替换插件。本文以 Harness 中加载一个工具插件并执行一次代码任务为贯穿场景,解释 Cordis 的时空可组合机制,并区分哪些是 Cordis 通用机制、哪些是 Harness 已实现、哪些是基于架构的推断。
空间组合:共享 context 与插件树
空间组合解决的是“系统由哪些部件构成、它们如何连接”的问题。传统依赖注入或事件总线也做组合,但 Cordis 的组合单元是插件,插件之间不直接互相 import,而是通过共享的 context 协作。
在 Cordis 中,context 是一个贯穿整个运行时的对象,它承载服务注册表、配置、事件总线、coeffects 等。插件在启动时被加载,并可以访问 context 来注册自己的服务、订阅事件、提供配置。插件之间不直接调用,而是通过 context 间接交互。例如,一个工具插件会注册一个工具服务到 context,agent loop 插件通过 context 获取该服务并调用。
DeepSeek Harness 的插件树在启动时由 profile、bundle 和 patch 构造。profile 定义了一组插件的集合,bundle 是插件的打包单元,patch 则是对已有配置的覆盖或修改。启动时,Harness 根据 profile 加载相应的 bundle,应用 patch,最终构造出运行时插件树。这个树中的每个节点都是一个插件实例,它们共享同一个 context。
这种组合方式与普通插件系统的区别在于:普通插件系统往往只提供“加载”和“调用”,而 Cordis 的插件通过 context 可以访问系统的其他部分,从而实现更灵活的组合。例如,工具插件可以通过 context 获取遥测服务,自动上报执行指标,而不需要显式依赖遥测插件。
时间组合:可逆 effects 与 reactive coeffects
时间组合解决的是“状态如何随时间变化、如何回滚”的问题。传统 Agent 框架中,工具执行会产生副作用,比如修改文件、发送请求,这些副作用通常是不可逆的。如果后续步骤失败,很难恢复到之前的状态。
Cordis 引入了可逆 effects 的概念。一个 effect 是一个描述“做什么”的对象,它包含一个执行函数和一个撤销函数。当插件执行一个 effect 时,系统会记录它;如果需要回滚,系统会调用撤销函数。这样,状态变化可以被追踪和逆转。
reactive coeffects 则是“对变化的反应”。当 context 中的某个状态变化时,coeffects 会触发相应的回调。例如,当工具执行完成时,coeffects 可以自动更新会话日志。
在 DeepSeek Harness 中,可逆 effects 用于管理工具执行和状态修改。例如,一个编辑文件的工具执行时,会先记录原文件内容,执行修改后,如果后续步骤失败,可以撤销修改。这种机制使得错误恢复成为可能:当 agent loop 检测到异常时,可以回滚到上一个一致状态。
一次代码任务的完整流程
下面以“修复失败测试”为例,描述数据与状态在 Harness 中的流动。
- 用户输入:用户通过 CLI 发送指令“修复失败的测试”。
- session event:Harness 将用户输入作为 session event 追加到会话日志。
- 构建上下文:agent loop 从会话日志中取出相关事件,构建模型请求的上下文,包括系统提示词、工具 schema 等。
- 模型请求:调用模型,传入上下文。
- 模型响应:模型返回一个工具调用意图,例如“运行测试”。
- 工具执行:agent loop 解析工具调用,通过工具注册表找到对应工具,执行它(在沙箱中)。
- 结果回写:工具执行结果作为 tool result 追加到会话日志。
- 下一步请求:agent loop 再次构建上下文(包含新的结果),调用模型,直到没有工具调用为止。
在这个过程中,会话日志是模型可见上下文的唯一来源。它记录了所有事件,包括用户消息、模型响应、工具调用和结果。如果 harness 重启,可以从会话日志恢复现场。
下面用 Mermaid 流程图展示这一流程:
flowchart TD
A[用户输入] --> B[追加 session event]
B --> C[构建上下文]
C --> D[调用模型]
D --> E{有工具调用?}
E -- 是 --> F[执行工具]
F --> G[追加 tool result]
G --> C
E -- 否 --> H[结束]
可逆 effect 如何影响状态与恢复
可逆 effect 在 Harness 中的具体体现是:每个工具执行都包装成一个 effect,记录执行前状态。当发生错误或用户取消时,可以撤销已执行的效果。例如,如果 agent 修改了多个文件后,在运行测试时发现失败,可以回滚所有文件修改,恢复到初始状态。
这种机制对错误恢复至关重要。传统框架中,一旦工具执行失败,会话可能处于不一致状态,难以继续。而可逆 effect 允许系统回滚到一致状态,然后重试或终止。
配置覆盖也是通过 patch 实现的。在启动时,patch 可以修改插件的配置,例如更改模型提供商的 API 端点。这种覆盖是可逆的,因为 patch 本身也是 effect,可以撤销。
unload 是 Cordis 的一个特性,允许在运行时卸载插件。当卸载一个插件时,它的所有 effects 会被撤销,服务会从 context 中移除。这实现了热更新:你可以替换一个插件而不重启整个系统。
与普通插件系统、DI/事件总线、无可逆 effect 框架的对比
下表对比了 Cordis 与普通插件系统、传统 DI/事件总线、无可逆 effect 的 Agent 框架在几个关键维度上的差异:
| 维度 | Cordis | 普通插件系统 | 传统 DI/事件总线 | 无可逆 effect 的 Agent 框架 |
|---|---|---|---|---|
| 组合方式 | 插件通过共享 context 协作 | 插件直接依赖或通过接口 | 组件通过容器注入依赖 | 模块直接调用 |
| 卸载 | 支持,可撤销 effects | 通常不支持 | 不支持 | 不支持 |
| 热更新 | 支持,可替换插件 | 有限支持 | 不支持 | 不支持 |
| 可测试性 | 高,可 mock context | 中 | 中 | 低 |
| 失败恢复 | 可回滚到一致状态 | 无 | 无 | 无 |
| 适用条件 | 需要频繁演进的复杂系统 | 简单插件化 | 依赖管理清晰 | 短生命周期任务 |
这个表格说明,Cordis 在组合灵活性和恢复能力上更强,但代价是学习曲线和实现复杂度。对于快速原型,普通插件系统可能足够;对于长期运行的生产系统,Cordis 的可逆 effects 和热更新能力更有价值。
边界与反例:什么情况下 Cordis 的机制不适用
Cordis 的机制并非万能。首先,可逆 effects 要求副作用是可撤销的。对于外部 API 调用,撤销可能意味着发送补偿请求,但并非所有 API 都支持。例如,发送邮件后无法撤回。因此,对于不可逆的副作用,可逆 effects 只能记录,不能真正回滚。
其次,共享 context 可能导致隐式耦合。插件通过 context 交互,如果 context 结构不稳定,插件之间的依赖关系会变得模糊,增加维护难度。
第三,热更新虽然强大,但需要插件实现良好的生命周期管理。如果插件持有外部资源(如数据库连接),卸载时未正确释放,会导致资源泄漏。
最后,Cordis 的抽象层较多,对于简单任务可能过于复杂。一个简单的 CLI Agent 可能不需要可逆 effects,直接调用工具即可。
结论
Cordis 通过共享 context、插件、可逆 effects 和 reactive coeffects,实现了空间和时间上的可组合性。DeepSeek Harness 利用这些机制,将模型适配器、工具、会话日志等组织成可替换插件,使得系统可以灵活演进、可靠恢复。然而,这些能力需要付出实现复杂度和学习成本。对于需要长期运行、频繁更新、高可靠性的 Agent 系统,Cordis 的机制提供了显著优势;对于简单场景,传统方法可能更合适。