第四课:Cordis 如何把插件组织成一个可运行的 Agent

上一课,你学的是"任务按照什么顺序执行"。这一课,我们换一个观察角度:

执行循环、模型适配器、文件工具、权限策略这些代码,怎样接入 Harness,又怎样相互协作?

我们继续围绕"读取文件、计算平均值"讲。先理解现有架构,不要求你编写插件。

一、先分清:Cordis 和 Agent Loop 各自负责什么

你已经知道,Agent Loop 会推进模型请求、工具调用以及后续请求。

Cordis 负责的是这些组件的加载、依赖、通信和清理。

对象 在这个例子中负责什么
模型 根据本次输入,输出文字或工具调用请求
Agent Loop 请求模型、分派工具调用、记录结果、决定是否继续执行
Cordis 让执行循环、模型适配器、工具等插件按依赖关系运行,并管理它们的生命周期

所以,Cordis 本身不是"另一个负责推理的 Agent"。

在 DSH 中,连 Agent Loop 本身也是通过插件提供的。运行中的产品由插件组合而成,不是只有你后来添加的业务功能才叫插件。官方架构说明

这也解释了一个重要区别:

插件之间的依赖关系,不等于业务任务的执行顺序。

"文件工具依赖文件系统服务",描述的是运行条件;"先读取文件,再计算均值",描述的是任务过程。

二、先拆开"读取文件"这个功能

上一课为了理解执行顺序,我们把它统称为"读取工具"。

现在往内部看,就能发现不同职责。

以下以文件工具这条路径为例;通过 Shell 命令读取文件,属于另一条调用路径。

要解决的问题 官方代码中的对应角色
向模型提供怎样的文件操作入口? dsh-tool-fs:提供文件读取、写入、编辑工具
文件访问要遵循什么程序接口? dsh-fs:定义 ctx.fs 服务及相关类型、约定
怎样实际访问本地磁盘? dsh-fs-local:文件系统服务的一种具体实现

注意:这是代码包的职责划分 ,不是说表中每个包都必须作为一个独立插件条目启动。实际部署还可能包含沙箱、文件操作策略等组件。官方文件系统说明

把它对应到一次读取:

  1. 模型提出文件读取的工具调用。
  2. Harness 的工具运行机制找到对应工具实现。
  3. 文件工具通过 ctx.fs 使用文件系统能力。
  4. 当前文件系统提供方完成实际读取。
  5. 文件工具整理结果,交回执行循环。

这里最关键的是第三步:

文件工具使用的是 ctx.fs 这份接口约定,而不是必须直接依赖某个本地磁盘实现。

因此,如果有另一个实现遵守相同约定,就可以通过组装配置更换提供方,让工具继续使用同一个接口。实际替换仍需验证路径、权限、错误处理等行为是否符合约定。

官方把这种职责划分称为:

  • Definition:定义接口。
  • Provider:实现接口。
  • Consumer:使用接口。

文件工具就是 Consumer,本地文件系统后端就是 Provider。

但这不是要求你以后每个功能都拆成三个 npm 包。官方明确说明:只有需要独立演进或替换时,才值得分包;简单工具可以在一个包内实现。官方能力角色设计

三、Plugin、Tool、Service,究竟有什么区别?

这是这一课最需要建立的认识。

1. Plugin:由 Cordis 加载和管理的扩展单元

一个插件被加载后,可以:

  • 提供一个服务;
  • 注册一个或多个工具;
  • 注册事件监听器;
  • 提供提示词内容;
  • 组合其他插件。

它不一定包含面向模型的工具。

例如,一个只负责记录运行情况的插件,可以监听事件,却不向模型增加任何可调用能力。

在代码层面,Cordis 支持函数、类,以及带有 apply 方法的对象等插件入口形式。官方插件注册接口

因此:

"安装了一个插件"不等于"模型多了一个同名工具"。

还要看这个插件实际注册了什么,以及这些注册对当前 Agent 是否可见。

2. Tool:提供给模型使用的具名调用入口

以文件读取为例,模型需要知道:

  • 工具叫什么;
  • 能做什么;
  • 接受哪些参数。

Harness 还需要持有这个工具的实际执行代码。

这两部分不是一回事:模型获得的是工具定义,不是自动获得工具实现的源代码。 工具运行时负责注册、查找和执行;向模型提供 schema 时,会排除执行回调等内部内容。官方工具运行时参考

因此,日常使用时更准确的说法是:

模型请求调用插件注册的 Tool,由 Harness 分派执行。

而不是"模型把整个插件运行了一遍"。

3. Service:插件之间可以使用的程序能力

文件工具需要读文件,就使用文件系统服务。

执行循环需要请求模型,就使用模型服务。

这里的 Service 通常是运行时中的程序接口,不意味着你必须另外部署一个 HTTP 服务或微服务。它的具体实现可以访问本地资源,也可以连接远程系统。

对应关系可以这样记:

Plugin 是扩展单元;Tool 是面向模型的调用入口;Service 是插件之间使用的程序接口。

