为什么DeepSeek Harness选择了Cordis作为Agent的内核?

AI资讯 1小时前 charles
495 0

众所周知,DeepSeek Harness的内核是cordis,通过vendor"钉"进主仓库,附带 18 条本地补丁,改名 @deepseek-ai/cordis

它来自一个最开始和 AI 八竿子打不着的项目——Koishi,一个 QQ 聊天机器人框架。

一个聊天机器人框架的内核,为什么能给当红炸子鸡 DeepSeek 当 Agent 运行时的内核?

带着这个疑问,这两天我仔细琢磨了那篇配套论文、cordiverse/cordis 的源码结构以及翻了各家代码级拆解,大概是摸到了一些门道。

一、先讲讲,这颗"内核"是哪来的

cordis 的作者叫 Shigma,本名史一凡,北大人。

这哥们对 "shi" 这个音有很深的执念:本名 Yifan Shi,做的框架叫 Koishi,GitHub ID 叫 Shigma。(doge)

Koishi 是 2020 年发布的聊天机器人框架,在国内做 QQ 机器人的圈子里几乎人手一份。四年时间,社区给它攒了 4000 来个插件。

而 cordis 这个名字,来自拉丁语的"心"。用 Shigma 自己的话说:Koishi 的一切,都从 cordis 开始。

时间线拉出来,你会发现这是个伏笔回收的剧本:

2022 年 5 月,Shigma 把 Koishi 里的插件管理内核抽了出来,做成一个独立的元框架,就是 cordis,首次提交在 5 月 18 日。

2023 年,他给 Koishi 写了一篇设计文章,标题叫《可逆的插件系统》。记住"可逆"这两个字——它是后面所有故事的主线。

2026 年,Shigma 的名字出现在 DeepSeek V3 技术报告里,人进了 DeepSeek。然后就是 8 月 13 日:cordis 以 vendor 的身份进了 DeepSeek Harness 主仓库;同一天,一篇北大加 DeepSeek 联合署名的论文挂上预印本——《A Programming Paradigm for Spatiotemporal Composability》,时空可组合性编程范式。

再看几个数,感受一下这个项目的"体格"。

cordis 仓库总共 7,634 行 TypeScript,其中核心逻辑约 2000 行;运行时零依赖——是真零,只依赖 Node 和 TS 的内置能力。550 个 commit 里,90% 以上出自 Shigma 一人之手。

顺便一提,围绕 cordis 还有个自建的小生态:官方组织 cordiverse 名下,除了主仓库,还有配套的论文仓库(2,100 多 star),加上 database、webui、registry、server 等八个官方插件包。典型的自上而下产品化打法——不是只交一个 core 等社区自己长,而是先把参考实现铺满。

说白了,这是一个人用四年业余时间养出来的一颗内核。

Shigma 当年在 cordis 文档里留过一句话:

"我希望它能成为未来软件(至少是我开发的软件)的核心。"

四年后,DeepSeek 用一篇论文加一整个 Harness,替这句话盖了章。

那么问题来了——一个聊天机器人框架的插件内核,到底解决了什么了不起的问题,值得 DeepSeek 连人带代码一起收编?

二、插件系统的难题:能装,不能卸

插件系统这东西,相信大家都非常熟悉了。

往一个系统里动态塞一个模块,这个能力几十年前就有了。现在的 IDE 和浏览器里面非常常见。

但是插件难的根本不是"装",是"卸"

先看一个最常见的死法。

你给一个框架装了个插件,它注册了全局事件监听、开了定时器、占了文件句柄、改了全局状态。等你想卸载它的时候发现:监听还在响,定时器还在跑,句柄漏了,全局状态被污染了。

这就好比,如果退租的时候墙上还留着钉子,房东找谁说理去?租客早就跑了。系统也一样——写插件的人早就不维护了,留你一个人面对满墙钉子。

大多数系统的解决办法简单粗暴——重启整个进程。

在 Node.js 的长驻进程里,内存泄漏和僵尸监听器不是意外,是常态。插件装了卸、卸了装,反复几个循环之后,系统就慢慢"变味"了。表现出来就是:跑得越久越慢,重启一下包治百病。

