首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >我看着 deepseek-harness agent 给自己写外挂:十来分钟,七个工具,五个字

我看着 deepseek-harness agent 给自己写外挂:十来分钟,七个工具,五个字

原创
作者头像
术哥
发布2026-09-06 18:05:05
发布2026-09-06 18:05:05
350
举报
文章被收录于专栏:运维有术运维有术

🚩 2026 年「术哥无界」系列实战文档 X 篇原创计划 第 200 篇,DeepSeek Harness最佳实战「2026」系列第 03

大家好,欢迎来到 术哥无界 | ShugeX | 运维有术

我是术哥,一名专注于 AI 编程、AI 智能体、Agent Skills、MCP、云原生、AIOps、Milvus 向量数据库的技术实践者与开源布道者

Talk is cheap, let's explore。无界探索,有术而行。

封面:能加菜的后厨
封面:能加菜的后厨

封面:一间能加菜的后厨,七枚工具章与「防错,不防坏」

agent 给自己写外挂,真正的卡点是:错了怎么办。

DeepSeek Harness 交出来的答案,五个字:防错,不防坏。

这五个字不是我读文档读出来的。2026年9月3日下午,我让一个正在干活的 agent 在自己面前写了一整轮外挂——登记、挂上、跑起来、停掉、销案,前后十来分钟,没改它一行源码。中途,它还当场拒绝过我一次。

上回我说,它给 agent 的自进化留了扇门。这回,我把门推开,进去看。

📌 本文看点

后厨能加菜:七枚章,三读四写,读的在前。

我亲跑十来分钟,被当场拒绝一次——那条报错,像批改作业。

版本只追加不涂改,试错有退路;沙箱不是保险箱,要把它当 bash 用。

01 一间能加菜的后厨 | The Kitchen

上回我拿马具作比:模型是马,Harness 是套在马身上的那套家什。这回往里走一步,把一个正在运行的 Harness 想成一间正在营业的后厨。

炉火正旺(进程活着),订单不断(会话进行中),墙上一排刀具铲勺(模型能调用的工具)。厨师长是模型,他全部的本事,都从墙上那排家什来。

而这间后厨有条不太寻常的店规:厨师长可以自创新菜。

七枚正式的章,三读在前:cordis_inspect_list、cordis_inspect_query、cordis_inspect_self,管翻档案——总目录、抽某一页细看、查自己登记过的菜;cordis_define、cordis_run、cordis_stop、cordis_undefine,管写菜谱、开火、停做、销案。

读的排在前头,这个顺序就是店规:动手之前,先查清这间后厨有什么食材、什么规矩。查的方式是问,不是猜。

仓库里的设计文档(2026年7月8日的 Agent Note)记载,这套工具最初只有三枚:inspect、mount、unmount——看一眼、挂上去、卸下来。今天源码里是七枚。给 agent 自改用的工具,自己先进化了一轮:从粗放的「挂上就跑」,长成了「查、写、审、跑、停、销」。

菜可以加,店规也能改。这间后厨的自我改造,从工具自己开始。

02 我亲跑的十来分钟 | Ten Minutes on the Stove

以下全是实测。2026年9月3日下午,我自己的会话,环境是 dsh 的网页版。

第一枚章,翻档案。动手之前,我得先弄清楚一件事:这间后厨里到底有什么、去哪问。cordis_inspect_list 给出一份目录:九个问询处(源码里叫 Inspect Provider),后厨这边四个——服务、事件、内置物件、工具各一处;前厅(浏览器那边)五个——服务、事件、内置物件三类与后厨共享,另外多出界面插槽和主题两处,「工具」则是后厨独有的一类。对正要写外挂的模型来说,这一页就是地图:能碰的服务、能挂的界面、能换的主题,动手前全摆在明面上。