一个 Plugin 可以提供 Service,也可以使用已有 Service 来注册 Tool。

四、ctx 是什么?为什么源码里到处都有它?

你以后读 DSH 源码,会不断看到:

TypeScript 复制代码
ctx.fs
ctx.tools
ctx.llm
ctx.sessions

先把这些表达式翻译成具体意思:

表达式 在程序中取得什么
ctx.fs 当前上下文中的文件系统服务
ctx.tools 当前上下文中的工具注册与执行服务
ctx.llm 当前上下文中的模型调用服务
ctx.sessions 当前上下文中的会话管理服务

ctx 是 Cordis 的 Context 对象。插件通过它访问服务、事件和生命周期管理能力。官方 Context 参考

这里要特别区分两个"上下文":

模型上下文,是一次模型请求携带的提示词、历史消息、工具结果等信息。

Cordis 上下文 ctx,是程序运行时用于访问服务、注册能力和管理资源的对象。

它们不能混用:

  • 把服务放到 ctx 上,不代表模型自动看见它。
  • 把一段文字放进模型上下文,也不代表程序里自动出现对应服务。

举个具体例子:

即使程序能够通过 ctx.fs 读取文件,如果没有适当的工具入口、没有对当前 Agent 暴露相应工具,模型也不能直接用 ctx.fs 发起文件操作。

另外,ctx 不是"所有插件永远共享同一份实现"的全局字典。Cordis 支持子上下文和服务隔离;经过明确配置,不同上下文中的同名服务可以解析到不同实现。

不过,服务作用域隔离不等于操作系统级安全沙箱。这两种隔离解决的问题不同,我们后面会专门展开。

五、inject:插件怎么知道依赖已经准备好了?

现在考虑一个实际问题:

文件工具开始初始化时,工具注册表或文件系统服务还没准备好,怎么办?

Cordis 通过声明服务依赖来处理。

你以后会看到这种代码:

TypeScript 复制代码
export const inject = ['tools']

它表达的是:

这个插件需要 tools 服务;服务未就绪时,不执行这个插件的初始化入口。

这里我们只读这一行,不需要创建文件运行。

如果一个插件需要多个服务,就声明多个依赖。文件工具这一类功能,至少需要考虑工具注册能力与文件访问能力;具体完整依赖以对应源码为准。

Cordis 会依据依赖是否就绪决定插件能否激活,而不是要求你靠手动排列一长串启动调用来保证顺序。官方服务与依赖说明

这里有两个容易混淆的"依赖"

npm 包依赖:代码能否被找到、导入。

Cordis 服务依赖:运行时需要的能力是否已经就绪。

例如:

  • 相关 npm 包已经安装;
  • 但没有加载对应服务提供方;
  • 消费该服务的插件仍然可能无法激活。

所以,inject 不负责安装 npm 包,也不会替你填写 API Key。

还有一个边界:

服务已就绪,不代表后续每次业务调用都一定成功。

模型服务可以已经可用,但某次远程请求仍可能超时。

必需服务运行中消失了怎么办?

如果某个必需服务的提供方被卸载,依赖它的插件会被卸载、清理;服务重新可用时,相关插件可以重新加载。

这是依赖管理的一部分,不是只在启动时检查一次。

因此,"更换底层服务"可能影响依赖它的组件,不能把热替换理解成任何任务都保证无中断。

六、Event:怎样参与执行过程,而不用修改每个工具?

现在增加一个需求:

每次工具执行完成后,都要记录一次运行情况。

如果在每个工具实现里分别添加记录代码,文件工具、Shell 工具、网络工具都要修改。

DSH 已有相应扩展点,其他插件可以监听它们,从而参与运行过程。

但"事件"不能一律理解成"发一个通知就结束了"。这里先认识两种机制。

1. 通知型:告诉监听者发生了什么

例如,工具运行时的 tools/result 可以供监听者观察最终工具结果。

使用 emit 模式分发的事件,会同步调用监听器,并忽略监听器返回值。

这类机制适合观察、记录等用途。不能随便在监听器里返回一个 false,就认为能够取消已经完成的工具操作。

2. 环绕型:让监听者参与处理过程

工具执行前后,还有使用 waterfall 模式的扩展点。

这类监听器可以包装后续处理。你以后会看到一个重要参数:

复制代码
next

调用 next(),表示把控制权交给后续处理;不调用,则会短路这条处理链。是否允许拒绝、怎样返回结果,要遵守该事件的具体约定。官方事件 API

注意这个名字:

这里的 next() 不是"开始下一个 Step"。

它只是继续当前事件的后续处理。一个 Step 内部,就可能经过多个这样的扩展点。

再区分:运行时事件与会话日志事件

下面两个名字很相似,但不是同一个东西:

  • tools/result:Cordis 工具运行时的结果观察事件。
  • tool/result:Session 日志中的工具结果事件类型。

如果你要观察日志中的 tool/result,应监听 session/event,再检查收到事件的 type

