deepseek-harness 学习手册
自指能力M6 · agent 修改自己的运行时
镇站35 min

agent 修改自己的运行时

七个 cordis_* 工具逐个拆解,为什么 M2 的架构让它是安全的,以及一页不回避的信任边界。

最后核对于 v0.1.1-rc.2 · commit b150a551b8 · 2026-08-22

这是全站最容易讲成噱头的一页,所以先把定位说清楚:「agent 修改自己的运行时」之所以值得讲,不是因为它炫,而是因为它是 M2 那条断言的终极检验。

如果一个架构里真的没有特权核心,那么在运行时往树上挂一棵新子树、再原样撤掉,就应该是一件结构上安全的事——不需要重启、不需要清理代码、不会留下残骸。dsh 把这件事做成了七个模型可见的工具。

先说清楚它不是什么

不改仓库源码、不改配置、不写盘、不装包、不改 cordis.yml、不能自动晋升成正式插件、活不过一次进程重启。动态插件只活在当前 DSH 进程的内存里,而且按 session 隔离——只有定义它的那个 session 能看见和控制它。

想留下一个实验成果?官方的建议是:让 agent 走正常开发流程,实现成一个普通的本地 / 项目 / 仓库插件。

6.1 七个动词

官方 system prompt 里把推荐工作流按 1–7 编了号,这就是最好的讲解顺序:

#工具它做什么
1cordis_inspect_list列出当前 Host 已知的每一个 Inspect Provider(含从浏览器同步过来的 manifest),带平台、用途、只读方法和输入输出 schema。「不要猜名字。」
2cordis_inspect_query跑一个 provider 显式声明的只读查询:精确的 Service 方法、Event 分发模式、Builtin 签名、工具 schema、主题 token、活的 Slot 树与 props。它调不了业务服务方法,也改不了运行时。
3cordis_inspect_self按递增的详细程度看当前 session 自己的动态对象:不给 id 只列插件摘要;给 pluginId 返回版本指针、最近一次 Run 和每个 Package 摘要;只有 pluginId + packageId 才返回那个不可变 Package 的源码与诊断
4cordis_define定义一个不可变的 Package。新插件只提交一个 3–6 个小写字母的语义前缀,最终 id 由 Host 分配。它只校验参数与语法、记录源码——不申请审批、不执行 apply、不改 currentPackageId
5cordis_run激活某一个精确的 Package。mode:"run" 用于首次激活、重启当前版本、或回滚;mode:"update" 用于切到另一个 Package。未授权的 Client Package 会产生一个审批请求并返回 awaiting-approval
6cordis_stop停掉当前 Run,取消未完成的审批或激活请求,保留插件、每一个不可变 Package、授权、以及版本指针。对已停止的插件调用是幂等成功。
7cordis_undefine永久删除。如果在跑就先停、先取消请求,然后删掉每个 Package、授权和版本指针。历史卡片只留下一条「插件已移除」的记录。

6.2 为什么 M2 的架构让这件事是安全的

三条来自 M2 的性质,共同构成了这件事的安全前提:

四个 id,把「谁在跑哪一版」讲清楚

标识指什么
pluginId一个可以随时间被修改的插件
packageId该插件下一个不可变的 Host/Client 源码版本
pluginRunId一次激活尝试;它把审批、宿主/浏览器加载、私有 RPC、Run 卡片和错误串在一起
currentPackageId最近一次完全成功的 Package。停止、发起 update、update 失败,都不会清掉它
nextPackageId正在等审批 / 正在尝试 / 等浏览器激活 / 最近失败的那个目标

这套设计里最能看出功力的一句是:「一次 update 会先停掉旧的 Run 再启动目标 Package。失败不会自动重启旧版本——要么用 update 重试,要么用 run 回滚到 current。」它拒绝了「失败自动回滚」这个看起来很贴心的行为,因为那会让状态变得不可预测。失败之后停在一个明确的、可查询的状态里,比自作主张更有用。

6.3 信任边界:官方自己怎么说

「沙箱隔离了全局,但它不是安全边界。」

这是 packages/extensions/tool-cordis/README.md 的原话,也是 system prompt 里给模型看的原话:「受限执行环境防的是意外误用,不是恶意代码的安全边界。动态代码拿到的 Service 连的是真实运行时。」

