当前位置: 首页 > news >正文

DeepSeek Harness 开源了,[一切皆插件],把他拆开看一看~

1. 终于等到DeepSeek的Harness

2026-08-13,DeepSeek 把 Harness 开源了,v0.1 开发者预览,MIT 协议(写作日 WebSearch 核实)。

先抛我的总论点:主流 Agent 换的是工具,DeepSeek 把 Agent 本身都变成了插件。这一句不是宣传话术,是字面事实。我拆了它的源码——本地一份47f9438的 HEAD——模型、工具、技能、会话、沙箱、存储、Agent Loop、调度、UI,全是插件,挂在同一个 Cordis 底座上,没有特权内核。

为什么值得拆?Harness 正在成为 AI Coding 的主战场。Claude Code、Codex 比的是默认体验打磨;DeepSeek 选了另一条路:把整个运行时摊开给你换。两条路线谁对,v0.1 阶段谁也说不死,但架构意图一眼能看穿。这篇我从 harness 工程角度,把「一切皆插件」这套设计拆开看,能换什么、换完什么代价,最后给一个本地实跑的配置观测。

2. 底座:Cordis 插件系统

拆之前先认识底座。dsh 不是自己写了一套 Agent 框架,它站在 Cordis 上——一个元框架,只管插件怎么加载、怎么卸载、谁依赖谁。剩下的全是业务。

这一句的分量要读出来:模型适配器是插件,工具注册表是插件,会话日志是插件,连 agent loop 本身都是插件。插件之间不靠改对方代码协作,靠两样东西:服务(往共享上下文注册能力)和事件(广播事实、被监听拦截)。一个插件卸载,它注册的服务和副作用一并撤销——Cordis 管这个叫「可逆副作用」,所以没有哪个插件欠着一屁股债赖着不走。

运行的 dsh 是一棵插件树,启动时由若干层按序叠加:

配置叠加从空根[]起步:先按 profile 声明的顺序叠各 bundle 包(web 就是 base → web-app),再叠 profile 自己的cordis.patch.yml,然后是 home 级那份,最后是--patch指定的覆盖层。后层覆盖前层,一条 patch 按 id 定位某个插件、整体替换它的 config。

这四层有一个工程含义:发行版、profile、用户、命令行,四个改配置的口子分得清清楚楚。你改坏自己的层,往上回溯到 bundle 就能排障。我本地实跑时--dump-default-config--dump-config输出完全相同——因为干净环境里没有用户层和 patch 层,正好说明这两个 flag 的分工:前者只看 bundle,后者看完整叠加。

架构文档把这类可替换能力叫seam(接缝):声明接口的 Service Definition、实现它的 Service Provider、使用它的 Consumer 三者一起设计。一个 seam 换掉提供方就能改变整个产品——把文件系统与进程提供方指向远程沙箱,Bash、PTY、LSP 就一起搬过去了,不用给每个提供方写 fork。16 个以上的 seam(llm/fs/shell/subprocess/sandbox/approval/codeRuntime/subagents/workflowEngine/lsp/web/compaction……)排成一张能力图,这是整个仓库的地图。

先卖个关子:连 Agent Loop 都能换,意味着什么,后面揭晓。

3. 核心循环:ReactLoopAgent 状态机

先看默认的 Agent Loop 长什么样。ReactLoopAgentpackages/core/agent-loop/src/agent.ts:64,它把工作切成两个单位:turn(轮次)和step(步骤)。一个 step 是一次模型请求加上它调用的工具;一个 turn 包含零个或多个 step——在领取首条输入之前打开,不再欠任何工作时关闭。

状态机就三条:turn():246)开着循环领 step,step():332)构建请求、流式收assistant/chunk、派发工具调用;step 跑完看还有没有活——工具还欠一个请求,或 next-step 输入到了——有就领下一个 step,没有就agent/turn-stopping收尾,turn/end关闭。

