DeepSeek Harness 最狠的不是免费,是把 spawn 子 Agent 做成了协议

上周我让 Claude Code 干一个重构活,跑到一半发现它在偷偷开 subagent,一层套一层,等我反应过来 token 已经烧穿了三层上下文。

我盯着屏幕上那个我根本没授权的子任务,第一反应不是「模型真聪明」,而是「这玩意到底是谁派出去的,它派了几层,我怎么知道它还能不能再派」。

说实话,这是我用 Claude Code 大半年一直别扭的地方。子 Agent 很好用,但 spawn 不 spawn、嵌套多深、能不能续谈,全靠模型读 prompt 自己悟,没有硬约束。你在 subagent 的 description 里写「不要嵌套」,它心情好就听,心情不好就当没看见。

然后这周 DeepSeek 扔出了 Harness v0.1,4 天 145,505 颗星 GitHub 2026-08-17。我把它 subagent 子系统的类型源码从头读了一遍,读完最大的感受不是「免费真香」,而是他们把我上面那个别扭,做成了一套带类型、带能力协商、带持久化深度的显式协议。

这篇就拆这件事。不聊 npx 一键启动,不聊 star 数奇观,聊点只有翻源码才看得见的东西。

先给结论

如果你只想知道这东西跟我有没有关系,看这张表。

你是谁 现在该干嘛
天天用 Claude Code subagent,被嵌套和上下文串台坑过 必读,DSH 的协议设计能直接纠正你对 agent 边界的理解
在评估自建 agent runtime / 做多 agent 编排 强烈建议读源码,它的 Capability Seam 抽象值得抄
想找个 Cursor 替代品写业务代码 别冲动,这是 harness 不是 IDE,rc.7 还在天天 breaking
只想蹭热点发朋友圈 看到这就够了,下面很硬

一句话定位,DSH 不是 Cursor 竞品,它是 Agent = Model + Harness 这个公式里的 Harness 层 deepseek.com/harness。模型负责思考,harness 负责给模型「理解环境、使用工具、在真实场景持续工作」的能力。Claude Code、Codex 自己也是 harness,只不过它们是封装好的产品;DSH 想做的是把 harness 本身拆开,让每一层都可替换。

一切皆插件,包括 agent loop 自己

要看懂子 Agent 协议,得先看懂 DSH 的骨架,因为子 Agent 在这套骨架里根本不是核心组件,而是一个「可选能力」。

DSH 基于一个叫 Cordis 的内核(cordiverse/cordis,5,366 stars),而且直接 vendored 进了仓库 DSH vendor/。Cordis 本身啥 agent 能力都没有,它只做一件事,插件的加载、卸载和依赖管理。在这个内核之上,DSH 把所有东西都做成了插件,model adapter 是插件,tool registry 是插件,session log 是插件,连 agent loop 本身都是插件

这句话分量很重。没有一个不可 patch 的特权核心。你想换模型,挂个 adapter 插件;你想换掉整个 agent 循环逻辑,也是挂个插件的事。扩展 DSH 不是去 fork 源码改核心,而是在别的插件旁边再挂一个,注册是 effect,插件卸载时自动回滚。

配置层叫 Profile + Bundle。一个 profile 是有序 bundle 层叠出来的插件树。dsh-base 是每个 profile 的第一层,装 model adapters、tools、persistence、sandbox、approval policy;dsh-web-app 加浏览器,dsh-headless 加一次性 runner。patch 按行 id 替换整行 config。

这里有个关键概念叫 Capability Seam,一个 seam = Service Definition(声明接口)+ Service Provider(实现)+ Consumer(通常是给模型用的工具)。三者齐备,这个能力才算在系统里「立住了」。

子 Agent,就是这样一个 seam。

子 Agent 为什么是 seam 而不是 loop 的一部分

大多数 agent 框架(包括 Claude Code)的处理方式是,把子 Agent 调用硬编码成一个工具,叫 Agent 工具,模型决定调不调、调哪个 subagent_type。这是「loop 的一部分」。

DSH 不这么干。它把 subagent 做成了一个和 bash 平级的可选能力 ,类型定义不在 core 里。但 subagent 和 bash 有个本质区别,bash 只允许一个 executor,你不能同时注册两个 bash 实现;subagent 却允许多个 provider 按名字共存 ,全部挂在 ctx.subagents 上。

这个设计的潜台词是,「怎么 spawn 一个子 agent」根本没有唯一答案。你可以在同进程里 spawn,可以 fork 一个带父上下文的,可以走远程 ACP 协议,甚至可以把整个 Claude Code 或 Codex 当成后端拉起来。框架不该替你决定,它只负责定协议。