第二枚,cordis_inspect_query,顺着目录抽一页细看:五十来个服务,每个都带精确到参数的方法签名。举一个具体的:管定时的 timer 服务,超时、间隔、节流、防抖,四组用法排得整整齐齐——模型要写定时器,翻开这一页就知道怎么调,不用猜。

一个「查字典」的动作,返回的不是简介,是字典原文。

然后我故意使坏。登记菜谱时,往代码里塞了一句 TypeScript 注解——就是给 JavaScript 标类型的写法,而这套系统只吃纯 JS。结果连档案柜的门都没让进:拒绝,id 不发放。

登记一版干净的菜谱,系统回话:已登记,尚未运行。注意,此刻什么都没跑。

cordis_run,开工。回执一行:运行中,编号 run-1。没有审批——我这份菜谱只有后厨部分,不碰前厅。按文档的规矩,动前厅的才要等人点头。

cordis_stop,停做。再查档案:状态停了,菜谱还在,版本指针原样保留。

cordis_undefine,销案。菜、菜谱、版本,一并清走。

接着我换了道菜,专测「改菜谱」:登记一版 v1,故意在版本内容里把 label(菜名标签)拼错——注意,拼错的是名字,不是代码,v1 本身是能跑的后厨插件,照常开工,这趟拿到的编号是 run-2;我不动 v1,追加一版改好拼写的 v2,用 update 切换,运行编号变成 run-3。切完查档案——两版菜谱并排躺在档案里,v1 标着非现行,v2 标着现行。

旧版没有被覆盖。它只是退居二线,按版本机制仍可作为回切对象。

十来分钟,两个完整来回,被拒一次。放权感?更准确的体感,是每一步都有记录、有门槛,像有人在旁边一格一格扶着你走。

03 那条报错,像批改作业 | An Error That Teaches

那次拒绝,留下一行报错。原文一字未动:

代码语言:markdown
复制
Error: dynamic package `code.host` failed to parse:
cordis-dyn-code.host.js:3
const label: string = 'broken on purpose'
      ^^^^^

SyntaxError: Missing initializer in const declaration
Note: it runs as the BODY of an async function (line numbers are offset by the 1-line wrapper). Check bracket balance — ending the returned plugin object with `});` closes a call that was never opened; a plain `return { … }` ends with `}` (an optional `;`), never `)`.

(最后那句 Note 是套用的通用提示——替我这类没踩中专属分支的报错兜底,跟当前的 const 错误不是一个毛病。)

语法门:错代码进不去
语法门:错代码进不去

语法门:被拒绝的错代码、干净进柜的菜谱,「批改作业」式批注

大家品品这段东西。

出错那行原文、脱字符箭头、行号,全给了——还提醒你行号有偏移,因为代码外面包了一层异步函数。最后附一条括号配平的讲解,把「结尾多写一个括号」这种经典手误,掰开揉碎讲了一遍。读者不是人,是模型自己。

define 这道门里装着一个编译预检:代码先过一遍语法编译,编不过,不发 id、不进档案——错误在进门之前就被拦下,改完重新登记就行,试错成本几乎为零。报错文本是专门构造的教学文案,源码里还处理了一类 TypeScript 写法:出错行命中 as 时会附一段「删掉类型注解」的正误对照。我这行 : string 注解没踩中那个分支,拿到的是通用提示,照样一眼能改。

设计文档把这件事定为三个正确性问题:注册当场校验、API 先给字典、挂上能完整释放。刚才看到的语法门,正是第一个问题的答案——错误在进门之前就被拦下,模型可以改完再试。

连账单都被算过。文档写明:检查结果以文本 JSON 渲染,在上下文压缩前会重复发送,所以生命周期确认被压成一行短句;工具目录不变时请求前缀稳定,KV 缓存就能持续复用。设计者连每一轮上下文要付多少 token 都算进去——把确认句压到一行,是为了让重复读便宜一点。

04 菜谱只追加,不涂改 | Define Is Not Run

系统默认你会写错,所以它得让写错这件事不贵。版本设计,就是干这个的。

