Skip to content

核心主张

README.mddsh 的架构称为「一切皆插件」。具体来说:

[Cordis] 是 dsh 下的框架:插件向共享上下文贡献服务类型化事件可逆效果。产品的每一部分 都是插件,包括模型适配器、工具注册表、会话日志乃至 agent 主循环本身,因此每一部分都可从配置中被替换。 —— docs/architecture.md

你要抓住的词是每一处写的「一个插件」:模型适配器(ctx.llm)、工具注册表(ctx.tools)、会话日志 (ctx.sessions)和 agent 主循环(ctx.agentLoop)都只是 Cordis 树中的条目。没有需要修补的特权核心—— 你只需在旁边挂载一个插件来扩展 dsh

插件向上下文贡献什么

一个 Cordis 插件是一个对象:要么是一个 Service 子类,要么是一个带可选 inject 属性、 包含 apply(ctx) 主体的普通函数。一旦 Cordis 挂载它,它就向共享 ctx 贡献三类东西:

贡献机制示例
服务声称一个稳定的 ctx.<key>ctx.llmctx.toolsctx.sessionsctx.agents
类型化事件通过 TypeScript 声明合并声明,用 emit / waterfall / parallel / serial 分发agent/pre-steptools/executeturn/start
可逆效果通过 ctx.effect()ctx.on() 安装,卸载时回卷一个提示词段、一个工具 schema、一个监听器

依赖通过 inject 声明:命名所需服务的插件会等到该服务存在后再启动,因此加载顺序被表达为服务需求,而非 手动排序。注册是效果,所以当插件卸载(切换 profile、编辑 cordis.patch.yml、重载)时,它安装的一切都会 可预测地回卷——不会留下半注册状态。

底层框架是内嵌的 Cordis,其设计在论文 A Programming Paradigm for Spatiotemporal Composability 中 阐述,仓库内解释见 docs/cordis-primer.md。提供「插件」抽象本身的包是 vendor/cordis 下的 @deepseek-ai/cordis

没有特权核心

由于每个能力都是一种服务,一次提供商替换就足以改变整个产品。docs/architecture.md能力接缝 (capability seam)模型把这一点讲得很清楚:一个可替换的能力有三个角色——

  1. 服务定义(Service Definition)——声明接口(例如 ctx.shell)。
  2. 服务提供商(Service Provider)——实现它(例如 dsh-bash-localdsh-bash-sandbox)。
  3. 消费者(Consumer)——使用它,通常是面向模型的工具(例如 dsh-tool-bash)。

单个角色并不构成接缝;新增一种能力意味着设计全部三者。packages/shell/shell/README.md 以微缩形式展示 了这一拆分:

角色
@deepseek-ai/dsh-shell服务定义:ShellExecutorctx.shell)+ 词汇类型
@deepseek-ai/dsh-bash-local服务提供商:本地子进程
@deepseek-ai/dsh-bash-sandbox服务提供商:同样的机制,但每个 spawn 都经由 ctx.sandbox 限制
@deepseek-ai/dsh-tool-bash消费者:面向模型的 bash 工具

由于消费者只与服务定义对话,把 dsh-bash-local 换成某个容器化或远程执行器无需创建任何 provider fork。 文件系统、子进程与子代理提供商也以同样方式工作(参见 docs/architecture.md 中的 capability-seams 一节)。

Profile 与 Bundle:命名组合

一个运行中的 dsh 并不是固定的程序;而是一棵启动时由有序层组合而成的插件树

  • Profile 是存储在 Harness home 中的命名组合。它列出所叠加的 bundle、持有其安装的树外插件,并保留 用户自己的 cordis.patch.ymlwebheadless 作为模板随附(app-boot 中的 PROFILE_TEMPLATES)。
  • Bundle 是一种 Cordis 配置行及这些行所挂载代码的分发格式。dsh-basedsh-web-appdsh-headless 是随附绑定的 bundle。

每个都在自己的 package.jsondsh 字段下声明自身:

jsonc
// 一个 profile 的 package.json
{ "dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"] } } }

// 一个 bundle 的 package.json
{ "dsh": { "bundle": { "patch": "./cordis.patch.yml" } } }

dsh.profile.bundles 按顺序列出 profile 的 bundle;dsh.bundle.patch 指向 bundle 的补丁文件——即 插入代码行的 YAML。组合机制位于 packages/boot/app-boot/(导出 resolveProfileDirinitProfileloadProfilecomposeEntriesPROFILE_TEMPLATESDEFAULT_PROFILE_BUNDLESPROFILES_DIRPROFILE_PATCH_FILENAME)。

层序

层以严格的顺序应用于空条目列表,源自 CLI 与 profile 机制:

text
空根
  → 每个 bundle 的补丁  (按 dsh.profile.bundles 顺序)
  → 该 profile 的    cordis.patch.yml
  → 家目录层        $DSH_HOME/cordis.patch.yml
  → 任意 --patch 覆盖

启动器标志本身见 apps/cli/src/args.ts;层序开示于 docs/architecture.mdLayers apply to an empty entry list in this order: each bundle in the profile's listed order, then the profile's cordis.patch.yml, then the home-level one, then any --patch overlay)。

检查组合后的树

因为运行中的树只是一叠补丁,它完全可检查。这条命令打印 web profile 的组合树,并在不启动树的情况下退出:

sh
dsh --profile web --dump-config

--dump-default-config 只打印 bundle 层(不含用户层、不含 --patch)。该 dump 由 app-boot 中的 renderConfigDump 使用 include 自身的 applyEntryPatches 渲染,因此打印出来的内容正是将要挂载的内容—— 工具不可能与启动过程漂移。它打印的任何一行都可以用你自己的补丁替换。补丁按 id 定位一行,并替换该行 的整个 config(不存在深合并层——profile 覆盖必须重述它保留的字段),或者 insert 一个新行。

为何这让每一部分都可被配置替换

回报是把「没有特权核心」变成现实:

  • bundle 的行集由补丁文件而非代码决定——按 id 覆盖一行即可改变行为。
  • 用户偏好存在于层级(先是每 profile,再是家目录层)中,每一层都压过其下的 bundle。
  • 由 app 插件(经由 @deepseek-ai/dsh-cmdlineparseCmdline)解析的标志从惰性 !!js 配置中读取, 例如 port: !!js ctx.webStartup.port ?? 3080,因此即便一个启动标志也只是某个配置表达式的值——在表达式 上写一个字面量,该标志就会静默失效。
  • 由于注册是可逆效果,实时编辑 cordis.patch.yml(经由 HMR 插件)会就地重组这棵树,而无需完全重启。

交互与生命周期的细节——webserver 行示例、web-startup 提供商与 profile 机制——在 启动流程与 CLI扩展系统(Cordis) 中有深入讲解。

延伸阅读

  • 什么是 DeepSeek Harness? —— 这一哲学所驱动的产品。
  • 启动流程与 CLI —— 实践中 profile、bundle 与 --dump-config
  • 扩展系统(Cordis) —— inject、事件与 ctx.effect()
  • docs/cordis-primer.md —— Cordis 五大思想小结。
  • docs/architecture.md —— 层序与能力接缝。
  • packages/boot/app-boot/README.md —— profile 与 composeEntries 细节。