具体地说:Node 全局变量要么不存在、要么被重定向到 ctx.fs / ctx.web / ctx.bash 这些 Cordis 服务,对 globalThis 的写入只在本地生效——但宿主域的 helper 让逃逸成为可能。挂载的插件拿到的是一个不含框架内部结构的 façade,可它允许的那些服务是会影响活运行时的

结论就一句:「把这个工具集当成 bash 权限来对待。」

正因为如此,这个工具集不在任何一棵出厂的插件树里——生成式 tool catalog 在它那一行明写着「Not in any shipped tree(a deliberate opt-in)」。你得自己把它挂上去,才有它。

顺带一个实测发现

仓库自带的 pnpm run demo:cordis 在 commit b150a55跑不起来:它的 overlay 和 web-app bundle 都插了 cordis-host-runner,加载时以 duplicate loader entry id 失败。绕过办法(一份只补 tool-cordis 的 overlay)写在 Lab 5 第 1 步

另外两条限制也写在 README 的「已知限制」里,同样坦白:ctx façade 不暴露 effect(),所以插件代码没法注册自定义 disposer(on / provide / tools.register 是受支持的清理路径);以及一个异步的宿主半体会跑出 vmTimeoutMs 的约束

6.3.5 实测:造一个小工具要几次审批

本站为了给首页录那段 demo,在 commit b150a55 上把这条路真跑了几遍。结论值得写下来,因为它是这套设计的代价那一面。

需求很小:一个 text_stats 工具(字符数 / 行数 / 最长一行)外加一个粘贴即看的面板。模型确实自己做出来了,但过程是这样的:

版本宿主校验挡回来的原因
pkg-1参数 schema 写在 input 下,契约要求 parameters
pkg-3output: { schema, render } 声明
pkg-4顶层 required 数组不被支持——这套 DSL 声明即必填
pkg-5schema.additionalProperties 必须显式写 truefalse
pkg-6……但 parameters 的根是隐式开放的,那里反而不能写 false

一次真实运行的连续版本链,核对于 commit b150a55。到第 6 轮仍在收敛。

把 schema 约定写进提示词之后再跑一遍,schema 这一类错误消失了,但换成了另外两类

版本换了个地方挡回来
pkg-1工具定义要用 execute 字段,模型按通用惯例写了 run
pkg-2浏览器半体:slots.register(options, component)options 里必须带 name

三件事叠在一起:

把两面都说清楚

正面:模型确实在自主修复。它每次都读诊断、改同一个插件、追加新版本再试——这正是 cordis_run 契约里写的「读诊断、改同一个插件、自主重试」,而且它连授权语义都推理对了(上一次的授权只覆盖失败的那个包)。

反面:把 schema 约定写进提示词,schema 这一类错误确实消失了,但摩擦只是换了个地方。本站又跑了一遍,这次挡回来的换成了:工具定义要用 execute 字段(模型按通用惯例写了 run)、以及 slots.register(options, component) 的真实签名(options 里必须带 name)。

所以真正的瓶颈不是某一条约定,是要一次性写对的小契约太多,而那份本该讲清它们的 skill 没加载上——模型只能一条一条撞出来,每撞一次就多一次审批。首页那段录屏用的是写明 schema 约定的提示词,提示词本身在片子第一帧里看得见。

6.4 延伸讨论:这条路通向哪儿

把「模型给自己长工具」拆开看,其实是两个正交的问题:

  1. 新能力从哪来?写代码、装包、连 MCP server,各家都能做。
  2. 它多快能生效、多干净能撤销?这才是 dsh 真正不一样的地方——因为「生效」在这里就是一次 ctx.effect(),「撤销」就是调它的返回值。

值得警惕的也是同一件事。system prompt 里有一整段在压制这个工具的使用冲动,措辞相当克制:

「动态 Cordis 插件是一种可用的实现机制,不是每个请求的默认选项。只有当用户想要设计或创造某样东西、或者一个临时界面能对当前工作有实质帮助时,才考虑它。这些指令或工具的存在本身、以及关于 Cordis 的讨论,都不会让一个请求变成动态插件任务。

这段话本身就是 M4 那套「模型看见什么是公开契约」的实践:一个强大工具的抑制条件,和它的能力描述一样,是要逐字写进 prompt、并且被 README 逐字引用的。

动手

Lab 5 · 让 agent 现场造一个工具(25 分钟)会把这七个动词走一遍。

目录

本页

M6 · agent 修改自己的运行时