很多插件的第一版都能运行,第二版开始却出现重复监听、卸载后仍占资源、服务加载顺序不稳定。原因通常不是 TypeScript 写错,而是把 Cordis 当成普通“初始化脚本”了。
在 DeepSeek Harness 中,Cordis 是能力组合层。插件通过 ctx 获取服务、注册事件和贡献能力;依赖、生命周期与清理由框架管理。
先建立四个概念
apply(ctx) 是插件入口;ctx.<key> 是稳定服务接缝;inject 声明依赖;effect 是随插件装载和卸载而存在的副作用。
一个只观察工具结果的插件可以很小:
import type { Context } from '@deepseek-ai/cordis'
import '@deepseek-ai/dsh-tools'
export const name = 'tool-audit'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.on('tools/result', (exec, result) => {
console.log(JSON.stringify({ tool: exec.name, blocks: result.content.length }))
})
}
ctx.on() 注册的监听器会在插件卸载时自动移除。数据库连接、Socket、子进程等无法自动回收的资源,应通过 ctx.effect() 返回清理函数。
选错事件,比没有事件更危险
Cordis 提供多种调度语义:
emit是广播,所有监听器同步收到;bail遇到第一个有效结果就停止;serial按顺序等待,可提前结束;waterfall是可包装的处理链,监听器必须调用next()才会继续。
官方特别提醒 waterfall 不调用 next() 会短路。这适合网关和策略拦截,但也可能让模型请求或工具执行悄悄停在你的插件里。开发拦截器时要分别测试“放行、改写、拒绝、下游报错”四条路径。
实时事件和持久事件不要混淆
agent/request、tools/result 等是进程内 Cordis 事件;turn/*、step/*、tool/call、tool/result 等是写入 Session 的持久记录。想在当前进程做监控,可监听实时事件;想在重启后重建事实,应监听 session/event 并检查 event.type,或直接使用 Session 的投影能力。
一个实用判断是:如果这条信息决定恢复后的行为,它就不应只存在内存事件中。
自定义工具的设计顺序
先定义最小输入 Schema,再定义执行边界,最后设计展示。工具描述要告诉模型什么时候用、何时不要用;参数要尽量封闭,避免让模型传任意 Shell 字符串;结果要有稳定结构,并限制返回体大小。
例如“查询构建状态”应接收 buildId,而不是接收任意 URL;“发布版本”应拆成查询与变更两个工具,让只读操作和外部写操作拥有不同审批策略。
不要轻易替换 Agent Loop
官方架构把 Session、System Prompt、Tools、Agent、Agent Loop 和 LLM 适配拆成独立接缝。大多数需求都可以通过工具、提示段、事件或能力 Provider 完成。只有标准的“模型请求—工具执行—继续”生命周期本身不适用时,才值得替换 Loop。
扩展越靠近核心,兼容和测试成本越高。预览版阶段尤其要优先使用公开服务与事件,而不是导入内部文件路径。
插件验收清单
至少测试:重复加载是否只注册一次;卸载后监听和资源是否释放;缺少依赖时是否明确失败;错误配置是否在启动期被拒绝;工具异常是否转成可诊断结果;大输出是否截断;敏感字段是否进入日志;版本升级后 Schema 是否变化。
最终建议
先写观察型插件,再写只读工具,最后才写带副作用的工具和拦截器。每个插件只占据一个清晰接缝,依赖显式声明,资源有对应清理,行为有四路径测试。
真正成熟的 Harness 插件,不只是“模型能调用”,而是能加载、能卸载、能失败、能审计,也能在升级时快速判断哪里变了。
继续阅读:Subagent、Workflow、Goal 与 Ralph 怎么选。需要回看完整顺序时,返回DeepSeek Harness 专题路线。