不能因为日志里有某个事件类型,就假设存在同名的 Cordis 事件。官方事件系统:会话记录的区别

到这里,服务和事件的使用区别就清楚了:

要直接使用一项能力,调用服务方法;要观察或参与已有处理过程,寻找对应的事件或策略扩展点。

七、生命周期:插件退出后,为什么不能留下旧注册?

假设某个插件加载时,注册了一个工具和一个事件监听器。

你修改代码后,再加载新版本。

如果旧注册没有清理,可能出现:

  • 同一个事件被重复处理;
  • 新旧逻辑同时运行;
  • 旧实例仍持有连接或其他资源。

因此,插件不仅需要"怎样开始",还需要"怎样退出"。

Cordis 为插件的每次运行实例管理生命周期。这个实例在文档里叫 Fiber ;现在先把它理解成"某次插件加载对应的运行实例",不要与 Turn、Step 混淆。官方 Fiber 参考

生命周期中发生什么?

对于同一插件实例的一次激活,通常是:

  1. 检查必需服务是否就绪。
  2. 执行初始化入口。
  3. 注册工具、服务、事件监听等能力。
  4. 在运行期间处理相应调用或事件。
  5. 卸载时撤销注册、释放资源。

每次调用一个 Tool,不等于重新初始化整个插件。

如果发生重载或依赖变化,则可能开始新的一次激活过程。

哪些东西会自动清理?

通过 Cordis 管理的工具注册、事件监听等,会随所属插件卸载而撤销。

自定义资源则需要提供清理逻辑。例如,插件打开了一个连接,就应把关闭连接的操作交给生命周期管理机制。ctx.effect() 是处理这类资源的方式之一。官方插件与生命周期说明

这里必须避免一个误解:

自动清理,不是自动理解任意代码产生的全部影响。

更不是业务回滚:

  • 撤销工具注册,不会自动删除该工具之前生成的报告。
  • 移除监听器,不会自动撤回它之前发送的请求。
  • 关闭数据库连接,不等于撤销之前已经提交的数据。

这与上一课"停止执行不等于撤销结果"是一致的。

八、本课实操:查看你的 Harness 是怎样组装的

这次实操只查看配置,不卸载插件、不改权限。

如果你之前使用 npx.cmd 启动,可以查看配置:

TypeScript 复制代码
npx.cmd @deepseek-ai/dsh --profile web --dump-config

如果你使用的是全局安装、已经可以直接执行 dsh

TypeScript 复制代码
dsh --profile web --dump-config

两种方式选与你现有安装一致的那一种。如果之前固定了包版本,这里继续使用同一版本,不需要为了本课升级。

--dump-config 用于预览组装后的配置;其中还可能保留运行时才解析的表达式。它不是向模型发送任务,也不是所有插件已经成功激活的证明。官方配置预览说明

你这次只看三件事

第一,找插件入口。

关注条目中的 name,看它指向哪个包或模块。不要把它与模型看到的工具名称混为一谈。

第二,找配置归属。

关注 idconfig:哪个配置属于哪个插件条目。

这里的插件条目 id,也不是你之前配置模型连接时填写的 Provider ID。

第三,尝试定位与 toolsfsllm 有关的条目。

然后用本课的概念判断:

  • 哪个负责工具注册或分派?
  • 哪个提供底层能力?
  • 哪个使用这些能力?

不要求你现在解释整个配置树。某些代码包只定义接口,某些插件可能在创建 Agent 时才被进一步挂载;找不到一个名称,不能直接判定安装有问题。

如果分享输出,只贴相关条目,并去掉密钥、令牌、私有地址等敏感内容。

相关推荐
Summer-Bright40 分钟前
深度 | GPT-6 Astra 的相变:从「会答」到「会做」,OpenAI 把对齐做成了护城河
人工智能·gpt·ai·astra·gpt-6
artificiali42 分钟前
880 第4章多元
人工智能·算法
xwz小王子1 小时前
拒绝视频生成,深度跃迁提出具身基座模型全新路线
人工智能·深度学习·机器学习
奈斯先生Vector1 小时前
AIGC 视频生成实战:用 Kling Video 拆解文生视频、图生视频与完整工作流
开发语言·人工智能·windows·python·aigc·音视频
m0_739312871 小时前
【自动驾驶/机器人控制】之坐标系&运动学仿真代码工程分析(一)
人工智能·机器人·自动驾驶
桃西西呀1 小时前
用 AI 写得更快,上线却容易炸?拆解 AI 编码生产力悖论的 5 个机制,附 9 个坑的自检清单
人工智能·llm·ai编程
用户3610588626121 小时前
Flink基础之有界与无界流详解:批流一体的底层逻辑
大数据·flink
derekwang851 小时前
驾驭 AI · AI Harness Engineering · 契约层设计
人工智能·ai编程
月华路1 小时前
《模型不玄学》第14章 标签、损失与样本权重
人工智能·算法·机器学习