这个问题,在 AI Agent 时代被放大了无数倍。

为什么?因为 agent 运行时把插件系统往一个它从没被设计过的方向上拧。

传统的宿主是静态的:编辑器在那儿,插件往身上挂,相安无事。而 agent loop 是一个带状态的多步流程机——它要跨几十轮对话维护上下文、调用工具、执行多步计划。你中途拔掉一个它依赖的组件,下游所有已经"答应"了这个计划的步骤,要么跟着变,要么当场崩。

文本编辑器可以容忍一个插件留下悬空引用,大不了界面闪一下。一个已经提交了多步计划的 agent 不能。

况且,现在大家想让 agent 自己改自己的运行时——装新工具、换新技能、挂新适配器。改动如果不能干净回滚,改坏一次,整个进程的状态就被污染了。

举个具体的场景。你让一个 agent 做接口压测,它发现自己缺一个压测插件,于是现场写一个、挂上去。听起来很爽对吧?但如果挂载这个动作注册了事件、占了句柄、改了状态,而后续卸载时留了一半垃圾,跑上几个小时之后,这个进程的内部状态就是一锅粥。

到那时,你甚至回答不了一个最基本的问题:现在系统里到底有什么?

这个问题在 agent 框架圈还有另一个变体。LangChain、AutoGen、CrewAI 这一批,核心循环、上下文管理、调度策略全锁死在框架内部——你能在边缘加工具、换模型,但想改 agent loop 的调度逻辑?要么 fork 整个框架,要么提个 PR 排队等人 review。想换掉 session 的存储方式?对不起,核心代码里写死了。

插件和框架之间,隔着一条"特权"的鸿沟。

看看市面上的基础设施框架,你会发现每家都只解了半道题:

NestJS 有依赖注入,但没有副作用追踪——写 NestJS 插件的人照样得手动清理 timer 和 listener。

pytest 那套 Pluggy 有 hook 系统,但插件之间不能声明依赖。

Effect-TS 有完整的 effect 模型,但它不是框架,生命周期得自己搭。

Vue 和 React 的 plugin 加 context,只覆盖 UI 层,够不到应用级生命周期。

cordis 的赌注就一句话:干净卸载,应该是框架在结构上保证的属性,而不是每个插件作者都要自觉重写一遍的纪律。

这条线划在哪,决定了后面所有架构设计的走向。

那 cordis 是怎么把"可卸"做成数学性质的呢?

三、时空可组合性,怎么理解?

《A Programming Paradigm for Spatiotemporal Composability》——时空可组合性编程范式。这篇文章把"动态组合"切成了两个正交维度,一个管时间,一个管空间。

时间维度:Temporal Composability,副作用可逆。

一个组件被卸载时,它产生的所有副作用——事件监听、文件句柄、内存分配、注册的命令——必须被完整逆转。系统恢复到"这个插件从来没来过"的状态,不多一分,不少一毫。

落到 API 上,核心就一个函数:ctx.effect()

它要求所有副作用操作都返回一个"清理函数"(disposer)。运行时把清理函数存进栈里,插件卸载时,按注册的逆序全部执行。写出来大概长这样:

ctx.effect(() => {
  const timer = setInterval(poll, 1000)
  return () => clearInterval(timer)
})

就这么几行。你注册副作用,顺手把"反悔的方式"交出去。剩下的——什么时候执行、按什么顺序执行、怎么保证执行——全是运行时的事,不劳插件作者操心。

你可以把它理解成租房:每往墙上钉一个钉子,就自动记一笔账;退租那天,按记账逆序把钉子全起出来、把胶印全擦掉,房间恢复到你入住前的样子。

相信懂 C++ 和懂 Rust 的朋友看到这里都不会陌生——这不就是 RAII 和 Drop trait 嘛。区别在于,cordis 把这件事从语言层提升到了运行时层。

语言级的 RAII 只能管住你自己写的代码。运行时级的 RAII,意味着两个从没见过面的团队、隔着时区的两个人写的第三方插件,卸载时照样干干净净。