define 只做登记——校验参数、过语法门、把源码存档,然后明确告诉你:还没跑。跑不跑、什么时候跑、跑哪一版,是 run 那枚章的事。

菜谱放进档案柜,和照着菜谱开火,是两件事。中间隔着「人还可以反悔」和「版本可以挑」。

一道菜(插件)名下挂着若干版菜谱(包),每一版登记完,就成了不可变的档案页:写错不许涂改,只能追加新页。我实测里那两版并存的菜谱,就是这套规矩的直接展示——v1 带着拼写错误躺在档案里,v2 是修正版,一枚指针标明现在用哪版。

为什么这么设计?我的推断:改错是常态。模型写的第一版代码多半有毛病,如果每次修正都覆盖旧版,新版一旦更糟,就再也回不去了。只追加、不涂改,等于给试错上了保险——撕过页的档案,历史就不可信了。

挂上去的东西,也不是没人管。先说我亲测到的:stop 之后,状态停、版本保留;undefine 之后,插件连同它的包,整体移除。至于更细的回收逻辑,是源码显示:每道动态菜挂在一枚统一的工作单(fiber)下,开工是开工单,停做就是注销这枚单,注销时它借走的灶台、领的食材、定的闹钟,全部自动归还,不用厨师长挨个收拾;启动失败也一样,先把没挂稳的释放干净,再报错。回收不是额外动作,是注销工作单自带的副作用——上篇文章我说过「拔掉之后自动收拾干净」,这次,我看到了实现它的那行机关。

版本切换:v1 不删,指针挪到 v2
版本切换:v1 不删,指针挪到 v2

版本切换:v1 带着拼写错误留在档案里,指针挪向 v2,旧版随时切得回

05 收走家电,贴上指路条 | A Sandbox That Points

误会最深的一个词,是沙箱。

一听沙箱,想到关押。实际它更像后厨角落里隔出来的一间小屋(源码里是 node:vm 的独立 realm,说人话:里外两间屋的全局变量互相看不见),里面几样常用家电被收走了——require、setTimeout、fetch 这些 Node 环境的入口。

按源码实现:调用这些入口时会抛出错误,错误信息里没有「不许用」三个字,而是一张指路条。调用 setTimeout 时,指路条写着:Node 的定时器不可用,请改用 Cordis 的 timer 服务——先报备要用的服务(声明 inject),再查 Service.listService 看准确用法;这些调用会自动挂在工作单上,停止时一并清理。发网络请求?指向 web 服务。碰文件、碰进程?指向 fs 和 bash 服务。文件、网络、进程、定时器,四类活全被引到统一的问询处,不走野路子。

还有个读源码才会会心一笑的细节:process 和 Buffer 这两位(Node 的进程管家和二进制桶)被处理成「干脆不存在」。代码里常见的 typeof process 探测,是先轻轻摸一下再决定走哪条路;如果一碰就炸,一段无害的探测就能把整份菜谱炸报废。设计者连模型会怎样试探环境都考虑到了。

指路条把模型随手写出的 Node 代码,引到可查、可收、可审计的正规通道上。

沙箱指路条:危险家电收走,正规通道贴满指路条
沙箱指路条:危险家电收走,正规通道贴满指路条

沙箱指路条:require、setTimeout、fetch 被收走,timer、web、fs、bash 指路条引向正规通道

06 边界清单 | Where It Stops

它不干什么,同样值得听。

一,沙箱不是安全边界。文档写明:信任等级与 bash 相当,授予这套工具,要像授予 bash 工具一样慎重。先接上一节的疑问:收走 require、setTimeout、fetch,收的是动态代码眼前直接能摸到的入口——它们被换成了指向 Cordis 正规服务的指路条;而宿主助手函数仍然够得着 Node,说的是沙箱本身不是保险箱。一个管「模型的手能伸多长」,一个管「这是不是保险箱」,两码事,不矛盾。原因很直白:这间小屋只隔离「意外的全局污染」。

