生命不息,折腾不止。DeepSeek Harness 最反直觉的一招:Agent 不只能「用」工具,还能在运行时给自己「造」工具——Creator 模式配七个 cordis_* 工具,就是这套「自进化」的总开关。

前两篇我们把 dsh 跑起来、接上了自定义 Provider(想接 Claude/GPT 的话,中转站 ai.aklibk.com 国内直连、免绑卡、人民币按量付费,上一篇文章有完整步骤)。今天聊点更野的:让 Agent 在跑的时候,自己查运行时、自己写插件、自己热插拔。这就是 Creator 模式。

一、Creator 模式到底是什么

dsh 一共四个运行模式,切模式切换的不是模型,而是「这台 Agent 被允许拥有什么手脚」:

  • Standard(标准):完整工具组合——文件编辑、shell、搜索,默认的编码 Agent 就是它。
  • Code(代码,也叫 PTC):让模型生成一段代码来编排多轮工具调用,而不是一次只吐一个工具调用。
  • Minimal(极简):只留一个 shell + 一个文件编辑工具,专给模型做基准测试用,是最诚实的「框架本身贡献了多少」的测法。
  • Creator(创造):在 Standard 全套能力的基础上,额外开放一组操作 Cordis 插件系统的专属工具。

Creator 模式特别在哪?官方原话:它能「检查当前运行时、在内存里试验 Cordis 插件,并据此组合和创作新的模式」。翻译成人话,Agent 可以:

  1. 看现在这台 dsh 里到底有哪些插件在跑;
  2. 在内存里直接试验新的插件组合;
  3. 生成一个全新的 preset(agent.cordis.yml)固化下来。

所以它不是一个「一键写插件」的魔法按钮,而是一个高信任的本地试验场。插件本身的写法还是 Cordis 统一规范(一会儿第六节讲),Creator 模式的价值是让 Agent 帮你查、帮你试、帮你生成骨架,把「需要熟读 Cordis API」这件事,降级成「用自然语言描述需求」。

二、七个 cordis_* 工具:三读四写

这七个工具由 @deepseek-ai/dsh-tool-cordis 包注册,就是 Agent「自进化」的全部弹药。先记住一句话:三个读的在前,四个写的在后——动手之前先看清楚现场。

工具 作用 改状态吗
cordis_inspect_list 列出 Host 与 Client 已知的 Inspect Provider、方法和 schema 否
cordis_inspect_query 调一个 Provider 声明的只读查询 否
cordis_inspect_self 查看当前会话拥有的动态插件、Package、版本与诊断 否
cordis_define 记录一个新的不可变 Package;只校验,不运行 是
cordis_run 首次运行、重启、回滚或更新到指定 Package 是
cordis_stop 停止当前运行,保留插件、Package、授权和版本指针 是
cordis_undefine 永久删除插件及其所有 Package、授权和指针 是

注意:旧的 cordis_inspect / cordis_mount / cordis_unmount 三件套已经被这套版本化生命周期取代了。网上老教程还在讲 mount/unmount 的,过时了。

这套机制背后是四个包在协作,知道分工有助于理解审批逻辑:

  • @deepseek-ai/dsh-tool-cordis:注册七个工具 + @pluginId 引用注入;
  • @deepseek-ai/dsh-cordis-host-runner:保存动态插件、Package、版本指针,以及 Host 半部的运行状态;
  • @deepseek-ai/dsh-cordis-client-runner:在浏览器侧授权并运行 Client 半部;
  • @deepseek-ai/dsh-client-ui-cordis:在会话里渲染定义、启动和状态卡片。

记住「Host 半部 / Client 半部」这对词,第五节的审批机制全靠它。

三、完整生命周期:查 → 定义 → 运行 → 收尾

官方推荐的动作顺序,跟「进厨房先看配料再动火」一个道理:

