dshseek

seek://guides/what-is-cordis

什么是 Cordis?(论文精读)

2026年8月14日Intermediate← 全部教程

TL;DR

Cordis 是 DeepSeek Harness 底层的插件框架,其设计被形式化在预印本《A Programming Paradigm for Spatiotemporal Composability》中。本指南用人话精读这篇论文:时间可组合性指组件移除时副作用完全回退,空间可组合性指依赖被声明并被响应式管理——两者即 revertible effects 与 reactive coeffects,最终统一进单一 context type。

要点

  • 论文把动态组合拆成两个正交维度:时间维(可逆 effect)与空间维(响应式依赖)
  • Revertible effects:每次上下文变换都带一个被运行时追踪的逆——插件卸载时回退它做过的一切
  • Reactive coeffects:组件声明所需(inject),运行时响应式地解析激活顺序
  • 两者统一为单一 context type;动态组合演算的元理论把保证从单组件提升到整个系统
  • Cordis 是其实现:effect 追踪、coeffect 解析、带配置调和与 HMR 的声明式加载器——dsh 的「一切皆插件」就是这套演算的产品化

$ dsh –读论文

大多数 agent 框架把插件当一个功能。DeepSeek Harness 把插件当一个范式,而且为它配了这个领域几乎没人配过的东西:一篇论文。A Programming Paradigm for Spatiotemporal Composability(时空可组合性编程范式)把 harness 插件运行时底下的数学形式化了,这个运行时叫 Cordis

不读论文完全可以用 dsh。但只要你装过两个都想改写提示词的扩展,琢磨过谁赢、卸载一个之后会发生什么——恭喜,你已经在没有词汇的情况下做时空可组合性研究了。这篇指南先给你词汇,再给机制,最后给代码的味道。

为什么一个聊天工具需要一篇论文

论文开篇就很诚实:现代软件——从插件系统到「自进化 agent harness」——越来越依赖动态组合,但「其形式化基础仍然欠发达」。翻译成人话:所有人都在热加载代码,几乎没有人能证明卸载时会发生什么。

DSH 的赌注是:几百个彼此独立的插件组成的生态,只有在组合本身有真语义时才不会疯掉。不是约定,不是「请自觉清理现场」,而是运行时能强制执行的语义。论文的贡献是把这个问题切成两个干净的维度,然后两个都解掉。

两个维度:时间与空间

时间可组合性(temporal composability):组件被移除时,其副作用完全回退。装一个记忆插件,它加了提示词分段、设置页、也许还有一张数据库表。卸掉它——这一切干净消失,还是残留物从此在每个会话里闹鬼?答案是「可证明地消失」,你就有了时间可组合性。

空间可组合性(spatial composability):组件间的依赖被声明并被响应式管理。工具插件需要工具注册表先存在才能注册。在多数系统里这是脆弱的启动顺序;在空间可组合的系统里,插件声明它需要什么,运行时反应——世界满足之前按住它,满足之时激活它。

两者正交:只有撤销没有依赖管理是可能的(编辑器的 undo 栈),只有依赖管理没有撤销也是可能的(永不卸载任何东西的 DI 容器)。论文的动作是把两者都提升为可组合的运行时机制。

Revertible effects(可逆效应)

时间维的机制:每次上下文变换都携带一个被运行时追踪的逆。你的插件注册一个工具时,它不只是改了一个注册表——它执行了一个知道自己如何自我撤销的、被追踪的 effect:

ctx.effect(() => {
  const dispose = ctx.tools.register(myTool)
  return dispose // 逆操作:卸载时注销这个工具
})

在一个 effect 里注册三个工具、一个提示词分段和一个事件监听器,teardown 会按顺序回退它们。primer 的规则很直白:每个注册都该有 disposer。这也是热更新能是特性而不是事故的原因——卸载旧版本(所有逆操作执行),再挂载新版本。

Reactive coeffects(响应式协效应)

空间维的机制:每个组件声明一份coeffect 规格——它对上下文的要求——运行时在上下文变化满足(或破坏)该规格时通知它。用 dsh 的话说,就是 inject

export const name = 'greet-tool'
export const inject = ['tools']   // coeffect:apply() 之前需要 ctx.tools

插件保持 PENDING 直到 ctx.tools 存在。没有人编写启动序列;依赖就是配置。如果缺一个 provider,失败形态是一句可读的「该插件正在等待 X」,而不是启动深处的 TypeError

单一 context type

优雅的部分来了:effect 上下文(你作用于世界的地方)与 coeffect 上下文(你对世界的要求)被统一为单一的 context type。那个对象——每个 dsh 插件里的 ctx——同时是你的能力句柄和你的依赖声明面。从这里论文构建出组件的概念和一个动态组合演算,其元理论把保证「从单个组件提升到交错组件构成的整个系统」。

你不需要懂演算也能感受它。dsh 的配置文件读起来只是一列行,却能启动成一个自洽的产品——原因就是每一行都是这个演算里的组件,元理论保证了几百行交错时保证不塌。

你实际会用的词汇

上游 primer 的五个 idea 覆盖日常插件开发——每一个都是论文机制穿了件实用外衣:

  1. 插件是实现 Service 的对象——带 injectapply(ctx) 的函数,或被 Cordis 挂载生命周期的 Service 子类。
  2. 上下文是服务的仓库——服务认领稳定 key(ctx.toolsctx.llm);按 key 查找,绝不按 import。
  3. 依赖走 inject——加载顺序是被推导的,不是被编排的。
  4. 类型化事件做通信——通过 TypeScript declaration merging 声明,四种派发方式:
    模式 等待 顺序 有返回值
    emit 注册顺序
    waterfall 注册顺序
    parallel 并行
    serial 注册顺序
  5. 注册是可逆 effect——你加进去的一切都会出来。

写第一个拦截器之前必须消化的一个语义:waterfall 是 around-middleware。监听器收到 (...args, next);调 next() 是委托,不调而直接 return 是短路。对单决策事件,短路就是设计——拥有决策权的策略监听器直接返回;只做观察的监听器必须委托。把它弄反是经典的第一坑。

二十行 Cordis

来自上游教程最后一章——一个完整的、模型可调用的工具:

import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'greet-tool'
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'greet',
    description: 'Greet the named person.',
    parameters: { name: { type: 'string', required: true } },
    async execute(args) { return `Hello, ${args.name}!` },
  }))
}

所有 idea 都在里面:inject(coeffect)、apply(生命周期)、ctx.tools.register(对 keyed service 的被追踪 effect)。再加一个只做 ctx.on('tools/result', ...) 的第二个插件,你就得到了无耦合的观察——两个插件互不知晓,上下文把它们连起来。

dsh 如何用它

拉远看:dsh 是一堆这样的插件,启动时由有序层组合——bundle、你的 cordis.patch.yml、覆盖层。会话日志、工具管线、agent 循环、Web UI:全都是这棵树上的行,全都可以被 patch。dsh --profile web --dump-config 打印完整组合;任何一行都能被 patch 替换。资源地图索引每个插件时,它们接入的就是这套机制——这也是为什么这个生态里的插件冲突原则上可解,而不只是希望上可解。

去读论文本身

预印本(2026-08-13 稿)比本指南短而密:读摘要,再读演算一节,再读元理论。作者请读者引用最新版——它还在活跃修订。从 github.com/cordiverse/paper 开始,PDF 在仓库里。

想走动手路线的话:上游 Cordis 教程用七章、免 key、可运行地走完这一切。如果还没读,先读什么是 DeepSeek Harness,了解这篇论文脚下产品层的全貌。

官方参考