在 Koishi 里,这件事被验证了四年:管理员从控制台停用一个插件,它对系统的影响原地撤回,其他插件接着干活;开发者改完插件代码一保存,被修改的插件重新挂载,缓存和连接纹丝不动。

空间维度:Spatial Composability,依赖反应式管理。

光能卸干净还不够。组件之间是有依赖关系的——功能插件依赖数据库驱动,数据库驱动依赖连接池,牵一发动全身。

传统 DI 容器(比如 Spring 那一派)的解法,是启动时把对象图装配好,一次成型,之后基本不再动。这对静态系统够了,对插件生态完全不够——插件是要随时来、随时走的。

cordis 的解法是"声明式依赖加反应式响应":插件不"接收注入",而是"声明需求"。运行时保证三件事——

B 依赖 A,B 一定在 A 就绪之后才启动;A 要停,B 一定先于 A 卸载;A 起不来,B 压根不启动。

变化时刻,当 A 被替换、重连或升级时,只有依赖真正发生变化的插件会被重启,依赖没变的插件纹风不动。

在 Koishi 生态里,这意味着你可以在线切换存储后端、重连消息适配器,而几百个插件里只有真正依赖它的那几个被重新激活。要知道,这些插件是几百个不同的作者独立写的,彼此之间唯一的协调机制,就是 cordis 的这条规则。

一套动态组合规则,真的能在开放生态里跑起来——这是论文最想证明的事。

底层还有个实现,叫 fiber。每个插件跑在一个 fiber 里,运行时给每个 fiber 维护一个"纪元"(epoch)——本质是所有依赖的指纹。依赖变了,指纹就变,插件被调度重载:先同步清空副作用列表,再逆序调用 dispose,中间用一把 inertia 锁保证重载和卸载不会并发打架。

两个维度合起来,产出一个工程上美妙的性质:路径无关性。

系统的最终状态,只取决于"开了哪些插件",跟这些插件以什么顺序、经历几轮装卸到达这个状态,没有任何关系。

为什么说"美妙"?因为绝大多数系统的内部状态,是跟操作顺序耦合的。先装 A 再装 B,和先装 B 再装 A,跑一段时间之后往往就是两个世界——注册表的顺序、缓存的内容、监听器的排列,全都带着历史痕迹。cordis 把这些痕迹全部装进可逆的 effect 里,装卸互为逆操作,历史被逐步抹平,只剩当下。

加载路径不影响终态——这就是热重载在数学上安全的全部前提,也是 agent 想改自己运行时还不留烂摊子的全部前提。

cordis 甚至自己实现了 Node 环境下的热重载:监听文件变化,备份模块缓存,清空,重新导入,重新注册插件,一套"备份-清空-重导入"的组合拳,跨 Node 22、23、24 三个大版本兼容。开发插件的人保存一下代码,插件原地换新,连接和缓存全都不动。

支撑这套范式的,是三个落地的机制,全部暴露在一个共享的 ctx 上下文对象上:

ctx.effect():可逆副作用,前面讲透了。

生命周期事件:ready / dispose / fork,挂载、销毁、分叉三个节点。

服务系统:插件的"贡献"以服务的形式注册进上下文,靠 Proxy 加 Symbol 命名空间做依赖路由——你写 ctx.foo,拿到的是当前作用域里那个正确的 foo,同名服务在不同隔离域里对应不同的实例。

Context 还支持三种嵌套操作:extend 继承、isolate 隔离、intercept 拦截,分别覆盖"局部定制、全局兜底"的三种玩法。会话隔离、请求隔离、不改实现就改配置,全靠这三板斧。

插件还自带配置解析:每个插件可以声明自己的配置模式,运行时沿原型链解析——子级覆盖父级,深度合并,父级兜底。这东西单看平平无奇,但它是 dsh 后来那套"四层配置管道"的物理基础。

数一数,cordis 一共给了五样东西:插件、生命周期与副作用、服务、事件、可配置。每一样单拎出来好像都听过,很多框架都有其中一两样。但五样拼在一起、互相咬合,才是论文标题里那个"时空可组合性"的完整体。