消息怎么进来?Inbox类(packages/core/agent/src/inbox.ts:25)维护两条队列:next-turnnext-stepclaim():71)领走整批 next-step 输入,如果要开新轮次再顺走一条 next-turn。有些消息立刻唤醒驱动,注入的上下文则留在队列里,等另一条消息把它叫醒。

这一套我压成一句话:循环不直接读「消息数组」,循环读队列。输入、注入、中断都变成队列操作,状态机只关心「队列里还有没有活」。分叉、恢复、回放全挂在同一个事件流上(下一节展开),循环本身反而保持得很薄。buildRequest:407)组装一次请求时,也要经过agent/request这个 waterfall,让插件能改写请求——连「模型这次调谁、用什么参数」都是可插拔的。

插件下沉到整个运行时,架构不神秘,真正的风险在生态。这句话我放在这儿,是第三节的立场,结论再打。

4. 一行真相:append-only 会话日志

第四节讲一个被我低估的设计:会话日志。

dsh 的会话不是「消息列表」,是一条append-only 的 SessionEvent 事件流packages/core/session/src/types.ts:404)。每条事件带typeseqtimedataseq就是log.lengthindex.ts:604的 append 里写死),单调、连续、可寻址。事件必须是无损 JSON——BigInt、函数、Date 这类直接拒收——写进去之前先 deepFreeze。日志是不可变的事实,谁也别想改。

事件按角色分三类,我叫它三域:

  • 边界turn/startstep/startrequest/header这类标记,不产生模型可见消息;

  • 表面user/messageassistant/messagetool/result,真正出现在模型历史里的东西;

  • 仅日志assistant/chunktool/code-dispatch、usage 这类,为保真和回放存在,但不进派生历史。

设计原则一句话:模型可见即已记录。抵达模型请求的一切,都必须能从日志重建。这是一条运行时不变式(packages/core/agent-loop/src/invariant.ts:21-54):每次 llm 请求,检查消息数组是否与deriveMessages()从日志算出来的一致,不一致直接 fail。不是约定,是断言。

这条不变式把整个系统焊住了。UI 渲染读日志,回放读日志,fork 读日志(index.ts:1081按 boundary seq 切一个子会话),恢复也是日志的纯函数。日志是唯一真相,其它全是投影。主流工具把会话状态当运行时内存来管,dsh 把它当数据库来管——回放、分叉、继续这些「看起来很难」的功能,在这里全是对同一事件流的不同读法。

5. 安全与扩展的接缝:工具执行流水线

工具是模型和世界之间的接缝,dsh 在这条接缝上放了一条完整的执行流水线。位置在packages/core/tools/src/index.tsprepareExecution:1463)跑 pre-execute waterfall,然后审批询问、单调守卫,dispatchScheduledExecution:1569)跑 execute(timeout/retry 包在外面),postExecute:1742)跑 post-execute waterfall,最后applyFinalContent:1649)应用定义自带的内容变换,tools/result通知最终结果。

简化成六个阶段:pre-execute waterfall → 审批询问 → 单调守卫 → execute → post-execute → finalizeContent + tools/result。两个细节值钱。

一是单调守卫和审批分开。守卫是已注册的「所有者策略」,不能重新排序;ctx.approval是一次性的人机询问,放在守卫之前。它们各管一摊,谁也不能绕过谁。审批缺了回答方、或回答不可用,一律按 deny 处理——fail-closed。

二是PTC(Programmatic Tool Calling)不绕过安全流水线。PTC 是 code preset 的核心卖点:模型不一个个调工具,而是生成一段 TypeScript 程序,把多步操作组合起来。但它复用的是同一个TOOL_RUNTIME_SCHEDULER——code-mode.ts:481拿到调度器,:545对每个子调用走scheduler.prepare()。换句话说,程序化调用里的每一个子调用,仍然完整过一遍 pre-execute/守卫/审批/execute/post-execute。这不是给模型开后门,是把批处理也关进同一条流水线。

6. 四种模式 = 四套插件组合