DSH 内置了 6 个 provider DSH 源码 packages/subagent/,我给你一个个摆出来。

Provider 机制 要不要父上下文
spawn-in-process 同进程新建 agent 不继承
fork-in-process 同进程 fork,继承父已完成 turn 前缀 继承 seed
acp Agent Client Protocol 远程 agent 不继承
codex 拉起 codex app-server --stdio 不复制父对话
claude-code @anthropic-ai/claude-agent-sdk@0.3.220 persistSession:false
dsh-sdk DSH 自己的 JSON-RPC SDK 按 descriptor

注意后面两个,DSH 把自己的直接竞品做成了 subagent backend,这事我单独开一节讲,戏剧性很强。

四个布尔值,把「能力不匹配」从运行时提前到启动时

这是我觉得整个 DSH 最值得抄的设计。每个 provider 在启动前必须声明自己支持哪些能力,就四个布尔值。

ts 复制代码
// 来源:DSH 源码 packages/subagent/src/types.ts
interface SubagentCapabilities {
  readonly outputSchema: boolean  // 能不能返回结构化输出
  readonly depthLimit: boolean    // 能不能遵守委派深度上限
  readonly toolFilter: boolean    // 能不能裁剪子 agent 的工具集
  readonly persona: boolean       // 能不能给子 agent 独立人格
}

看着简单对吧。狠的是后半句,如果父 agent 发起一个请求,要求子 agent 必须支持某个能力(比如我要你返回结构化 outputSchema),而这个 provider 声明它不支持,DSH 的做法不是「先接着,做不到拉倒」,而是启动时直接抛 SubagentError('UNSUPPORTED_CAPABILITY') 拒绝启动

官方管这叫 fail loud, no silent degradation,失败要响,不许静悄悄降级。

你可能觉得这有什么了不起。我给你看 Claude Code 的对照面。Claude Code 的 Agent 工具靠 prompt 里的 description 让模型「理解」这个 subagent 能干什么。模型读完 description,觉得它能干,就把任务派过去;结果这个 subagent 其实不支持结构化输出,或者工具集不对,它不会提前拒绝,它会「接了然后做不好」,最后给你一坨似是而非的自然语言。你排查半天才发现是能力不匹配。

DSH 把这件事从「模型靠语义猜」变成了「provider 用类型声明,运行时按契约校验」。四个布尔值就是一份合同,签字画押,做不到就别上桌。

这才是协议。

一次性 vs 可续,两种生命周期

DSH 的子 agent 有两种活法,one-shot 和 continuable。这个区分也是 Claude Code 完全没有的。

One-shot(一次性) 好理解,SubagentRun 就是一个句柄,派出去,干完,返回一个 SubagentResult,完事。没有 steering,没有 resume。结果里带 output、structured(如果请求了 outputSchema)和 stopReason,stopReason 就那几种,completed / aborted / error / max-tokens / refusal。

Continuable(可续) 才是真正有意思的东西。它是一个持久化的子 Session,可以跨多次对话甚至跨冷启动存在。一个 continuable 子 agent 最多有一个进程内 Activation(驻留期),Activation 不是 request 也不是 Task,它能在里面跑多个 FIFO turn,甚至在它自己派出去的子代还在跑的时候保持驻留。

这里有几个操作得讲清楚。

startContinuable() 先预留一个稳定的 child id,provider 返回一个 detached 的 ContinuableCreateSpec,里面只有可选的 parent-history seed,然后 continuation manager 自己造 agent、建 inbox、提交初始 prompt。注意,provider 不参与 continuable 的后续生命周期 。冷恢复根本不经过 provider,manager 拿通用 descriptor 加 ctx.agents.resume() 就把它拉起来了。

followup() 是唯一的续消息操作。如果 Activation 在 running,就入队;如果在 waiting,就唤醒;如果压根没有 Activation,就冷启动一个新的。一个接口把三种状态全兜住。

interrupt() 是唯一公开的停止操作,授权检查后调 Agent.cancel(cause, {keepInbox:true}),不 await 静止,不清 inbox,也不动它的后代。意思是你打断它,它收件箱里没处理的消息还在,后代也继续跑。

还有个 reportFrom(),子到父的回报通道,凭证是子自己,调用者不能指定收件人。谁派的它,它就报给谁,不靠消息里的字段授权。

这套东西解决的问题是,长任务里子 agent 不该是「一锤子买卖」。你派一个子 agent 去做代码检索,它可能需要被追问、被打断、被要求阶段性汇报,然后过两天冷启动回来接着干。Claude Code 的 subagent 做不到,它就是一次性函数调用,没有「会话」的概念。