1
2
3
4
5
6
1. cordis_inspect_list      先看有哪些 Provider、方法可用
2. cordis_inspect_query 导航式查询,找到精确的 service/event/slot
3. cordis_inspect_query 再对精确目标查完整契约
4. cordis_define 最后才写代码
5. cordis_run 跑起来
6. cordis_stop / cordis_undefine 收尾

cordis_inspect_query 只能用 cordis_inspect_list 返回的精确名字,瞎猜是要报错的。参数长这样:

1
2
3
4
platform: host
provider: Service
method: listService
input: {}

Host 查询在本地执行;Client 查询会等浏览器页面响应。这里有个硬约束:Inspect 只能读——读契约、服务、事件、Builtin、Slot、token 或当前树,不能代替业务 Service 调用,也改不了运行时。

cordis_inspect_self 三种用法,越具体信息越多:

1
2
3
cordis_inspect_self                                    # 当前会话的插件摘要
cordis_inspect_self pluginId:"echo-1" # 版本指针、最近运行、Package 列表
cordis_inspect_self pluginId:"echo-1" packageId:"pkg-2" # 该 Package 的源码 + 运行诊断

最后一种会返回这个不可变 Package 的 Host/Client 源码和运行诊断,出故障排查就靠它。注意 packageId 不能脱离 pluginId 单独查。

四、定义一个会说话的插件

cordis_define 是「写」的第一步,也是最容易误解的一步:它只校验、只保存,不运行、不申请授权、不移动版本指针。定义完系统会回你「已定义,尚未运行」。

新插件只提交一个 3–6 位小写英文语义前缀(idPrefix),Host 负责生成唯一 id:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
plugin:
kind: new
idPrefix: echo
name: Echo tool
purpose: Register a small echo capability
code:
host: |
return {
name: 'echo-package',
inject: [],
apply(ctx) {
// 用 cordis_inspect_* 查到的真实 Cordis API
}
}

更新已有插件时,改成 kind: existing 并带上 pluginId,就会追加一个新 Package 而不是覆盖旧版:

1
2
3
4
5
6
7
8
plugin:
kind: existing
pluginId: echo-1
name: Echo tool v2
purpose: Add the second behavior
code:
host: |
return { name: 'echo-v2', inject: [], apply(ctx) {} }

几个关键约束,踩坑率最高:

  • 至少提供 code.host 或 code.client 之一;
  • 内容是返回 Cordis Plugin 的普通 JavaScript function body,不转换 TypeScript、JSX 或 import;
  • Package 不可变——更新是追加,不是覆盖;
  • define 成功会返回稳定的 pluginId 和精确的 packageId,下一步必须显式 cordis_run。

跑起来用 cordis_run,mode 只有两种:

1
2
3
pluginId: echo-1
packageId: pkg-2
mode: run # 或 update
  • run:首次启动、重启当前版本或回滚;
  • update:从当前版本切换到另一个 Package。

五、为什么「沙箱」不是安全边界

这是整个机制里最反常识、也最重要的一节,务必看完再决定要不要开 Creator。

审批与否,看代码跑在哪,而不是看它「危不危险」:

  • Host 半部:跑在 dsh 的 Node 进程里、node:vm 沙箱内,能碰文件、网络、命令、服务和模型工具——全是 server 端资源,但不需要审批(由沙箱 + 运行时守卫兜底)。
  • Client 半部:跑在浏览器页面里,能碰主题、布局、页面状态,全局只剩 React、console、styles、host 这几个,fetch、setTimeout 被藏起来了——却需要人工审批,因为它钻进了用户自己的页面和会话。

看起来很别扭对吧?更危险的 Host 半部反而免审,看着无害的浏览器半部反而要签字。原因就一句话:Client 半部是「用户的个人扩展」,触及的是用户的人设和会话,必须本人点头。