这里还有一个细节:cordis 的事件系统有五种派发模式——emit、parallel、serial、bail、waterfall——全部统一到一条内部路径上。前四种好理解,广播、并发、串行、短路。第五种 waterfall(瀑布流)是:一串监听器排成链,每个都拿到 next(),不调用就掐断整条链。

接下来看 DeepSeek 怎么在这颗内核上造发动机。

四、DeepSeek 在这颗内核上,造了一个什么发动机?

dsh 是个 49 个包的 TypeScript monorepo,约 1980 个 TS 文件,要 Node 22 以上才能跑。一条命令 npx @deepseek-ai/dsh web,浏览器里就起了一个 Agent 运行时,默认端口 3080。

standard 预设里都装了些什么?检视仓库、编辑文件、执行 shell 命令、检索本地文件和网页、维护计划、调用技能、委派子代理、执行审批策略。一个完整的 agentic coding 循环。

"产品的每一个部分都是插件——包括模型适配器、工具注册表、会话日志,以及 agent 循环本身——所以每一部分都能从配置里替换掉。这里没有一个特权内核可供修补。你扩展 dsh 的方式,是在其他插件旁边挂载一个新插件。"

—— DeepSeek Harness 架构文档

"没有一个特权内核可供修补。"

这句话的意义,要跟当前主流 Agent 产品结合对比才能掂出来。

Claude Code 和 Codex 允许你加工具、改提示词、挂 hook,但那个"读消息、思考、调工具、再思考"的主循环,是焊死在成品里的地基。你只能在地基留好的口子上挂东西。打个比方:Claude Code 和 Codex 是把模型和外壳焊死的成品手机,拿到就能用,想换颗螺丝得找原厂;dsh 不卖整机,卖的是图纸加全套可拆装模组——电池、摄像头、操作系统、屏幕,连"怎么打电话"的规矩都是一个个能拔能插的插件。

而 dsh 把地基本身也做成了插件:core/agent 定义接口,core/agent-loop 只是"实现该接口的默认驱动"。既然是默认的,就意味着可以换掉。

一个运行中的 dsh,本质就是一个 cordis Context:所有能力作为插件向 Context 注册服务、事件和 effect,最终由一份配置文件组装成可运行的 Agent。配置是一条四层管道——bundle 分发层、profile 补丁、用户主目录补丁、命令行 --patch,逐层叠加,后写覆盖。跑一句 dsh --dump-config,你这台机器实际启动成什么样,打印出来的任何一行都能被你的补丁替换。

换能力的方式,是改配置,不是改源码。这就是"一切皆插件"的操作入口。

仓库里目前带着四个预设:standard(常规任务全集)、code(对外叫 PTC 模式——工具列表塌缩成一个 run_code,模型直接写 TypeScript 程序来编排多步工具调用,五轮往返压成一轮)、minimal(最小环境,官方跑分用的就是它)、creator(创造模式,挂上 tool-cordis 和两个创作技能,让 agent 在运行时内省、试装插件、创作新 preset)。

还有个容易被一带而过的细节:预设是"每会话"级别的。同一个进程里,可以同时跑几个组装方式完全不同的 agent——一个揣着全套工具干重活,一个最小配置跑基准,各带各的工具和提示词分段,互不干扰。

连你浏览器里打开的那个 Web 界面,本身也只是一个默认挂载的插件。卸掉它,换一个自己写的,系统照跑。

在这套底座上,DeepSeek 盖了三根承重柱。

第一根:瀑布式工具流水线。

工具的每次执行,都走一条 cordis waterfall:tools/pre-execute 到 tools/execute,再到 tools/post-execute,落到 tools/result。链上每个监听器都拿到 next(),不显式调用,链路就断。

这一下就把所有横切关注点统一了:超时策略挂在 execute 上,沙箱提醒挂在 post-execute 上,审批在 pre-execute 返回时触发,外部 hook 桥直接挂 pre 和 post 两个节点。

