inject:让依赖决定加载顺序,而不是 YAML 行号
理解 Cordis 的 PENDING、依赖跟踪和可选服务探测,解释为什么 provider 更换会自动带动 consumer。
核心规则:需要服务才能运行的插件声明 inject;Cordis 让它等待服务就绪,而不是让开发者手写启动顺序。
这条规则把“谁先启动”的问题从配置文件的排列方式,改成了运行时可以观察和维护的依赖关系。
01 / 两行配置,顺序可以交换
提供方:
import { Service, type Context } from '@deepseek-ai/cordis'
export class GreeterService extends Service {
constructor(ctx: Context) {
super(ctx, 'greeter')
}
}消费方:
export const inject = ['greeter']
export function apply(ctx: Context) {
console.log(ctx.greeter.greet('world'))
}两项配置可以按任意顺序写入 cordis.yml。Consumer 的 apply 只有在 greeter 已注册后才运行。
数组形式只声明服务名,Cordis 会把它归一化为“服务名 → null 配置”。如果服务支持局部 intercept 配置,inject 也可以使用对象形式,把服务名映射到对应配置。
02 / PENDING 是等待,不是半启动
当依赖不存在时,Fiber 停留在 PENDING:
apply不会被调用,因此插件不会运行一半。- 进程不会因为一个等待中的 Fiber 自动保持事件循环活跃。
- provider 稍后挂载时,依赖满足,consumer 才会进入
LOADING;只有 config 和apply都成功,才会到达ACTIVE。
PENDING 不等于 FAILED:缺少依赖会等待,config 或 apply 抛错才会失败。这也是“没有输出”最常见的解释之一。先检查服务名和 provider 是否进入了当前组合,再检查插件代码。
03 / 依赖会在运行中继续被跟踪
inject 不是只在启动瞬间做一次的检查。如果 provider 被卸载或热替换,依赖它的 consumer 会一起卸载;provider 恢复后,consumer 会重新加载。
这和 effect 的所有权一起工作:consumer 的监听器、注册和外部资源在依赖消失时先撤销,避免它继续引用不可用的服务。
因此可以把 shell 的本地 provider 换成 E2B provider;所有 inject: ['shell'] 的插件不需要改代码,就会使用新实现。
04 / 硬依赖和可选依赖
硬依赖适合“没有它就没有意义”的插件:
export const inject = ['tools']可选依赖适合“有它更好,没有也能工作”的插件:
export function apply(ctx: Context) {
const greeter = ctx.get('greeter')
console.log(greeter ? greeter.greet('maybe') : 'no greeter available')
}不要把可选服务放进 inject,否则缺少 provider 时整个插件会合法地停在 PENDING。ctx.get() 是运行时探测,不会创建依赖边;provider 后来出现时,插件也不会因为这次探测自动重新运行 apply。
05 / 依赖关系比启动脚本更稳定
手写启动脚本需要维护拓扑排序、重试和卸载顺序;inject 把这些变化交给 Fiber 和注册表。增加一个 provider、替换一个 provider,或让一个服务短暂离场,都沿用同一套就绪语义。
在 DSH 中,工具、模型、会话和 Agent 能力因此可以被 profile 重新组合,而不需要让每个消费者知道完整的启动图。
下一步
更多文章
waterfall:用 next() 做拦截、包装与短路
沿着一条真实的 Cordis waterfall 链路,理解监听器如何委托下游、包装结果,或在拥有决策权时直接短路。
cordis.yml:把插件列表变成可修改的应用组合
理解 Cordis 配置项、稳定 id、group、isolate 与 patch 层,读懂配置文件如何定义一次运行。
组合与热重载(HMR):导入新模块,回卷旧 Fiber,再激活替换
理解稳定 id、配置组和 Cordis HMR 的卸载—重载流程,并学会定位始终停在 PENDING 的插件。
邮件列表
加入我们的社区
订阅邮件列表,及时获取最新消息和更新