$ dsh –这是个什么
每隔几周就有一个新 agent 框架发布,而每一个最终都会撞上同一堵墙:模型是最容易的部分。难的是模型周围的一切——连接各家 provider、暴露工具、记住昨天的会话、决定一个子进程能碰什么、渲染一个 UI——并且要在用户装了三个陌生人写的插件之后依然不散架。
DeepSeek Harness(dsh)就是 DeepSeek 对这堵墙的回答。它是一个开源 agent harness,MIT 协议,目前处于 developer preview,建立在一个赌注上:一切皆插件。不是「一切皆可扩展」——是一切本身就是扩展,包括你以为核心的那些部分。
harness 到底是什么
把一个 agent 剥到只剩模型调用,你得到的是一个 chat completion 接口。有用,但它读不了文件、记不住昨天、也不会在跑 rm 之前先请示。补上这些能力——并让它们互不踩脚——的那层脚手架,就是 harness。
具体到 dsh,它拥有的是底盘:模型适配器、带守卫执行管线的工具注册表、只追加的会话日志、沙箱与审批策略、凭据、设置、Web UI。而其中每一个都是挂载进共享上下文的插件。架构文档说得直白:“There is no privileged core to patch: you extend dsh by mounting a plugin beside the others, and registrations are effects that unwind when their plugin unloads.”(没有特权核心可打补丁:你通过把插件挂到其他插件旁边来扩展 dsh,注册是 effect,插件卸载时随之回退。)
这句话就是整个产品。如果连 agent 循环本身都是插件,那么记忆插件、TUI 客户端、飞书桥就都不是二等公民——它们和循环是同一等公民。这也正是本站存在的原因:当一切能力都是插件,生态就是产品,而总得有人把生态画成地图。
解释整个系统的五个服务
插件向共享上下文贡献服务,服务认领稳定的 key。记住五个 key,你就能听懂几乎任何 dsh 讨论:
| 服务 | 职责 | Key |
|---|---|---|
| Sessions | 只追加的会话事件日志 | ctx.sessions |
| System prompt | 提示词分段与工具 schema 组装 | ctx.systemPrompt |
| Tools | 带作用域的工具注册表与执行管线 | ctx.tools |
| Agents | Agent 接口、实时注册表、agent/* 事件 |
ctx.agents |
| LLM | 消息/流词汇表与适配器缝 | ctx.llm |
插件不 import 这些服务的具体实现——它声明需要它们。一个工具插件写 inject: ['tools'],加载器就会按住它,直到注册表存在。加载顺序通过依赖要求表达,而不是靠谁精心编排的启动序列。(支撑这套机制的框架是 Cordis,它有自己的论文和专门的指南。)
一个 turn 的解剖
上游术语表在这里异常精确,值得背下来:
- turn 是一个会话中对已收输入的一次排空(drain)——模型和工具停下来、或策略介入时结束。
- step 是一次模型请求加上它的响应引发的工具执行。一个 turn 包含零或多个 step。
流程是:输入进入 agent 的 inbox。driver 认领一批,turn/start 事件开启 turn,每个 step 从已安装插件注册的提示词分段和工具 schema 组装出提示词。模型流式返回(assistant/chunk 事件实时追加);如果它请求工具,每个调用穿过带守卫的管线——pre-execute、execute、post-execute——结果喂给下一个 step。当不再欠任何东西,turn/end 关闭这个 turn。
有意思的是缺的东西:没有隐藏状态。转向消息和注入上下文走的与普通输入是同一个 inbox、同一条认领路径。只有一扇前门,每个访客都登记。
日志即事实
这条不变量把 dsh 与一千个 agent 套壳区分开:model-visible means logged(模型可见即已落日志)。任何到达模型请求的东西,都必须能从会话日志重建——而且有运行时断言在守着。
会话日志是 SessionEvent 的只追加流。fork、resume、transcript、telemetry、持久化全部从这条流派生;模型看到的历史是它的投影(deriveMessages()),不是一份会漂移的平行副本。想新增一种模型可见的输入?不要往提示词里塞私货——加一个新的会话事件,从日志渲染。
这也是我们 DSHSeek 愿意为这个生态画地图的原因:一个感知全程落日志的 agent,才是可审计、可引用、可信(或有据可疑)的 agent。
缝(seam):为什么一次替换能搬动一切
seam 是一个可替换的能力,有三个角色:Service Definition(谁拥有接口)、一个或多个 Service Provider(谁实现它)、Consumer(谁消费它,通常是面向模型的工具)。教科书例子:dsh-shell 定义缝,dsh-bash-local 与 dsh-bash-sandbox 提供实现,dsh-tool-bash 消费它。
因为文件系统和子进程的 provider 共享同一个执行世界,把它们指向一个远程沙箱时,Bash、PTY、LSP 会一起搬过去——不需要 fork 任何东西。Subagent 也以同样的方式在同一接口后面变化,从一个全新的子 agent 到另一个产品里的委托 turn。
当你在资源地图里评估一个插件时,诚实的问题永远是:它坐在哪条缝上。占据 provider 槽位的插件是在替换能力;监听 tools/* 事件的插件是在为能力加策略。两者都正当,但失败方式完全不同。
Profile、bundle、patch
一个运行中的 dsh 是启动时由有序层组合出的插件树。profile(如随包发布的 web 和 headless 模板)列出它堆叠的 bundle 并保存你自己的 cordis.patch.yml。bundle 是「配置行 + 它们挂载的代码」的发行格式——它插入的一切都能被上面的层继续打补丁。
层按顺序应用:各 bundle、profile 的 patch、home 级 patch、--patch 覆盖层。patch 按 id 定位一行,整体替换或插入新行。想看你机器实际启动的完整组合:
dsh --profile web --dump-config
它打印的任何一行都可以被你自己的 patch 替换。这就是配置驱动组合的应许之地:不是一个设置页面,而是一棵可检查、可 diff 的树。
跑起来
npx @deepseek-ai/dsh web
Web UI 会起在 http://127.0.0.1:3080。动手前两句诚实的警告:
- 这是 developer preview。 README 用大写字母说:THERE WILL BE COMPATIBILITY-BREAKING CHANGES。锁版本。
- 生态很年轻。 我们地图上的插件从 86k star 的工具到双位数的周末实验都有——把 star 当信号而非保修,记得看
verifiedAt。
常见坑
- 不要用 fork 来定制。 架构给你事件、缝和 patch,就是为了让你不必 fork。fork 会把你冻死在所有未来的修复之外。
- 注册前先想作用域。 注册要么全局、要么 scope 到某个 agent;scoped 工具只在该 scope 内遮蔽同名全局工具。如果你的工具到处可见,说明你注册成了全局。
- 事件是有契约的。 有些是 waterfall——监听器必须调用
next()委托,短路是一个决策而不是事故。监听之前先读事件的模式。 - turn / step / round。 round 是外层策略迭代(goal round、fresh-agent 尝试)。该数 round 的地方数了 turn,你的统计就会说谎。
FAQ
能上生产吗? 不能——developer preview,官方明说会有破坏性变更。可以用它构建和学习,别拿 SLA 押注。
必须用 DeepSeek 的 API key 吗? 模型层和一切一样是适配器缝(ctx.llm),适配器是插件;你把它指向哪里是配置问题。
我只想要个 CLI,为什么要关心一篇论文? 因为当两个插件都想改写提示词时,总得有人算清楚谁赢、卸载时谁回退干净。这篇论文就是那个「有人」。下一步读什么是 Cordis。
接下来去哪
- 什么是 Cordis? —— 「一切皆插件」背后的论文,用人话讲
- 资源地图 —— 本站索引的核验插件生态
- 官方架构文档 —— 来自源头的系统地图