这意味着"审批、沙箱、超时、hook"这些原本散落各处的安全逻辑,现在共享同一条主干道。想加一道新的关卡,挂一个监听器就行,不用碰任何核心代码。

更关键的一条:模型驱动的调用、workflow 脚本的调用、subagent 的调用、PTC 模式的子调用——全部过同一个 ToolRuntime.execute 闸门。PTC 的子调用想绕过审批和沙箱?门儿都没有。

第二根:会话日志是唯一事实源。

每一次 agent 运行都记进一份 append-only 日志:系统提示、推理过程、工具调用、返回结果、子代理调度、每一次上下文注入,全量留痕。

Turn 和 Step 都是日志事件,喂给模型的消息序列由 deriveMessages() 这个纯函数从日志投影出来。每次派发请求前,agent-loop 会拿 JSON.stringify 比对"即将发出的请求"和"从日志重建出来的结果",不一致,直接 fail。

换句话说,这个系统在机械上强制"模型看到的"和"日志记录的"完全一致。排障、调试、评测、合规,全靠这个底座。Agent 干了什么、看到过什么,事后可以完整回放——这是原则。

评测也一样受益。想知道模型从哪一轮开始跑偏?日志里逐轮回放。想知道某个工具调用为什么被拒?瀑布链上每一站都留了痕。想知道换个模型的对照组表现?同一个日志,换一个适配器重放就行。

第三根:Capability Seam,能力接缝。

可替换的能力被拆成三个角色:服务定义(接口)、服务提供方(实现)、消费方(模型面向的工具)。

威力最大的是"执行世界"抽象——文件系统和子进程共享同一个 provider。你把 provider 指向远端沙箱(仓库里已经有 E2B 的概念验证),Bash、终端、LSP 整体迁移,消费方零改动。

同一个逻辑推到极致,就是 subagent:一个接口底下并存着六七个 provider。其中两个是别人家的成品——subagent-claude-code 直接在会话工作区里调 Claude Agent SDK,subagent-codex 通过 codex 的 app-server 起临时线程。

模型无关也是写进 DNA 的:provider 目录里躺着 Anthropic、OpenAI、Bedrock、Vertex、Azure 和任意 OpenAI 兼容端点的适配器,近 40 个模型可换。默认是 DeepSeek,但没人拦着你接 Claude 和 GPT。

沙箱是操作系统级的真隔离,不是应用层"自觉":Windows 用 CreateRestrictedToken,Linux 用 Landlock 加 bwrap,macOS 用 Seatbelt。agent 在什么样的笼子里跑代码——这本身也是个配置项。社区连笼子都造好了三款:sandbox-micro、sandbox-mxc、sandbox-nono,对应三套隔离方案,挑一个换上就行。

其实,我之前挺好奇的,以 DeepSeek 的阵容和实力,为什么不自己造,偏偏是它?

五、为什么偏偏是 cordis?

这里我按照个人的理解推测和代入判断一下。

首先,自进化的前提是可逆。

一个会自我演化的 agent,需要三件事:动态加载(装得上新工具)、安全卸载(改坏了能回滚)、状态无关性(先装 A 还是先装 B,终态一致)。

市面上大多数框架做到了第一条。后两条,几乎没有人在框架层做到——直到 cordis 把它们做到了范式层。

dsh 仓库里有个包叫 packages/self-modification/,描述的是:"agent 检查并挂载它自己的插件。"当所有能力都是插件,而 agent 又能装插件,它就具备了改造自己的接口。

但是当前版本里,动态包是不可变的、仅内存态的,进程重启即失;所谓"改自身",是会话内的临时叠加,加上用文件工具把新 preset 写盘、重启挂载——不是运行中热替换当前装配。

然而方向我相信是明白无误的:一个能改自己运行时的 agent,只有在改动可逆时才值得拥有。改坏了回滚不干净,自进化就是自毁。cordis 的路径无关性,恰好是"自进化"这四个字的安全带。

其次,不重造已经验证过四年的轮子。

DeepSeek 大可以自己写一个插件系统。但自研的东西,上线第一天没有实战数据;cordis 带着 Koishi 四年、4000 来个插件、几百个互不相识的作者的验证。