模式不是硬编码,是四份 YAML。apps/cli/config/agent-presets/下四个目录,每份preset.yml+agent.cordis.yml,由agent-presets插件按default: standard挂载。

  • standard(标准模式):功能完整的编码 Agent——文件编辑、Shell、检索、Skills、计划、目标、子代理、工作流全给。

  • code(PTC 模式):具备 standard 全部能力,再通过 Code Mode SDK 把工具呈现给模型,让模型用一段 TypeScript 程序组合多步操作。

  • minimal(极简模式):只有两个工具——持久 bash +str_replace_editor。我读了它的agent.cordis.yml,整份文件就两个 group,persona 直接写死完整系统提示词,连上下文压缩都没有。

  • cordis(创造模式):给「想造 preset 的人」用的,带运行时检查、插件实验和 preset 创作指导。

注意一个坑:standard/code/minimal/cordis 是 agent-preset,不是dsh --profile能启动的 boot profile。CLI 随附的 boot profile 只有webheadless两个;preset 是「这个 Agent 会话里装哪些工具、用什么人格」,boot profile 是「启动哪一棵插件树」。我本地跑--profile standard --dump-config,直接报错profile "standard" does not exist——这个区分最容易被配置文档坑到,单独拎出来说。

每份 preset 的agent.cordis.yml是「agent-plane」的组合:里面涉及服务的行,必须放在带isolaterealm 的 group 里,否则服务会发布到 root realm 变成进程全局,两个 preset 撞名就报错。这个机制保证不同 preset 各带各的私有实例——同一进程里,standard 的 agent 和 minimal 的 agent 互不污染。

到这里已经摸到第二节那个关子的边了:「换」不是换个实现类,是换一整套插件组合。但真正的「换循环」,要看到多 Agent 与 workflow——下一节。

7. 多 Agent 与 Workflow:现在兑现那个关子

第二节我说「连 Agent Loop 都能换」,现在兑现。

子 Agent 是一层。SubagentProvider接口(packages/subagent/subagent/src/types.ts:285)定义了什么是一次「委派」,注册了五类实现:

  • spawnsubagent-spawn-in-process/src/index.ts:41):新建子 Agent,inheritsParentContext = false,孩子从零开始,看不到父对话;

  • forksubagent-fork-in-process/src/index.ts:48):继承父的已完成轮次前缀(inheritsParentContext = true),从父的上下文续着干;

  • acpsubagent-acp/src/index.ts:146):进程外的 ACP 子 Agent,无父上下文;

  • claude-code / codex:把一轮委派给 Claude Code 或 Codex 进程(standard preset 里默认disabled: true,注释写得明白:复制 preset 再打开,就能只让某个组合用)。

更「换循环」的是 workflow 引擎。packages/workflow/workflow-worker-thread/src/runtime.ts:90把一段 workflow 脚本用vm.Script编译进 worker thread,脚本里能调用agent()parallel():401,各 thunk 并行,非致命错误归 null)、pipeline():428,逐项过阶段、无跨阶段屏障)。也就是说,一个任务怎么编排,不是写死在循环里,而是跑一段可编程的脚本——循环被换成了可组合的脚本运行时。

最极端的是 Ralph(packages/workflow/tool-ralph/src/index.ts)。它内置一段固定脚本RALPH_SCRIPT:90):每轮派一个全新的结构化子 Agent 去攻一个目标,子 Agent 必须返回规范化 report(status/summary/evidence/nextSteps/blocker),模型只能填数据,不能改循环。requireFreshProvider:220)强制子 Agent 必须是「真·干净上下文」——继承父上下文的 fork 直接被拒。standard preset 里给 Ralph 配了maxRounds: 64agent.cordis.yml:233)。

一句话收束:spawn/fork/acp/codex/claude-code 换的是「子 Agent 从哪来」,workflow 换的是「任务怎么编排」,Ralph 换的是「整个循环归谁控」。底下的 turn/step 状态机还是那个,但用不用它、怎么用它,全由插件组合说了算。这就是「连 Agent Loop 都能换」的全部含义。

