CORDISDSH 的心脏第 08 章 · 思想五 · 落地全书目录术语表读论文原文 ↗
第八章 · 思想五 · 落地

从论文,
到 24 万 star

前四章是图纸,这一章看它怎么变成真东西:Cordis 用五个动作实现了整套理论,Koishi 跑了四年,dsh 把它装进了一个 agent。

约 7 分钟 · 上一章 07 思想四 · 一生 · 下一章 09 抄作业

题图插座在墙上,灯可以换。

Cordis:五个动作

论文第 5 章讲实现。Cordis 的核心库,说到底只给了组件五个动作:

动作 做什么 对应
ctx.effect 做一件事,当场交回执 04 回执
ctx.set / ctx.get 在总账上登记一个键 / 读一个键 05 插座
ctx.isolate 让一个名字在子环境里指向私有的一份 05 隔离
ctx.intercept 在读依赖的路上加一道规则 05 拦截
ctx.use 按一份说明书装上一个组件,得到一个 fiber 07 一生

其中最要紧的是第一个。Cordis 里一切对语境的改动,都只走 ctx.effect 这一个入口 p.59:登记一个键是它,装上一个组件也是它。入口只有一个,回执就一张都漏不了。第 06 章说的「一扇门」,在代码里就是这一个函数。

声明式加载器和热重载不在核心里,是另外两个包,以组件的身份挂上去。

Koishi:四年,四千个插件

理论站不站得住,要看它在真实世界里跑得怎么样。论文拿 Koishi 当证据 p.69:

  • 拆得干净:在控制台停用一个插件,它的改动当场撤回;开发时改完代码一保存,插件原地重载,系统其余部分的缓存和连接都还在 p.70。
  • 接得稳:Koishi 的插件之间有真正的依赖网,适配器提供各个聊天平台,数据库驱动提供存储,功能插件声明依赖再去用。运行中换掉存储后端,只有依赖真的变了的插件才重新上岗;依赖不在的插件安静待命,不报错 p.70。
  • 不挑环境:Koishi 的网页控制台本身是第二个独立的 Cordis 应用,跑在浏览器里 p.69。同一套模型,服务器上能用,浏览器里也能用。

这些插件出自几千个互不认识的作者。论文最看重的,正是「独立写成的代码之间,组合照样成立」。

dsh:墙上一排插座

dsh 的 55 个包,每个核心能力都占着一个固定的 ctx 键:ctx.llm 是模型适配器,ctx.tools 是工具注册表,ctx.sessions 是会话记录,ctx.agentLoop 是默认的思考循环,还有 ctx.fs、ctx.shell、ctx.sandbox 等等。

插件之间只认这些名字,不直接引用彼此的代码。所以「一切皆插件」不是口号:连 AI 的主循环,也只是插在 ctx.agentLoop 这个插座上的一个插件,和别的工具一样能被换掉。

Cordis 本身,被整个搬进了 dsh 仓库的 vendor 目录,连同加载器、热重载、配置校验等一共 9 个目录。dsh 不跟外部版本走,这颗心脏由它自己维护。

图 8.1 · 插座、电源、灯

一个能力,三个角色

dsh 的文档把一个能力拆成三个角色,叫能力接缝:声明接口的、实现接口的、使用接口的。

换成图上的东西:墙上的插座是接口(一个 ctx 键),中间那个电源盒是提供者,亮着的灯是使用者。灯只认插座,不认电源盒。电源盒随便换,灯照样亮。

演示 8.1 · 可以点换掉插座背后的电源
    配置 · cordis.patch.yml(示意)fs: local # 本机磁盘
    提供者
    本机磁盘供电中
    远程沙箱 #b7备用
    插座
    ctx.fs接口不变
    文件系统
    插在上面的工具
    Bash 命令在岗
    跑在:本机
    终端在岗
    跑在:本机
    代码补全在岗
    跑在:本机
    读写文件工具在岗
    跑在:本机
    旁白这是 dsh 里文件系统这个能力的样子:中间是插座(ctx.fs),左边是给插座供电的提供者,右边是插在上面的四个工具。按「开始」,看现在谁在供电。
    按主按钮一拍一拍往下走

    三层配方:档案、捆绑包、补丁

    dsh 启动时组装哪些插件,由三层配置决定:

    层 是什么 例子
    Profile 档案 一套命名的组合,决定启动什么 dsh web 就是 dsh --profile web
    Bundle 捆绑包 分发单位,包自己声明里面有什么 dsh-base 是公共底层;dsh-web-app 往上加浏览器界面
    Patch 补丁 你自己的覆盖文件,按 id 替换或插入一行 在 cordis.patch.yml 里把 fs 换成沙箱

    运行 dsh --profile web --dump-config,能打印出整棵启动树;打印出来的每一行,都能被你的补丁顶掉。这就是第 07 章「声明式配置」落到产品里的样子。

    会话日志:唯一的事实

    dsh 还有一个设计,和第 06 章的「一扇门」是同一个思路:模型看到的上下文,完全从持久化的会话日志推出来。规矩是「模型看得见的,一定已经记下了」,运行时会检查每一次模型请求都能从日志完整重建。

    好处是只有一份真相:调试、从某一轮分叉出新会话、回放、换一个界面,全都从同一份日志出发。

    这里还有两个时间单位值得知道。step 是一次模型请求,加上它引发的工具调用;turn 是从接下一条输入,到再没有欠着的事为止,可以包含零个或多个 step。一轮对话就是一串环节:开始、请求模型、执行工具、结束。每个环节都能挂监听器,加工后交给下游,或者直接拍板。拦截请求、改写工具调用,都在对应环节上挂一个就行。

    借鉴之前,看清两件事

    第一,dsh 还处在 developer preview,README 用大写字母警告会有破坏兼容的改动。借它的思路,别直接抄它的接口。

    第二,论文讲的是 Cordis 的理论,dsh 是建在 Cordis 上的产品。本章关于 dsh 的内容来自它的 README 和架构文档,不是论文的结论。

    带走这一句

    能力做成插座:接口不变,提供者随便换,用的人只认名字。

    请把这个项目里可替换的能力(文件读写、执行命令、调用模型、存储等)改造成「能力接缝」:
    1. 每个能力定义一个接口,用一个固定的名字注册;
    2. 使用方只按名字取接口,不直接 import 具体实现;
    3. 实现作为提供者注册到这个名字上,切换实现只需要改一行配置;
    4. 提供者被替换时,使用方自动重新接上;
    5. 说明改造后,把文件系统换成沙箱需要改哪一行。