委派深度持久化,嵌套三层是硬拒绝不是软提醒

回到我开头那个痛点,模型偷偷 spawn 了三层 subagent。DSH 怎么防的。

它把委派深度持久化在 SessionHeader.delegationDepth 字段里,运行时还有个 AgentOptions.subagentDepth,两者取较大值。子 agent 创建时,持久化父 depth 加 1。关键是冷启动不能把这个深度降下来 ,你 resume 一个 depth 为 2 的 session,它还是 2,没法靠重启洗白。一旦超过配置的 maxDepth,直接硬拒绝。

这跟 Claude Code 的区别是本质性的。Claude Code 的「不要嵌套」写在 prompt 里,是软约束,模型听不听是缘分;DSH 的 maxDepth 写在持久化的 session header 里,是硬约束,超了就是 error,模型再想 spawn 也 spawn 不出来。

再讲一个 fork 的细节,很见功力。fork-in-processCreateAgentOptions.seed 给子 agent 传父 log,但传的不是整个历史,而是父 log 的平衡已完成 turn 前缀 ,也就是截到最后一个 turn/end 为止,进行中的那个不平衡 turn 被排除掉。为什么这么抠,因为 DSH 的 session log 是 append-only 的,有个运行时不变量叫 Model-visible 等价于 logged,任何到达模型的东西必须能从 log 重建。传一个半截 turn 过去,replay 的时候 invariants 会对不上。传「平衡前缀」就是为了保证子 agent replay 出来的状态跟父 agent 在那个时间点完全一致。

这就是源码级阅读才能看到的东西,人家不是随便 fork 一下,是连「截到哪」都有协议层面的理由。

三种 Harness 哲学横评

铺垫够了,上桌。Claude Code、Codex、DSH 三家在子 Agent 这件事上的设计哲学,我整理成这张表。

维度 Claude Code Codex DeepSeek Harness
子 agent 模型 Agent 工具加 subagent_type,单进程,模型决定 spawn app-server stdio 沙箱进程,审批驱动 命名 provider 注册表,多 provider 共存,能力显式协商
谁定边界 模型,靠 prompt 和 description 产品加审批策略 provider 能力声明加 start 时 fail-loud 校验
隔离 同进程独立上下文 进程加 sandbox provider 可选,同进程 spawn/fork、远程 ACP、外部产品进程
可替换性 Skill/MCP 可加,loop 不可换 沙箱/审批可配,核心固定 一切皆插件,agent loop 本身可换
委派深度 软约束,prompt 引导 产品自管 持久化 delegationDepth 加 maxDepth 硬拒绝
父子上下文 子收 prompt,不收父历史 不传父对话 fork 可传平衡 turn 前缀,其他不传
可续子 agent 无,ephemeral continuable 加 Activation 加 followup/interrupt/report
跨产品委派 不能委派给 Codex 不能委派给 CC claude-code 和 codex 都是内置 provider,可互委

我挑三个最有 insight 的差异展开。

第一,谁来决定子任务的边界。 Claude Code 把这个权力交给了模型,模型读 description 自己判断要不要 spawn、spawn 谁。这很灵活,但边界是隐式的、语义的、随时可能漂移。Codex 把权力收给产品和审批策略,更可控但更死。DSH 走了第三条路,边界由 provider 的能力声明决定,启动那一刻用类型系统校验,过了就过了,过不了直接拒绝。这是把「边界」从一个 prompt 工程问题变成了一个协议契约问题。

第二,可续子 agent 这件事,只有 DSH 有。 Claude Code 和 Codex 的子 agent 都是 ephemeral 的,一次调用一次消亡。但真实的长任务里,子 agent 经常需要被追问和续谈。DSH 的 continuable 加 Activation 加 cold resume 是目前我在开源 agent 框架里看到最完整的子会话生命周期设计。代价是复杂度,这套 continuation manager 的心智负担不低,但它解决的是真问题。

第三,跨产品委派。 这个下一节细讲,它是整张表里最颠覆的一格。

把竞品做成自己的 subagent backend

这一节是全文最有戏剧性的部分,你坐稳。

DSH 内置了 codexclaude-code 两个 provider。意思是,你在 DSH 里跑一个 agent,这个 agent 可以把子任务委派给 Codex,也可以委派给 Claude Code。DSH 把它的直接竞品,变成了自己的执行后端。

我读了官方那份 Agent Note(2026-08-04-claude-code-and-codex-subagent-backends.md),里面的设计决策非常刻意,我给你拆。