回到第三节那个判断:架构不神秘,风险在生态。换的能力给够了,谁来换出好东西,是下一节要谈的成色问题。

8. 本地实践:跑起来看配置

理论看到这儿,落地看一下。我在干净的目录里真实装了一遍(素材全文在 local-practice.md),下面全是本机输出。

  • 安装:单独建目录npm install @deepseek-ai/dsh --no-fund --no-audit,装到0.1.0-rc.6别在源码仓库目录里直接 npm install——那是 pnpm workspace,依赖带workspace:协议,npm 会报EUNSUPPORTEDPROTOCOL workspace:

  • 绕 shim:Windows 上包 bin 指向lib/bin.js(ESM),用node node_modules/@deepseek-ai/dsh/lib/bin.js直跑,绕开.cmdshim。

  • --version0.1.0-rc.6

  • --help→ 子命令webplugin;选项--profile--patch--dump-config--dump-default-config

  • --profile web --dump-config→ 490 行 YAML。顶部按# == <层来源>注释分组,能看出每行来自哪个 bundle、被哪个层 patch 过。

几个真实配置片段(dump 原文):

  • 默认模型:agent-default-modelprovider: deepseek-officialmodel: deepseek-v4-flash(dump 时的默认值,随版本可能变,待核实);

  • 沙箱:sandbox-policy默认workspace-writebash-sandboxdisabled: !!js process.platform === 'win32'——Windows 上自动禁 bash 沙箱,换成pwsh-sandbox

  • 审批:approval默认ask,只有danger-full-access才是never

  • 服务:webserver默认host: 127.0.0.1port: 3080

  • 默认 preset:agent-presetsconfig.default: standard

最有意思的实操是--patch覆盖层。写一个十几行的 YAML,id 定向改agent-presets的 default:

- id: agent-presets config: default: code

--profile web --patch extra.yml --dump-config,diff 就两处:多一行patched by <extra.yml 路径>注释,default: standard改成default: code。不改源码、不重启整棵树,就换掉了默认人格。

诚实边界我照实说:dsh web真正启动、dsh plugin add、四个 preset 的 YAML 挂载、!!js表达式在启动时的求值结果,这几项我都是源码读到、没实跑验证——没配 API key,没起 webserver。凡标了行号的都是源码事实,凡说「真实输出」的都是上面这些本机跑出来的。

9. 结论:DeepSeek 押注了什么,v0.1 的真实成色

拆完,我给一个总体判断。DeepSeek 押注的是架构开放:把插件边界下沉到整个运行时——模型、工具、技能、会话、沙箱、存储、Agent Loop、调度、UI,全是插件。这个选择和我之前写过的「Agent 为核心 + 能力/知识/协作」的 Agent+ 路线是同一件事的两面:既然 Agent 是核心,Agent 周围的一切就该可替换。

但 v0.1 的真实成色,我泼三盆冷水。

第一,「什么都能换」不等于任务成功率更高。换的能力给的是架构自由度,真正兑现体验的是高质量默认插件、稳定组合范式、可信评测——这三样在 v0.1 都还在路上。README 明说当前是开发者预览、未来将出现破坏兼容性的变更,接口快速变化是实打实的迁移成本。

第二,生态风险在「谁来换出好东西」。插件边界越深,接口稳定性、依赖管理、版本兼容、性能、调试复杂度越难控制。可替换是能力,可组合是纪律,后者不是开源就自动有的。

第三,缺交互 CLI 是实打实的短板。boot profile 里headless是一次性任务执行(dsh --profile headless "run the tests"),web是浏览器 UI;没有一个驻留终端的持续对话入口。对一个面向开发者的 harness,这一步挺影响上手手感。