审批的细节(来自对 cordis_run 源码的阅读):

  • 审批在 cordis_run 阶段触发,且只有当这个 Package 包含浏览器侧代码、且该版本还没被授权过时才弹。返回 awaiting-approval 表示「在等人」,不是报错。
  • 两种授权粒度:单勾(approvedClientPackages 只覆盖当前 packageId)和双勾(clientVersionUpdatesApproved 覆盖该插件未来所有版本)。因为代码一改就是新 Package,单勾下次还会再问。
  • 授权挂在 packageId 上,技术故障重跑不会重新弹;cordis_stop 只停运行但保留授权;只有 cordis_undefine 才会连授权一起清空。

再说「沙箱」。很多人一听沙箱就觉得「关起来了很安全」,错。它更像是在厨房角落隔了一个小房间——两边的全局变量互相看不见,但它不是一个安全边界。官方口径:授予这套工具集,信任级别等价于授予 bash。沙箱只防「误用」,不防「恶意」。

沙箱里被移走的三个东西,尤其能看出设计者的用心:require、setTimeout、fetch 这三个 Node 入口被直接拿掉,但报错信息不写「禁止」,而是给你路牌——「Node timers 不可用,请用 Cordis timer 服务」「要网络?去看 web 服务」「文件进程?走 fs 和 bash 服务」。文件、网络、进程、定时器四类能力,被统一导向可审查、可审计的官方通道。更狠的是 process 和 Buffer 被当成「根本不存在」——模型爱写的 typeof process 探测直接失效,想偷偷摸环境变量的路被堵死。还有一个 vmTimeoutMs(默认 5000ms)兜住同步求值的超时。

最后是开这个模式的三个安全红线:

  1. 只在可信本地环境用,禁止公网部署开 Creator;
  2. 授权粒度能窄就窄,API Key 走环境变量,工作区目录收窄;
  3. 沙箱 ≠ 安全边界,别把「开了沙箱」当成「可以随便跑任意代码」。

六、Creator 是试验场,规范才是「真写插件」

最后把关系理清,免得你被「Agent 自己写插件」唬住。分两层看:

表层:Creator 模式让你在会话里直接下指令,比如「在 scratch-plugin/src/ 下给我生成一个插件,注册一个 fetch_jira_ticket 工具,Token 从环境变量 JIRA_TOKEN 读」,Agent 会生成 .ts 源码和对应的 cordis.yml。插件开发被降级成了自然语言描述。

底层:真正「写插件」走的还是 Cordis 统一规范,跟你用哪种模式启动无关。最小形态就是一个导出 name 和 apply 的 TS 模块:

1
2
3
4
5
6
7
8
import type { Context } from '@deepseek-ai/cordis'

export const name = 'my-tool-plugin'
export const inject = ['tools'] // 声明依赖,就绪后才调用 apply

export function apply(ctx: Context) {
ctx.tools.register(/* ... */)
}

再用一个 cordis.yml 把它插进运行时,路径必须绝对:

1
2
3
- insert:
- id: my-tool
name: '/absolute/path/to/scratch-plugin/src/my-plugin.ts'

启动时用 --patch 覆盖层加载(本地试验最方便):

1
pnpm dsh web --patch ./scratch-plugin/cordis.yml

想直接体验 Cordis 那七个工具,官方仓库里带了现成的 patch 文件:

1
pnpm dsh web --patch apps/cli/config/examples/cordis/cordis.yml

小结一句:Creator 模式负责「让 Agent 帮你把插件造出来、试出来」,而插件最终落地,靠的是 apply + inject + cordis.yml 这套规范。前者是试验场,后者是生产规范,两条腿走路。

生命不息,折腾不止。下一篇我们把这套「自进化」的底裤彻底扒开——《DeepSeek Harness 权限模型:沙箱 × 审批,两个旋钮怎么管住 Agent》,讲 read-only / workspace-write / danger-full-access 三档沙箱、ask / never 两种审批策略怎么组合,以及为什么 danger-full-access 会默认关掉审批、它在无人值守 CI 里到底该不该开。