这两个 provider 都是 one-shot onlyinheritsParentContext: false,不声明任何可选能力。每次委派,拉起一个全新的产品进程和一个不可续的产品会话。只接受独立文本任务,把父 session 的 cwd 传过去,不复制父对话

具体到实现,Codex provider 启动 codex app-server --stdio,建一个 ephemeral:true 的 thread,turn/completed 是权威终态。如果任务执行中需要审批而没人在,审批策略默认 cancel 或 decline,不授予任何权限。Codex 这边 pin 的是 0.147.0,走 Responses 协议。

Claude Code provider 用 @anthropic-ai/claude-agent-sdkquery()persistSession:false,禁用 AskUserQuestion,而且故意不传 canUseTool 和 elicitation 回调,意思是一旦需要人机交互就直接失败,不弹问题。pin 的是 SDK 0.3.220 / CLI 2.1.220 DSH package.json

产品进程由 dsh-subprocess 统一管,凭据清洗、进程树终止、整树退出观察都在这一层。

为什么设计得这么「绝」,每次都全新进程、不传上下文、无人交互就失败。官方原话是,每次委派付出全新产品进程加独立模型上下文的代价,产品 payload 回到父级的只有最终文本。这不是偷懒,这是安全边界

你想,如果子 agent 是一个完整的 Claude Code 进程,它默认继承了你父会话的全部上下文和权限,那它就不是「子 agent」了,它是你父 agent 的一个分身,边界瞬间崩塌。把它做成 one-shot、无上下文、无审批就死,等于在 DSH 的编排层和外部产品之间画了一道清清楚楚的线,你就是个干独立文本活的临时工,干完交结果,别想碰我的会话状态。

还有个细节,这两个 provider 默认不装在 dsh-base 里,是 production-install exclusion,需要你在 Profile 里显式安装、放到 host 平面,再由 Agent Preset 决定要不要暴露对应工具。也就是说,DSH 团队自己都觉得这是个高级能力,不打算让新手一上来就玩跨产品委派。

这一节的架构判断我留给你,一个框架敢把竞品做成自己的 backend,要么是狂妄,要么是它对自己「编排层」的定位有足够的自信。我倾向于后者。

预览版的坑,以及现在值不值得上手

吹完了,说点实在的。这东西现在是 rc,而且是 rc.7,不是 1.0。

仓库 2026-08-13 才创建,到我写稿这天 4 天 GitHub repo.created_at。npm 上 latest 是 rc.6,next 是 rc.7 npm view 2026-08-17。官方 README 明明白白写着 THERE WILL BE COMPATIBILITY-BREAKING CHANGES。SESSION_FORMAT_VERSION 是 0,没有兼容承诺,SQLite 用单调的 SCHEMA_VERSION 拒绝旧格式,意思是你今天存的 session,明天升级可能就读不出来了。

环境门槛也不低,Node 要 ^22.19.0 || >=24.0.0,包管理器 pin 了 pnpm 11.7.0 DSH package.json engines。头号贡献者 tianyicui 一个人提了 5262 个 commit gh contributors,你大概能感受到这项目现在有多早期、多集中在少数人手里。

启动命令倒是简单。

bash 复制代码
# 需要 Node >= 22.19,官方一键启动 Web UI(我本机沙箱网络超时没跑成界面,命令来自 README)
npx @deepseek-ai/dsh web
# 默认监听 http://127.0.0.1:3080

但我必须诚实,我这边 npx 在沙箱里安装超时,没拿到实时 Web UI 截图。这篇是源码级拆解,我读完了架构文档、subagent 的 types.ts/index.ts/continuation.ts、Codex 和 Claude Code provider 的 Agent Note、sandbox 文档和 base bundle README,但我不编造 UI 操作体验。市面上那些「我跑了一遍 DSH 真香」的文章,你留个心眼,很多可能 npx 都没装完。

我的上手建议分三档。

  • 如果你是做 agent 基础设施的,现在就去读源码,重点读 packages/subagent/,Capability Seam 和 continuation manager 这两套抽象值回票价,哪怕最后不用 DSH。
  • 如果你是想拿它当生产工具写业务的,等 1.0。rc 阶段天天 breaking,session 格式都不兼容,别拿生产项目陪跑。
  • 如果你是来凑热闹的,star 一下放观察列表就行,生态刚起来,awesome list 都是今天才建的。

常见问题

DSH 和 Cursor 到底是不是竞品。

不是。Cursor 是 IDE,是带编辑器的产品;DSH 是 agent harness/runtime,是给模型提供工具、会话、沙箱、子 agent 编排的底层框架。它确实带个 Web UI,但定位是 harness 的一个界面,不是 IDE。更贴切的类比是,DSH 想做 agent 界的 Express/Fastify,而 Cursor 是 agent 界的 Chrome。