"不同作者独立开发的插件,在开放生态里动态组合还能保持正确"——这是拿四年生产环境换来的。

再加上工程适配性:零运行时依赖,核心 2000 行,TypeScript 原生,塞进 monorepo 零摩擦。vendor 进来,打 18 条补丁,改名发包,一个下午的活。

反过来看自研的风险。插件系统的坑,全藏在边角案例里——并发卸载、循环依赖、部分失败、热重载的缓存一致性。这些坑,cordis 用四年时间和几千个社区插件替你趟完了。自研团队要用生产事故去趟,代价只会更高。

还有人才方面,Shigma 本人在 DeepSeek。Harness 团队负责人崔添翼,浙大计算机出身,在校拿了 6 次 ACM 亚洲区金牌,毕业后在香港和纽约的 Jane Street 干了 9 年,是梁文锋的浙大学弟,今年 3 月加入 DeepSeek 组建 Harness 团队。论文三作者横跨北大和 DeepSeek——一作史一凡,二作张伟是北大软件研究所的副教授,研究方向就是软件工程和编程语言。

这个阵容,不是"临时找个开源库用用"的配置。这是冲着"Agent 时代的运行时基建"去组的队。

最后,可换,比选对更重要。

当前,模型和 agent 的能力,离收敛还远得很。今天看着正确的 agent loop、正确的工具协议、正确的记忆方案,明天可能被一篇论文整个掀翻。

在这个阶段做基建,"押注哪个答案"是个伪问题,"保留更换答案的能力"才是真问题。Claude Code 把一切焊死,换来稳定;dsh 什么都不焊,换来一条永远敞开的退路。

DeepSeek 团队就那么点人,不可能独自把记忆、调度、沙箱、多智能体的所有可能性都探索一遍。把 harness 开源成插件市场,就是把这个探索问题外包给全世界。社区插件话题 dsh-plugin 下面,已经攒了近 300 个。

迁移的路也全铺好了。hook 层面,hooks-claude-code 直接吃你现有的 hooks.json;API 层面,DeepSeek 官方同时提供 OpenAI 格式和 Anthropic 格式两个 BASE URL——一堆原本只认 Anthropic 接口的客户端,改个地址就能指过来。两家主流 coding agent 的用户迁移成本,被压到接近零。

六、写到最后

当前官方在 GitHub 上暂不接收外部 PR,文档基本就是仓库本身,UX 还远远谈不上打磨完毕。

开源社区的规律从来是 1% 的贡献者养活 99% 的使用者。harness 这种需要开发者投入真金白银时间的项目,从 star 到插件之间隔着的不是热情,是真实的工程收益。收益什么时候出现?要么等生态里长出"杀手级插件",要么等 DeepSeek 把模型能力和框架价值的联动真正跑通。

Claude Code 走的是苹果那条路:闭源、垂直整合、体验可控。dsh 摆明了走安卓那条路:图纸全公开,等全世界来盖房子。

房子最后有没有人盖,DeepSeek 说了不算。但如果这条路径跑通了,dsh 就是 Agent 时代的 npm,Agent 时代的 Kubernetes——比跑分领先几分,黏得多。基建的生意,从来不是赢在最快,而是赢在最难离开。

模型决定一辆车能跑多快,
harness 决定这辆车能不能边跑边换轮子。

而敢边跑边换轮子的车,
才开得进没人画过地图的地方。

· · ·

往期推荐

· 一文解读 DeepSeek Harness 到底是个啥以及想革了谁的命?

· 别只盯 Attention 了,FFN 其实是大模型真正的"知识库"!

· DeepSeek V4 为什么放弃了 MLA 多头潜在注意力机制?

如果你觉得有用,欢迎转发给同样在关注 Agent 架构的朋友

♥ 喜欢作者

原创不易,感谢支持


登录查看剩余 70% 内容

版权声明:charles 发表于 2026年8月24日 pm1:58。
转载请注明:为什么DeepSeek Harness选择了Cordis作为Agent的内核? | AI工具大全&导航

相关文章