Skip to content

插件生命周期:能力归属、作用域与工作结算 ​

工具注册、文件服务和 Agent 工作各有所有者。Cordis 回收插件贡献,scope 限定可见性与登记归属;在途执行的取消和等待由工作所有者负责。

登记归属与文件能力依赖

箭头表示标注的调用或依赖关系;点击组件定位正文。完整关系见下方文字说明。

文字关系说明与章节链接

scope →(贡献归属)→ tool-fs;tool-fs →(登记工具)→ tools;tool-fs →(消费服务)→ ctx.fs;fs-local →(提供服务)→ ctx.fs;AgentHandle →(清理所属层)→ scope。

scope · tool-fs · tools · AgentHandle · ctx.fs · fs-local

工具注册与文件提供者分别持有能力 ​

启动配置包含 dsh-tools、dsh-fs-local 和 dsh-tool-fs。dsh-tools 提供工具注册表 ctx.tools,dsh-fs-local 提供文件服务 ctx.fs,dsh-tool-fs 使用这两个服务注册 read 等工具。ctx 是插件收到的上下文对象,插件通过它查找服务、注册工具和监听事件。

// 工具插件登记定义,返回可撤销注册;模型可见描述由注册表参与组装。
const dispose = ctx.tools.register(definition)

这三个包承担不同操作:工具注册表保存名称与执行器,文件提供者负责磁盘访问,文件工具把模型参数转换成文件服务调用。服务定义约定方法和类型,提供者实现方法,消费者调用方法;本例中的文件工具就是文件服务的消费者。

消费者通过 inject 声明必需服务。Cordis 在依赖就绪后激活相应插件,因此配置中写有 dsh-tool-fs,并不等于 read 已可用:缺少必需服务时,消费者仍需等待。Cordis 管理每个插件运行实例的依赖和退出过程,这个运行实例称为 fiber。

工具分派依赖可替换的文件服务 ​

插件完成注册后,工具注册表可以向模型请求提供 read 的名称、说明和参数。模型返回 read(README.md) 时,AgentLoop 把调用交给 dsh-tools,后者分派给 dsh-tool-fs;文件工具再调用 ctx.fs,由 dsh-fs-local 访问磁盘。文件工具调用的是文件服务的方法,不需要直接导入本地文件实现。

这也说明替换提供者为什么有意义:只要新的提供者满足相同文件服务约定,文件工具就可以继续使用同一套调用方式。替换过程仍须正确处理旧实例和依赖关系;修改配置后是否已应用、是否需要重启,应以装配结果为准。

本例为应用级注册。某项工具最终是否对某个 Agent 可见,还取决于注册作用域和工具限制;注册成功不等于所有 Agent 都能调用。

权限策略加入调用前的事件链 ​

直接调用 ctx.fs 用于读取文件;执行前审批等附加行为则通过事件接入。例如工具执行链发出 tools/pre-execute,权限插件监听这个事件,参与是否允许本次调用的决定。这样文件工具仍负责读取,权限插件负责自己的检查,两者不需要复制 AgentLoop。

// 权限等插件在执行前参与允许、拒绝或审批;需委托后续监听时调用 next()。
'tools/pre-execute'

ctx.on 把监听器登记到插件所属的上下文,并关联清理操作。对于 waterfall 事件,监听器调用 next() 才会继续执行后续监听器;直接返回会截断后续处理。审批插件可以在自己负责的决定上截断,单纯记录信息的监听器则应继续委托。若遗漏 next(),后面的插件即使已加载,也不会收到这次处理机会。

卸载回收贡献并由所有者结算工作 ​

现在关闭 dsh-tool-fs,并假定配置变更已经应用到运行中的应用。该插件注册的 read、write、edit 等工具应随实例退出而撤回。随后重新组装模型请求时,不能继续把这些已撤回的工具当作可用能力;已经发出的模型请求不会因此被改写。dsh-fs-local 是另一个插件,关闭文件工具不等于也关闭文件服务,其他消费者是否继续工作取决于各自的依赖。