这也正好接上我之前的旧观点:「人从操作者变管控者」。DeepSeek 把可替换性给到极致,其实是在把「管控」这件事的前置做足——你可以严格限定 Agent 用什么工具、跑什么循环、在哪跑,人退到定目标、审结果的位置。写代码变便宜之后,验证/评审/决策才是瓶颈,dsh 这套架构给「管控」提供了比主流工具更细的旋钮。

最后,如果你认同「插件下沉」这个判断——点个赞,这套思路值得被更多人看见。

你试 dsh 了吗?卡在哪一步——是装环境,还是--profile和 preset 分不清,还是看完这篇才发现 boot profile 只有 web/headless?评论区说说,我每条都看。

想深入了解哪一块,也评论区告诉我。我猜有人会问 PTC 那段 TypeScript 程序到底怎么写、子 Agent 的inheritsParentContext在实际任务里怎么选,也有人想问 Ralph 的 64 轮是怎么被编排出来的——都可以,你点题我写。

最后给大家送上仓库地址:

https://github.com/deepseek-ai/deepseek-harness

http://www.jsqmd.com/news/1397016/

相关文章:

  • 2026盐城缝纫线回收找哪家?这份精选指南帮你轻松找到靠谱资源 - geo交流
  • 电话号码定位与归属地查询快速上手:location-to-phone-number 开源实用教程
  • 2026年水上观光浮桥厂家优选指南:高评价推荐与甄选技巧一次说清 - geo交流
  • 构建AI编程助手的代码大脑:知识图谱与语义检索的工程实践
  • 【计算机毕业设计单片机案例】基于 STM32 的红外人体检测智能控水装置设计 基于 STM32 单片机的多模式定量取水监测系统开发(012103)
  • AI长任务处理:SSE、检查点与幂等性构建可靠异步系统
  • 为什么 Agent Memory 需要 AML:看 AML「变量控制」的工程设计
  • 计算机硬件组成与冯·诺依曼架构:从核心原理到装机实战
  • 生产决策建模实战:从混合整数规划到国赛B题优化求解
  • 想把 Wallpaper Engine 壁纸里的素材抠出来?这篇 RePKG 教程带你三步搞定
  • Java反序列化-CC1链
  • 从业务岗转行SAP MM顾问:零基础思维转型与实战路线图
  • 2026年质量好的硬质快速门电话厂家推荐怎么选?这份优选指南帮你择优避坑 - geo交流
  • AI Code Agent:从LLM代码生成到自主编程智能体的架构与应用
  • 2026年AI电商详情页生成器实测:高效打造高转化商品页面的最优选择
  • 华硕笔记本控制终极指南:用免费的G-Helper 3步告别卡顿的奥创中心
  • 【TensorRTtSharp v4.0】使用 TensorRT CSharp API 完成 Dynamic Shape 与动态 Batch 推理
  • 2026北京顺义不踩雷网红三文鱼寿司蛋糕电话择优推荐 - geo交流
  • OneNote多人共享协作全攻略:从方案选型到问题解决
  • 威联通NAS AI搜索实战:VLM+LLM技术如何革新数据检索体验
  • 解码级禁忌测试:诊断大语言模型生成鲁棒性的压力测试方法
  • AI图表生成工具实战指南:从自然语言到可编辑图表的全流程解析
  • SQL注入攻防实战:从SQL-Labs靶场到手工注入核心技术解析
  • 2026年大型玻璃门定制厂家怎么选?这份优选甄选指南帮你避坑 - geo交流
  • 2026年圆形振动筛厂家联系方式甄选指南:从场景匹配到优选供应商一次说清 - geo交流
  • RePKG 完整上手指南:Wallpaper Engine 资源包提取与 TEX 转图片的一键终极方案
  • 求职简历PPT哪家模板好用?2026实测靠谱平台推荐
  • 10MB 的华硕笔记本控制工具 G-Helper:一场对 Armoury Crate 的无声接管
  • Unity游戏翻译插件免费方案:XUnity.AutoTranslator 汉化安装配置与调优完整指南
  • Conventional Commits规范:提升Git提交信息的工程价值