6 个 provider 里我日常该用哪个。

你在 DSH 内部编排,优先 spawn-in-process(要隔离就用它)和 fork-in-process(需要继承父上下文做接力时用)。跨机器或跨团队用 acpclaude-codecodex 是高级能力,需要跨产品委派时再开,而且要接受每次全新进程的代价。dsh-sdk 是给外部程序通过 JSON-RPC 接入用的。

continuable 子 agent 听着强大,是不是该一律用它。

不是。continuable 有持久化 session、有 Activation、有 inbox,复杂度和资源开销都比 one-shot 高一大截。绝大多数一次性子任务,one-shot 就够了。只有当你明确需要追问、打断、跨冷启动续谈时,才值得上 continuable。别为了用而用。

委派深度 maxDepth 设多少合适。

没有银弹,但我的经验是别超过 2 到 3 层。DSH 把它持久化硬拒绝是好事,但你要自己想清楚为什么需要深层委派。大多数「需要嵌套」的场景,其实是任务拆解没做好,而不是真的需要 5 层 subagent。把 maxDepth 当设计压力,逼自己把任务拆平。

Claude Code 用户现在能从 DSH 偷师什么。

就算不用 DSH,你也可以借鉴它的协议思维,在自己的 subagent description 里把「输入契约、输出契约、能力边界」写死,把不支持的能力显式标出来而不是让模型猜;在编排逻辑里自己做深度计数和硬截断;把一次性任务和可续会话在设计层面分开。协议不一定要框架给,你自己在 prompt 层和脚本层也能立。

写在最后

回到我开头盯着屏幕的那个下午。

我当时别扭的根本不是「模型偷偷开 subagent」这件事本身,而是没有人在协议层面回答「谁有权 spawn、能 spawn 成什么样、嵌套多深」这三个问题。Claude Code 把答案藏在 prompt 里,Codex 把答案收在产品策略里,DSH 第一次把答案摆到了台面上,用类型、用能力声明、用持久化深度,写成了一份可以校验、可以拒绝、可以跨产品复用的合同。

我不确定 DSH 最后能不能成,rc.7 的 breaking 节奏和单核心贡献者的集中度都是真实风险。但我确定一件事,agent harness 这个层未来一定会往「显式协议」方向走,而不是永远靠模型读 prompt 自己悟。隐式的东西终将被显式化,能被类型系统约束的,就不该留给心情。

你在用 Claude Code 的 subagent 时踩过什么坑,嵌套失控、上下文串台、还是子任务一去不回,评论区聊聊,我想看看有多少人跟我受过一样的罪。

做 Agent 开发的朋友,下篇我打算动手把 DSH 的 continuation manager 抄一遍,用最小可运行代码讲透 continuable 子 session 的状态机。感兴趣的话,关注「码哥跳动」,发出来你能第一时间看到。身边有人在做 agent 编排选型的,这篇可以直接甩给他,省得他再踩一遍软约束的坑。


🤖 配套实战手册

整理了一份《AI Agent 入门实战手册》,从零到可运行代码,含 5 个踩坑场景和生产级模板。

回复「agent」即可获取。

转发给正在学 AI 开发的朋友 ,少走弯路 🚀

相关推荐
啾啾Fun3 小时前
【AI Coding】5-Pi Agent Harness:一个极简终端编码Harness的解构
人工智能·microsoft·ai agent·claude code·ai coding
youcans_4 小时前
【嵌入式软件AI编程】08. 第一个STM32工程
stm32·单片机·ai编程·嵌入式软件·claude code
西瓜太郎123415 小时前
Claude Code、Codex CLI、Gemini CLI 能否共用一枚 Key?先看协议选择矩阵
前端·api 网关·claude code·gemini cli·codex cli
VIP_CQCRE19 小时前
Claude Code 接入 NanoBanana MCP:让 AI 编程助手直接完成图片生成与编辑
ai·mcp·claude code·ace data cloud
华科大胡子2 天前
用 Claude Code 重构遗留系统:从评估到落地的完整实践指南
claude code
码哥字节3 天前
AI一天出百本书,写不出我被Redis坑的那三天三夜
redis·ai编程工具
码哥字节4 天前
Claude Code 8问,最狠难题怎么破
mcp·claude code·agent skills
AI工具人PM产品经理4 天前
Claude Code TDD 实战:84 个测试护体的 spec→plan 开发流
jest·tdd·工作流·ai 编程·claude code