注册需要清理操作。例如工具注册 API 返回的 disposer 用于撤销注册,ctx.effect 关联资源的创建与清理,ctx.on 关联监听器的注册与移除。插件退出时,框架执行这些清理;插件作者仍须把自己创建的注册和资源正确纳入管理。

撤回注册只阻止后续继续使用该注册,不能证明先前的读取、模型请求或后台任务已经停止。拥有这些工作的组件还需要发出取消、等待工作结束,再释放相关资源。例如 Agent 的拥有者应等待其循环和工作结束。同一进程中不响应取消的代码仍可能拖延退出,依赖注入不会强制终止它。

重新激活建立新一轮注册 ​

重新启用文件工具时,Cordis 再根据依赖建立可运行的插件实例,文件工具重新注册 read 等能力。下一次模型请求组装时,工具注册表据当前注册和 Agent 的可见范围生成工具描述。先前的会话记录仍是历史事实,不会因为卸载、重载工具而自动删除。

如果旧实例没有撤销监听器,重新加载后就可能对同一个事件处理两次;如果资源没有释放,旧任务也可能继续占用连接或进程。可撤销注册的作用,是让每次启用对应自己的注册,每次退出撤销自己创建的注册。配置操作若只写入磁盘、尚未应用,以上运行变化也尚未发生;需要重启的组合应重启后再确认。

Agent 作用域承载贡献与生命周期 ​

文件工具也可能只为某个 Agent 注册。例如插件通过该 Agent 的 agent.ctx 注册一个专用工具,这个注册只对该 Agent 的作用域可见。dsh-scope 将注册的可见范围和存活时间关联起来:关闭该 Agent 的作用域时,撤销其中拥有的工具、提示词片段和监听器,不应连带删除其他 Agent 的独立注册。

// 组合方为 Agent 等拥有者建立作用域,关联注册的可见范围与存活时间。
const scope = createScope(ctx, agent)

// 作用域拥有者等待清理其中的注册;作用域本身不构成文件或进程沙箱。
await scope.dispose()

dsh-agent 提供 AgentHandle;拥有者调用并等待 handle.dispose(),以完成该 Agent 的停止与清理。调用 agent.cancel() 主要用于取消当前工作,并不等于销毁 Agent,也不等于卸载应用级插件。

// 仅拥有 AgentHandle 的组件销毁 Agent,等待循环停止、注销并展开作用域清理。
await handle.dispose()

因此,排查一个工具为何消失时,需要区分三个操作:停用提供工具的插件、结束拥有专属注册的 Agent、修改工具可见限制。作用域管理的是注册归属与可见性;它不是文件或进程沙箱,也不会让任意 ctx 服务自动变成每个 Agent 各有一份。

辅助示例

观察同一项文件读取能力的变化:启动文件服务与文件工具 → read 完成注册 → 模型调用 read 读取 README → 停用文件工具并撤回注册 → 重新启用并建立新注册。每一步分别对应服务是否就绪、工具是否可见、执行器是否可调用和旧工作是否已结束;配置文件中的启用状态不能单独回答这些问题。

设计取舍 ​

文件工具依赖 ctx.fs,而不是固定依赖本地实现,因此文件后端可以独立替换;工具和监听器随所属实例退出而撤销,因此重载时不必另行维护全局清理名单。代价是插件作者必须明确资源归属,并为注册、请求、进程和子任务实现相应的清理。排错时要分别确认有效配置、依赖就绪、作用域可见范围、注册状态与在途工作,不能仅凭插件名称出现在清单中判断能力正常。

相关包与插件
资料来源与核验范围

本章依据固定版本的官方文档整理,设计取舍根据所述机制分析;核验范围为文档,不包含运行实测。

图示
100%双指缩放 · 滑动查看