一句话概括
DeepSeek Harness 是一棵插件树:一个运行的 dsh 进程是一组在启动时按有序补丁层组合起来的 Cordis 插件,产品中的每一个部分——模型适配器、工具注册表、session 日志,乃至 agent 循环本身——都是并排挂载、可从配置替换的插件。
这里没有需要打补丁的特权核心。扩展 dsh 的方式是把一个插件挂到其余插件旁边,而每一次注册都是会在其插件卸载时按可预期顺序展开的副作用(effect)。
以 Cordis 上下文为骨干
Cordis 是 dsh 底层的 vendored 插件框架。每个能力都跑在同一个 Context(ctx)上。一个服务认领一个稳定的 ctx.<key>——ctx.tools、ctx.llm、ctx.sessions——其他插件按 key 而非 import 具体实现来找它。依赖通过 inject 声明,加载顺序以服务需求表达,而不是手写的启动时序。
插件向共享上下文贡献三样东西:
- 服务(Services)——安装到
ctx.<key>的值(session 存储、LLM 适配器注册表)。 - 类型化事件(Typed events)——通过 TypeScript 声明合并声明名称,并以
emit、waterfall、parallel或serial分发。 - 可逆副作用(Reversible effects)——通过
ctx.effect()或ctx.on()安装的注册项(prompt section、工具 schema、监听器、provider),在重载与拆除时可预期地展开。
结果是某个能力"接缝(seam)"有三个角色——声明接口的服务定义(Service Definition)、实现它的服务提供者(Service Provider),以及使用它的消费者(Consumer)——而替换一个 provider 就改变整个产品。例如 filesystem 与 subprocess 的 provider 共享同一个执行世界,因此把它们指向一个远程沙箱,Bash、PTY 与 LSP 会一起迁移,无需任何 provider 分叉。
Profile 与 bundle
两个 package.json 层面的概念架构起这棵树:
| 概念 | 定义 | 声明位置 |
|---|---|---|
| Profile | 一份命名组合,存于 $DSH_HOME/profiles/<name>,列出它堆叠的 bundle、它安装的树外插件,以及用户自己的 cordis.patch.yml | profile package.json 中的 dsh.profile.bundles |
| Bundle | Cordis 配置行及其所挂载代码的分发格式——它插入的任何内容都可被其上层继续打补丁 | bundle package.json 中的 dsh.bundle.patch |
web 与 headless 作为 profile 模板随附。三个 bundle 覆盖各层:
@deepseek-ai/dsh-base——每个 profile 的第一层:模型适配器、工具、持久化、沙箱与审批策略、设置、凭据、遥测。@deepseek-ai/dsh-web-app——添加浏览器应用。@deepseek-ai/dsh-headless——添加一个完全没有 server 的一次性运行器。
各层按下述顺序作用于一个空条目列表:profile 里列出的每个 bundle、然后 profile 的 cordis.patch.yml、再然后 home 级补丁文件、最后任意 --patch 覆盖。一条补丁按 id 瞄准一行并替换其整个 config,或插入新行。
核心包对照表
packages/core 承载"产品 API 主干"——插件与消费者所依赖的稳定表面:
| 包 | 负责 | ctx key |
|---|---|---|
core/session | 只追加的 SessionEvent 日志与内存存储 | ctx.sessions |
core/system-prompt | prompt section 与工具 schema 的组装 | ctx.systemPrompt |
core/tools | 作用域化的工具注册表与受守卫的执行流水线 | ctx.tools |
core/agent | Agent 接口、活跃注册表与 agent/* 事件 | ctx.agents |
core/agent-loop | 实现该接口的默认驱动器 | ctx.agentLoop |
core/scope | 每智能体的作用域化注册原语 | 库——无 key |
llm/llm | 消息与流词汇,外加适配器接缝 | ctx.llm |
注意这里刻意的拆分:core/agent 拥有公开的 Agent 契约,而 core/agent-loop 是它的唯一默认实现。扩展插件依赖 agent 这一接缝(包括它们需要发起者 agent 的时候),从不直接依赖 agent-loop,因此驱动器保持可替换。core/scope 是唯一不是服务的包:一个无依赖的库(createScope/scopeOf/scopeTarget),位于 session/ 与 system-prompt/ 之下,正是为了让这两个包消费它而不形成环。
Host 与 Client 拆分
dsh 对同一棵源码树按两个面(face)构建(根 tsconfig.host.json 与 tsconfig.client.json,由 --env.DSH_BUILD_FACE 切换):
- Host——Node 进程:运行 webserver、API 网关、沙箱、subprocess 与 filesystem provider,以及 agent 循环。这就是
dsh web启动的东西。 - Client——提供给页面的浏览器 bundle。它挂载 web client 模块与双半包(dual-half package)的浏览器半(例如
packages/extensions/cordis-client-runner中的 Cordis 动态包运行器),并通过 API 网关(packages/api/gateway)与 host 通信。
纯 host 包(如 @deepseek-ai/dsh-cordis-host-runner)是本进程自己的;带有浏览器半的包必须由页面执行,因此 host 挂起、由浏览器作答。两个面从不混入同一个聚合:host 聚合排除浏览器半包,使每个面保留自己的 TypeScript 编译程序。
启动时的层次组合
启动是 packages/boot/app-boot 里的一次调用:
dsh web
└► bin.ts parseDshArgs → { mode: 'profile', profile: 'web', args, patches }
└► profile-boot.ts runProfile
├► composeProfile ─ healProfilesModuleFallback
│ loadProfile → bundle 层(base、web-app)
│ + profile cordis.patch.yml
│ + home cordis.patch.yml
│ + --patch 覆盖
│ + 遥测开关 + 随附 preset 根
├► boot(name, rootConfig, patches, prepare)
│ new Context (Cordis)
│ ctx.plugin(Loader) cordis-plugin-loader
│ prepare: provide 环境 + cmdlineArgs/appExit
│ mountRootInclude → 'cordis:include' 内置
│ Loader 创建根 Include 条目
│ Include 读取 cordis.yml(空的 profile 根)
│ include 应用展平的补丁列表,
│ 挂载每一个插件行 → ctx.sessions、工具注册表、…
│ await loader, assertEntriesActivated
└► watchUserPatches 经 HMR 让 cordis.patch.yml 保持热重载prepare 回调在任何配置树条目挂载之前运行,提供启动环境快照与 ctx.cmdlineArgs/ctx.appExit——这些是启动器事实,不是配置。mountRootInclude 调用把静态 import 的 Include 挂为 cordis:include 内置并附带 cordis:group,因此配置树的行在不依赖被包含树自身说明符解析的情况下即可解析。
插件树分层图
┌──────────────────────────────────────────────┐
--patch │ 覆盖补丁(CLI,最高优先级) │ ─┐
├──────────────────────────────────────────────┤ │
$DSH_HOME/ │ home cordis.patch.yml(所有 profile) │ │
cordis.patch. │ │ │ 应用顺序
yml │ │ │
├──────────────────────────────────────────────┤ │ 自下而上
profile/ │ profile cordis.patch.yml(用户自己的层) │ │ (后到者优先)
cordis.patch. │ │ │
yml ├──────────────────────────────────────────────┤ ┘
│ @deepseek-ai/dsh-web-app (浏览器表面) │
│ └ 或 @deepseek-ai/dsh-headless(一次性) │ 补丁层
├──────────────────────────────────────────────┤ 按 dsh.profile
│ @deepseek-ai/dsh-base │ .bundles 顺序
│ 模型适配器、工具、持久化、沙箱、审批、 │
│ 设置、凭据、遥测 │
└──────────────────────────────────────────────┘
根 profile/cordis.yml = [ ] (空条目列表)组合复用 boot include 所做的那同一个 applyEntryPatches 调用——composeEntries 复用它,因此 --dump-config 打印出与同一次调用将挂载的完全一致的内容。
各看什么(阅读地图)
| 你想… | 请看这里 |
|---|---|
| 看看你的机器实际启动的树 | 运行 dsh --profile web --dump-config |
| 组合 profile 与 bundle | packages/boot/app-boot/src/profile.ts(Profile、loadProfile、resolveBundleDir) |
| 从配置启动一棵树 | packages/boot/app-boot/src/index.ts(boot、mountRootInclude) |
了解 CLI 标志与 dsh plugin | apps/cli/src/args.ts、apps/cli/src/plugin.ts |
| 读 session 日志 / turn 流程 | packages/core/session/src/、docs/subsystems/session.md |
| 看 Agent 接口与事件 | packages/core/agent/src/runtime-types.ts、docs/subsystems/core.md |
| 端到端追踪一个 turn | packages/core/agent-loop/src/agent.ts(ReactLoopAgent)、docs/agent-lifecycle.md |
| 新增模型 provider | 在 ctx.llm(packages/llm/llm/)注册其适配器 |
| 新增模型面对工具 | 在 ctx.tools 注册;其 schema 会进入 prompt 组装 |
| 替换一个 provider 世界 | docs/capability-seams.md 中的各接缝 |
| 改动循环本身 | 本页路线图(循环是 core/agent-loop),以及 docs/architecture.md |
把事件作为扩展点
选择正确的事件域是大多数变更的第一步:
- Session 事件是追加到日志、并通过
session/event广播的持久事实——当该事实必须跨重载存活时用它。 - Agent 事件(
agent/*)携带活跃的Agent——inbox、step、status、request、validation、continuation——用来观察或拦截进行中的工作。 - 能力事件把策略与适配器挂到某个接缝(
fs/*、tools/*、telemetry/*)上,而无需 import 循环。
事件地图(docs/event-producer-consumer.md)列出了每个事件的生产者与消费者。
延伸阅读
- 启动过程与 CLI——从
dsh web到运行中的 server 之间发生了什么。 - 扩展(Cordis)系统——服务、类型化事件、可逆副作用、dsh 的 Cordis 封装。
- 运行时与智能体生命周期——agent 生命周期状态、setup 窗口、持久的
session/event流、事件分发。 - 仓库文档:
docs/architecture.md、docs/capability-seams.md、docs/module-graph.md。 - 源码:
packages/core/README.md、packages/boot/app-boot/src/profile.ts、packages/core/agent/src/runtime-types.ts。