设计文档里连「加固沙箱」都讨论过,并明确否决——真正的隔离要独立进程、权限提示,那超出一个显式启用的开发工具的范围,还跟它的核心目的——把活跃运行时交给模型——正面冲突。你自己品:一个沙箱先自我检举「我不是安全边界」。这是工程文档的写法,不是营销文档的写法。

二,时间限制只管一半。vmTimeoutMs(给同步段设的时限,示例配置里五千毫秒)只约束同步执行;异步的主体,能逃出这个时限。文档和源码都写明了这一点。

三,作用范围:以会话为界,以进程为本。菜谱只在登记它的会话里可见可控,但后厨是同一口灶——跑起来的菜,可能波及同一进程里的其他会话。这条我未实测,按文档转述。

四,重启即清空。所有定义只存在于进程内存:不写仓库文件、不装包、不改配置。进程一重启,当天登记的菜谱全部回收,没有自动恢复。这条我也未亲测(没舍得重启),依据是文档与源码。我的理解:这是刻意区分「实验」和「资产」——想长期保留的东西,请走正规插件的开发流程,别把记忆寄存在一间打烊归零的后厨里。

五,我的实测边界,如实披露。我跑的都是后厨侧的菜谱。带前厅(浏览器那一半)的包要走审批:工具回一个 awaiting-approval,等人点头;批准可以只盖当前这一版,也可以一次盖住这个插件日后的所有版本。这部分我未实测,全部按文档转述。同样没测的还有:update 失败后的回滚、运行时出错后的「读诊断—修版本—再更新」循环、指路条抛错的真实原文。

六,利益相关。上一篇我夸过它的方向,这一篇继续夸它的工程。你要觉得我收了钱,那就是你对——我只收了自己的好奇心。

边界清单:把它当 bash,别当保险箱
边界清单:把它当 bash,别当保险箱

边界清单:不是安全边界、信任等级接近 bash、同步有时限、重启即清空

07 写在最后 | Cook Something

防错,不防坏——这五个字,在刚才每一节里都成了形。

想做到能写,一行 eval(把字符串当代码直接执行)就够了。设计含量都在错误那一侧:语法错误进门就被拦,报错像批改作业;版本只追加不涂改,试错永远有退路;副作用全挂工单,注销即回收;危险的近路被收走,正规通道贴满指路条。至于坏——它诚实地承认不管,把那道题留给决定是否授予 bash 的人。

我的判断:把错误做便宜,比把恶意做绝路,现实得多。一个系统让人放心地错、便宜地错、错了还回得来,这扇门才真开得出去。

真想自己开火,先把仓库里的路标备好:packages/extensions/ 下四份 README 是组地图,docs/subsystems/extensions.zh.md 是接口全集,docs/tool-catalog.zh.md 的 cordis 段则告诉你模型能看见哪些工具。启动只要这一条命令:pnpm dsh web --patch apps/cli/config/examples/cordis/cordis.yml,但要自备模型凭据。还要记住,这套工具不随成品组合默认分发,必须显式挂载;我这轮实测所在的界面,就是提前挂好的环境。文档对这份信任成本说得很直白:等于发 bash。

生产环境,手别痒。

这回,模型手里多了一样东西:给自己修工具的工具。手艺还潮,菜谱常错,但错的那一页永远留在档案里——

那是它学会做饭的过程,不是污点。

去开一次火。

然后,回来交作业。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 01 一间能加菜的后厨 | The Kitchen
  • 02 我亲跑的十来分钟 | Ten Minutes on the Stove
  • 03 那条报错,像批改作业 | An Error That Teaches
  • 04 菜谱只追加,不涂改 | Define Is Not Run
  • 05 收走家电,贴上指路条 | A Sandbox That Points
  • 06 边界清单 | Where It Stops
  • 07 写在最后 | Cook Something
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档