【deepseek-harness】DSH 文档合辑 · 篇一:核心架构与概览

第一章 项目概览与入门

本章导读

本章是《DeepSeek Harness 实践指南》的起点,也是全书唯一的「从零开始」章节。我们将依次回答四个问题:

  1. DeepSeek Harness 是什么------它解决什么问题、由谁开发、架构上有什么与众不同的地方;
  2. 它处于什么产品阶段------开发者预览意味着什么,对你使用它的稳定性预期有什么影响;
  3. 如何把它跑起来 ------npmnpx)一键运行与从源码 clone + pnpm install + pnpm run build 两种路径的前置要求、命令与行为细节;
  4. 如何完成第一个任务------通过 Web UI(Web 用户界面)配置模型、选择工作区、运行一个真实任务,并理解权限审批闸门。

读完本章,你应当能够在自己的机器上拥有一个可用的 DeepSeek Harness 实例,并完成一个端到端的最小任务。后续章节将在此基础上展开模型提供方的高级配置、插件体系、工作区与权限策略、以及开发实战。

读者对象与前置知识

本章面向以下读者:

  • 新用户:第一次接触 DeepSeek Harness,希望尽快跑通一个实例;
  • 贡献者:希望从源码构建并理解项目结构,为后续开发章节做准备;
  • 集成方 :评估是否将 dsh 纳入自己的工具链,需要了解其架构理念与许可证。

前置知识要求:

知识 要求 说明
命令行 熟悉常用 shell 命令(cdgit clone 等) 安装步骤全程在终端中完成
Node.js 已安装,或知道如何安装 npx 方式运行的前置条件
pnpm 仅源码运行需要 pnpm installpnpm run build 的前置条件
LLM API 拥有至少一个可用模型密钥 推荐使用 DeepSeek API 密钥;也可用其他 OpenAI 兼容提供方

如果你完全没有命令行经验,请先补一节基础 shell 教程再来读本章;dsh 的安装与运行步骤均以命令行为载体。

1.1 什么是 DeepSeek Harness

DeepSeek Harness(命令行简称为 dsh)是由 DeepSeek AI 开发的开源 agent harness(智能体框架)。它面向的核心场景是:为大语言模型(Large Language Model,LLM)提供一套可运行、可观测、可扩展的智能体(agent)执行环境------模型在其中读取与编辑文件、运行命令、委派子任务,并在多轮(turn)对话中持续推进一个目标。

1.1.1 「harness」一词的含义

在智能体(agent)语境下,harness(运行框架 / 执行环境) 指包裹在大语言模型之外的那一层软件:它负责把模型的文本输出转化为真实世界的操作------调用工具、执行命令、读写文件------再把操作结果反馈给模型,如此循环。与「框架」(framework)强调代码组织方式不同,「harness」强调对模型的驾驭:模型本身只提供推理与决策,而它接触外部世界的每一寸缰绳,都握在 harness 手里。

dsh 正是这样一层缰绳。具体而言,它为模型提供:

  • 工具(tool)层:读写文件、执行 shell 命令、搜索代码、委派子智能体等工具的统一注册与执行流水线;
  • 会话(session)层:仅追加(append-only)的会话事件日志,使每一轮(turn)、每一步(step)都可回放、可审计;
  • 权限层:权限策略与审批闸门,决定哪些操作可以直接执行、哪些必须先经人工批准;
  • 模型适配层:将多家提供方(provider)的模型统一适配到同一套请求/响应形态之下,使模型可以即插即换。

1.1.2 它解决什么问题(为什么需要 harness)

如果把「让一个 LLM 帮我完成一个多步骤任务」当作一个工程问题,那么裸模型只有推理能力,没有任何手段接触外部世界。要让它真正干活,你需要自己解决一连串问题:如何把工具描述喂给模型?如何解析它的工具调用并执行?如何把执行结果拼回上下文?如何控制它能碰哪些文件、跑哪些命令?如何让它把大任务拆成子任务并分派?如何做日志与回放?

dsh 的工程价值就在于:把这一整套基础设施做成一个开箱即用、又处处可替换的开源实现。你不必从零搭一个「模型 + 工具 + 权限 + 会话」的执行环境;同时,因为一切皆插件,当默认实现不合你意时,你可以替换其中任意一层,而不必放弃其余部分。

理解 dsh 的架构,只需要抓住两个关键词:

1.1.3 一切皆插件

dsh 采用**一切皆插件(everything-is-a-plugin)**的架构。这意味着产品的每一部分------模型适配器、工具(tool)注册表、会话(session)日志、权限与审批策略,乃至驱动智能体循环(agent loop)本身------都是一个插件,挂载在一棵共享的插件树上。其直接后果是:

  • 没有特权内核 :不存在某个必须打补丁的核心模块;扩展 dsh 的方式,是把新插件挂载到既有插件旁边。
  • 一切皆注册,注册皆可撤销:插件通过贡献服务、类型化事件和可逆副作用参与运行,卸载插件时这些副作用会被撤销。
  • 一切皆可从配置替换:因为每一部分都是插件,所以每一部分都可以被你的配置覆盖。你可以用自定义插件替换默认的模型路由,也可以覆盖默认的工具集,而无需修改发行版代码。

1.1.4 基于 Cordis 驱动

插件树的运行时由 Cordis 提供。Cordis 的设计思想参见论文 A Programming Paradigm for Spatiotemporal Composability (一种时空可组合性的编程范式)。在 dsh 中,一个运行中的实例可以被理解为一棵插件树,由启动时按序叠加的各层(profile,档位 与 bundle,捆绑包)组装而成。对于使用者而言,这意味着:

你看到的每一个内置能力,本质上都是一个可替换、可叠加、可撤销的插件;而你在配置文件里写的每一条覆盖,都是在对这棵插件树的某一层做声明式修改。

如果你希望深入理解插件树、profile 与 bundle 的组合机制,后续章节会给出系统性介绍,并推荐官方 架构文档 与 Cordis 入门教程作为延伸阅读。

1.1.5 核心概念速览

在继续之前,先把本章及全书将反复出现的核心概念集中定义一次,避免后文不断解释:

术语 英文 定义
智能体 agent 在 harness 中运行、由模型驱动的执行单元,能调用工具、委派子任务并维护计划
会话 session 一次持续对话的完整记录,以仅追加的事件日志形式存储,可回放、可审计
轮次 turn 会话中的一个往返:用户(或自动机制)给出输入,智能体产生输出
步骤 step 轮次内的最小动作单位,如一次工具调用或一段模型输出
事件 event 会话日志中的单条记录,描述发生的某件事实(消息、工具调用、结果等)
工具 tool 智能体可调用的能力单元(如读文件、执行命令),统一注册、带把关地执行
插件 plugin 向插件树贡献服务、类型化事件与可逆副作用的扩展单元;dsh 的一切皆插件
提供方 provider 提供模型服务的来源(如 DeepSeek、Anthropic、OpenAI 或自建网关)
模型 model 具体可调用的大语言模型实例,隶属于某个提供方
档位 profile 存放在 Harness home 中的具名插件组装,列出自己叠放的捆绑包与用户补丁
捆绑包 bundle Cordis 配置项及其挂载代码的分发格式,是插件树中可叠加的一层
能力接缝 seam 插件之间约定的扩展/替换点,使一层可以在不修改另一层的前提下接管某项能力
工作区 workspace 智能体被授权操作的文件系统目录,选中前会话输入框不可用

1.2 产品阶段:开发者预览

注意: DeepSeek Harness 目前处于 开发者预览 (developer preview)阶段,正在快速迭代。未来将出现破坏兼容性的变更(breaking changes)。

作为读者,这意味着:

  1. 接口可能不稳定:命令行参数、配置文件字段、插件挂载点均可能在版本之间变化,文档可能暂时滞后于代码。
  2. 请固定你依赖的版本:如果你的工作流依赖某个版本的行为,建议记录并在升级前阅读变更说明。
  3. 欢迎反馈 :预览期的价值在于共建。遇到 bug 或有改进想法,请通过 GitHub Discussions 反馈(详见 1.6 节)。

1.2.1 预览期使用建议

  • 生产使用请谨慎:预览阶段不承诺稳定性,若你的业务对中断敏感,建议先在隔离环境中评估。
  • 备份重要数据$DSH_HOME 下存放着你的凭据与设置,建议在重要改动前保留备份。
  • 跟随仓库 README 与 CHANGELOG:官方文档(尤其是仓库 README 与各插件的 README)是行为的最终依据;本指南在事实与仓库文档冲突时,以仓库文档为准。
  • 报告问题时提供版本 :提交 bug 时附上 dsh 的版本号、操作系统与可复现步骤,能显著加快定位速度。

1.3 安装与运行

dsh 提供两条运行路径,按你的目的二选一。两条路径的对照如下:

维度 npmnpx)方式 源码方式
前置要求 已安装 Node.js Node.js + pnpm
获取方式 npx 自动拉取发行包 git clone 官方仓库
是否需要构建 是(pnpm run build
适用场景 日常使用、快速体验 开发、调试、贡献、深度定制
升级方式 npx 自动获取新版 手动 git pull + 重新构建

1.3.1 通过 npm 运行(推荐)

先安装 Node.js,然后在任意目录执行:

sh 复制代码
npx @deepseek-ai/dsh web

该命令会完成「获取发行包并启动 Web UI」的全部过程,无需手工安装。关于它的行为细节:

  • 默认在 http://127.0.0.1:3080 启动 Web UI;
  • 在本机(非 SSH)启动时,会尝试用默认浏览器自动打开页面;
  • 通过 SSH 远程启动时,命令只会打印宿主机 URL ------因为本地端口转发地址由你的 SSH 客户端或编辑器持有,dsh 无法替你打开;
  • 传入 --no-open 参数可仅启动服务器而不打开浏览器,例如:
sh 复制代码
npx @deepseek-ai/dsh web --no-open

1.3.2 从源码运行

如果你需要阅读源码、调试插件或参与贡献,从仓库源码运行:

sh 复制代码
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web

两条命令的分工值得明确:

  • pnpm run build 负责准备仓库产物(构建各包);
  • pnpm dsh web 直接复用这些已构建产物启动,不会重新构建

因此修改源码后,通常需要重新执行 pnpm run build 再启动。

1.3.3 仓库结构速览

clone 下来后,仓库的顶层布局有助于你定位后续开发章节会涉及的内容。几个值得记住的位置:

路径 内容
README.md / README.zh.md 项目入口说明(含本指南 1.3 节引用的全部运行命令)
CONTRIBUTING.md / CONTRIBUTING.zh.md 贡献指南
LICENSE MIT 许可证
THIRD_PARTY_NOTICES.md 第三方依赖及其许可证
AGENTS.md 面向 agent 的开发约定
packages/ 各插件包(核心包、模型适配器、工具、持久化等),dsh-base 等捆绑包也位于此
apps/ 应用入口(如 Web UI 的 apps/web 与 CLI)
docs/ 文档,含 development.zh.md(开发指南)、architecture.zh.md(架构)、user/guide/(用户指南)等

提示:本指南后续章节会频繁引用 packages/docs/ 下的具体文件。现在先建立「文档与源码同仓、中文文档以 .zh.md 后缀命名」这一印象即可。

1.4 启动 Web UI

无论走哪条路径,入口都是 dsh web 子命令(npx 方式写作 npx @deepseek-ai/dsh web)。启动成功后,终端会打印访问地址,默认即为 http://127.0.0.1:3080

有几个初始状态概念需要先建立:

  • $DSH_HOME :Harness 的主目录,存放 settings.yaml(设置)、.credentials.yaml(凭据)等文件。
  • 默认文件系统位置dsh 进程会把启动时所在的目录 作为默认文件系统位置。换言之,你在哪个目录下执行 dsh web,工作区选择时就会默认关联到哪个目录。
  • 全新的 Web UI 不会自动选中任何工作区------你需要手动添加并选中一个(见 1.5.2 节)。选中工作区之前,会话输入框是不可用的。

1.4.1 关于访问地址的常见疑问

场景 行为 说明
本机启动 打印 http://127.0.0.1:3080 并尝试用默认浏览器打开 最典型的使用方式
本机启动 + --no-open 只打印地址,不打开浏览器 适合你已在本机浏览器中打开、或希望控制打开时机的场景
通过 SSH 远程启动 只打印宿主机 URL 本地端口转发地址由你的 SSH 客户端或编辑器持有,dsh 无法替你打开浏览器;你需要自行完成端口转发
端口被占用 启动可能失败 请释放 3080 端口,或通过启动参数指定其他端口 待核实

提示:如果你通过 VS Code 的 Remote-SSH 或类似远程开发环境使用 dsh,编辑器的端口转发机制会自动把远程的 3080 端口映射到本地,此时在本地浏览器打开 http://127.0.0.1:3080 即可。

1.5 快速开始:四步完成第一个任务

假设 Web UI 已经启动。以下四步构成一次完整的最小运行闭环:配置模型 → 选择工作区 → 运行任务 → 理解审批。

1.5.1 第一步:配置模型

打开 Web UI 的 设置 → 模型 页面:

  1. 在 DeepSeek 卡片中输入你的 DeepSeek API 密钥
  2. 点击保存。

两点值得注意:

  • 无需重启:模型路由配置保存后立即可用,服务器不重启。
  • 密钥只写 :密钥保存后,页面只会收到脱敏描述符,永远不会回传明文;密钥实际存储在 $DSH_HOME/.credentials.yaml 中,设置文件(settings)只保留它的凭据引用。

除 DeepSeek 之外,你还可以添加 Anthropic、OpenAI 等目录提供方,或接入公司网关、自建服务器等自定义提供方 (OpenAI 兼容端点)。完整的提供方配置、自定义端点与常见排错(如 MISSING_CREDENTIALUNKNOWN_MODEL、网关拒绝请求等)详见官方《配置模型》指南,本指南后续章节也会给出深入讲解。

1.5.2 第二步:选择工作区

点击 Web UI 中的 选择工作区

  1. 添加你启动 dsh 时所在的项目目录(或任意你希望智能体操作的目录);
  2. 选中该工作区。

再次强调:选中工作区之前,会话输入框不可用------这是预期行为,不是故障。

工作区是智能体能力的边界:它决定了智能体可以读写哪些文件、在哪个目录里运行命令。如果你需要智能体同时操作多个目录,可以添加多个工作区 待核实;日常使用一个工作区即可。

1.5.3 第三步:运行第一个任务

启动一个会话,发送这样一条指令:

Summarize this repository and identify its main packages.

(中文大意:总结这个仓库,并识别其主要包。)

接下来你会观察到智能体的典型工作方式:它可以读取和编辑工作区文件、运行命令、委派工作(如派生子智能体)并维护计划(plan) ,在多轮中逐步完成任务。这就是 dsh 的核心循环------模型在框架提供的工具与权限边界内自主行动,Web UI 则把每一步呈现给你。

1.5.4 第四步:理解权限与审批

dsh 内置权限策略(permission policy)。当智能体想执行某项操作、而该操作按当前权限策略需要审批 时,Web UI 会先向你发起询问,由你决定是否放行。这构成了预览阶段使用 dsh 的基本安全模型:能力是开放的,但敏感操作有人工闸门。

理解审批机制,有三个要点:

  1. 审批由权限策略驱动:不是所有操作都要审批。策略会区分哪些操作可以自动执行(如读取文件)、哪些必须先经人工批准(如修改系统配置、删除文件等,具体边界以策略为准 待核实)。
  2. 审批是同步的:在 Web UI 中,当审批请求出现时,智能体会暂停等待你的决定,不会绕过闸门继续执行。
  3. 审批结果可追溯:每一次审批决定都会记录在会话日志中,便于事后审计。

安全提示:审批闸门是人机协作的最后一道防线,但并非银弹。在不受信任的目录中运行智能体时,建议保持谨慎,不要对高风险操作盲目批准。

1.6 社区与支持

  • GitHub Discussions :欢迎通过 GitHub Discussions 提交反馈或 bug 报告。这是预览期最直接的沟通渠道。
  • 插件生态 :如果你在开发插件,可以为你的插件仓库添加 dsh-plugin 话题(topic),便于其他用户在 GitHub 上发现它。
  • 企业微信群:欢迎加入 DeepSeek Harness 企微群------扫码添加企微小助手并填写入群问卷,完成后小助手会邀请你入群。
  • 贡献代码 :参与贡献请阅读仓库中的 CONTRIBUTING.zh.md;深入开发请先阅读官方开发指南架构文档

1.7 许可证

DeepSeek Harness 采用 MIT 许可证发布,源代码见仓库 LICENSE 文件。第三方依赖及其各自的许可证见仓库中的 THIRD_PARTY_NOTICES.md

MIT 许可证意味着你可以在保留版权声明的前提下,自由地使用、复制、修改、合并、发布该软件的副本;同时该库按「现状」提供,作者不提供任何明示或暗示的保证,也不承担任何责任。

1.8 本章小结与下一步

本章建立了使用 DeepSeek Harness 的四个基础认知:

  1. 它是什么:DeepSeek AI 开源的智能体框架,一切皆插件,由 Cordis 驱动;
  2. 它处于什么阶段:开发者预览,会有破坏性变更,请固定版本并积极反馈;
  3. 怎么跑起来npx @deepseek-ai/dsh web 一键体验,或 clone + pnpm install + pnpm run build 从源码运行;
  4. 第一个任务怎么走:配置模型(设置 → 模型,填 API 密钥)→ 选择工作区 → 发送指令运行任务 → 理解审批闸门。

下一步建议按以下顺序推进:

  • 深入模型配置:了解目录提供方、自定义提供方(OpenAI 兼容端点)与常见错误排错;
  • 理解插件与 profile :认识 dsh 的插件树、profile 与 bundle 的组合机制,以及 dsh --profile web --dump-config 这类自省命令;
  • 进入开发:阅读官方开发指南与架构文档,尝试编写你的第一个插件。

1.8.1 常见问题(FAQ)

Q:npx @deepseek-ai/dsh web 提示找不到包或下载失败?

A:请确认本机已安装 Node.js(建议较新的 LTS 版本),并检查网络是否能访问 npm 官方源。如使用国内网络,可考虑配置 npm 镜像源后重试。

Q:启动后浏览器没有自动打开,我该怎么办?

A:手动在浏览器中打开 http://127.0.0.1:3080。如果你是通过 SSH 远程启动,需要先完成端口转发,再在本地浏览器打开该地址。

Q:为什么会话输入框是灰的、不能输入?

A:你还没有选中工作区。点击「选择工作区」,添加一个项目目录并选中它,输入框即可使用。

Q:配置模型后需要重启服务器吗?

A:不需要。模型路由配置保存后在下一次请求时生效,服务器无需重启。

Q:$DSH_HOME 默认在哪里?

A:$DSH_HOME 是 Harness 的主目录,存放 settings.yaml.credentials.yaml 等文件,其默认位置以项目文档为准 待核实

Q:我可以同时运行多个 dsh 实例吗?

A:可以,每个实例使用独立的端口(默认 3080,多实例时请注意避免端口冲突 待核实)。

Q:我修改了源码,为什么启动后没有生效?

A:源码方式下,pnpm dsh web 直接复用已构建产物,不会重新构建。修改源码后请重新执行 pnpm run build,再启动。

Q:MIT 许可证意味着我可以做什么、不能做什么?

A:你可以自由使用、复制、修改、分发(包括商用),只需保留版权声明。不能向作者主张任何担保或责任。详见 1.7 节。

1.9 参考资料与延伸阅读

本章内容基于以下素材整理(均见 DeepSeek Harness 官方仓库):

  • 仓库 README.zh.md------项目定位、开发者预览声明、运行命令与社区入口;
  • docs/user/guide/index.zh.md------Web UI 使用指南(配置模型、选择工作区、运行任务);
  • docs/user/guide/providers.zh.md------模型提供方配置(DeepSeek、目录提供方、自定义提供方与排错);
  • docs/architecture.zh.md------架构文档(Cordis、profile 与 bundle、核心包);
  • Cordis 入门教程与论文 A Programming Paradigm for Spatiotemporal Composability

再次提醒:本文所有命令与配置均以项目当前版本为准;由于项目处于开发者预览阶段,如与你的实际版本不符,请以仓库 README 与官方指南的最新内容为准。文中以 待核实 标注的条目为作者无法从既有素材中确认的细节,请以仓库实际行为为准。


第二章 核心架构

本章导读

第一章回答的是「怎么把 DeepSeek Harness 跑起来」;本章回答的是「它到底是怎么构成的」。一旦你打算修改 packages/ 下的任何内容、为自己的产品定制 dsh,或者仅仅想理解为什么替换一个配置项就能改变整个产品的行为,你就需要本章的内容。

本章沿着一条主线展开:dsh 是一个全插件架构------不存在特权内核。围绕这一论断,我们将依次讨论:

  1. Cordis 框架------dsh 底层的插件框架,插件如何通过共享上下文、类型化事件与可逆副作用协作;
  2. 档位(profile)与捆绑包(bundle)------一棵插件树在启动时如何分层叠加组合;
  3. 核心包------向 Cordis 树贡献关键服务的七个基础包;
  4. 事件模型------会话事件、agent(智能体)域事件与能力事件的三个领域,以及如何选择扩展点;
  5. 轮次(turn)与步骤(step)流程------一次模型交互在架构中的完整生命周期;
  6. 会话日志与派生态------「模型可见即已记录」这一运行时不变量;
  7. 能力接缝(seam)------可替换能力的三角色模型;
  8. 「新行为放哪里」------一份从目标到机制的决策表。

读完本章,你应当能够在阅读任何 dsh 子系统文档之前,先在自己的头脑中建立起一张完整的架构地图,并知道每一项常见改动应该落在哪个扩展点上。

前置知识 :本章假定你已通读第一章并跑通过一个 dsh 实例。若你对 Cordis 完全陌生,建议同时翻阅仓库中的 docs/cordis-primer.zh.md(Cordis 入门)与 docs/cordis-tutorial/(Cordis 教程),本文只做框架级概述,不重复教程内容。

2.1 全插件架构:没有特权内核

理解 dsh 架构只需先记住一句话:

产品的每一部分都是插件------包括模型适配器、工具注册表、会话日志,乃至智能体循环(agent loop)本身------因此每一部分都可以从配置替换。

通常的智能体框架都假定一个「核心」:模型调用、工具分发、消息管理被硬编码在内核中,外围功能以钩子(hook)形式挂接。dsh 走的是相反的路。它的底层是 Cordis 插件框架(以 vendor 方式引入,源码见 vendor/ 目录),dsh 的全部产品能力都被拆解为 Cordis 插件:

  • 模型提供方(如 DeepSeek 适配器)是插件;
  • 每个工具(bash、文件读取等)是插件;
  • 会话日志与持久化是插件;
  • 智能体循环驱动器是插件(dsh-agent-loop,它是 harness 中唯一包含具体循环逻辑的包);
  • 甚至沙箱、审批策略、设置、凭据、遥测都是插件。

这个设计带来三条直接后果:

  1. 扩展方式是把新插件挂载到已有插件旁边,而不是给内核打补丁。仓库中不存在需要补丁化的特权内核代码路径。
  2. 各项注册都是副作用(effect) 。插件在挂载时通过 ctx.effect()ctx.on() 安装提示词片段、工具 schema、适配器、监听器等;插件卸载(teardown)时这些副作用按预期一一撤销。
  3. 一切可替换性最终落在配置层 。你不需要 fork 代码,只需在配置(profile 的 cordis.patch.yml 等)中把某一行指向另一个实现,行为即改变------这正是 2.7 节「能力接缝」机制的由来。

2.1.1 Cordis 的五个核心概念

在深入 dsh 各层之前,先把 Cordis 框架的五个核心概念过一遍,后文的所有术语都建立在这之上。

概念 说明
插件(plugin) 插件是实现 Service 的对象。它可以是一个带有可选 injectapply(ctx) 字段的函数,也可以是一个 Service 子类,其生命周期由 Cordis 挂载到当前上下文中。
上下文(context) 上下文是服务的容器。一个服务占据一个稳定的 ctx.<key> 位置(如 ctx.toolsctx.llmctx.sessions);其他插件通过 key 查找服务,而非导入具体实现。
依赖注入(inject) 插件通过 inject 声明所需的服务;Cordis 会等待这些服务就绪才启动该插件。加载顺序由此通过服务依赖表达,而不是手动编排启动序列。
类型化事件 服务通过 TypeScript 声明合并注册事件名,然后以四种模式之一分发:emit(观察)、waterfall(瀑布式包装)、parallel(并行扇出)、serial(按序执行)。
可逆副作用 提示词片段、工具 schema、适配器、提供方和监听器通过 ctx.effect()ctx.on() 安装,在 reload 和 teardown 时按预期撤销。每个注册都应有对应的 disposer(资源释放函数)。

四种分发模式是事件的公开约定的一部分,新事件必须以 @mode 标签记录其模式,以便生成的目录能把声明与分发调用点做交叉校验:

模式 是否 await? 分发顺序 是否有返回值?
emit 监听器按注册顺序观察
waterfall 监听器按注册顺序观察
parallel 所有监听器并行观察事件
serial 监听器按注册顺序观察

其中 waterfall(瀑布式事件) 的语义值得多说几句,因为 dsh 的关键拦截点几乎都用它。ctx.waterfall 是环绕中间件:监听器接收 (...args, next);调用 next() 会执行下游监听器,下游返回值通过 next() 返回当前包装层,可被该层包装后继续向外返回;不调用 next() 直接返回则短路 。协作式监听器通常修改一个共享的请求或决策对象,然后委托;策略监听器在拥有决策权时可以不调用 next() 直接返回,而仅做观察的监听器则必须委托。

Cordis 还有一套 Loader 配置机制:@deepseek-ai/cordis-plugin-include 插件将 !!js 标记解析为表达式节点,Loader 在声明的注入激活后基于插件上下文插值条目的 config,并在每次挂载决策时插值其 disabled 字段。这个机制在 2.2 节的 profile 组装中会再次出现(例如按平台门控 shell 栈)。

2.2 档位(profile)与捆绑包(bundle)

2.2.1 运行中的 dsh 是一棵插件树

运行中的 dsh 是一棵插件树 ,由启动时按序叠加的各层组合而成。树的每一个「条目」(entry)指向一个可加载的插件,并携带其 config;多个条目可以引用同一个插件包,以不同的配置或禁用状态出现。

两个一等公民概念支配着这棵树的组成:

  • 档位(profile) :存放在 Harness home($DSH_HOME,默认 ~/.dsh)中的具名组装 。一个 profile 是一个 ~/.dsh/profiles/<name> 目录,包含一个 package.json(声明树外插件依赖与 profile 清单)和用户自己的 cordis.patch.yml。它做三件事:列出自己叠放哪些捆绑包、存放自己安装的树外插件、保存用户自己的 patch 文件。
  • 捆绑包(bundle)Cordis 配置项及其挂载代码的分发格式 。一个捆绑包就是一个 npm 包,它的 patch 文件描述要往树里插入哪些条目;因为是配置项的形式,它插入的内容始终可被其上各层 patch

两者都在各自的 package.json 中通过 dsh 字段声明自己:dsh.profile 列出一个 profile 的捆绑包列表(dsh.profile.bundles),dsh.bundle 指向一个捆绑包的 patch 文件(如 "dsh": { "bundle": { "patch": "./cordis.patch.yml" } })。profile 组合器通过 manifest 的 dsh.bundle.patch 字段解析 patch,绝不通过代码导入。

2.2.2 随发行版交付的捆绑包分层

发行版自带一个基础捆绑包和两个表层捆绑包,构成典型的三层结构:

捆绑包 定位 主要贡献
dsh-base 每个 profile 的第一层,共享 dsh 核心 模型适配器(DeepSeek 等)、工具、持久化、沙箱与审批策略、设置、凭据、遥测、核心 spawn/fork subagent 提供方。它以一次 insert 在空的 profile 根之上插入全部基础插件行,自身不贡献任何模型可见文本,也没有运行时 API。
dsh-web-app 浏览器应用表层,叠加在 dsh-base 之上 设置 coding persona,插入 Web 宿主行(webserver、API 网关、workspace、投影缓存、存储)、浏览器插件名录与客户端插件重载链(dsh-client-hmr),并挂载 web-runtime 粘合插件(配置项含 openBrowserprintUrlsurfaceContexttrustedHosts)。普通 web-startup 提供方注入 ctx.cmdlineArgs,解析 --host--port、可重复的 --trusted-host--no-open 等参数。
dsh-headless 一次性任务表层,与 web 同级、同样叠加在 dsh-base 之上 提供编码 persona 和工具模式、禁用 HMR(热模块替换)、挂载 headless-runner 插件(配置项为 {task})。不挂载任何 Host、HTTP server、Web runtime 或浏览器插件 ------runner 通过 ctx.agents 创建一个全新持久化 agent,把任务作为普通用户消息提交,等待完全停稳后把最后一条非空 assistant 文本写入 stdout,再经 ctx.appExit 请求退出(最终 turn/end 正常完成 → 退出码 0,否则 1)。

两个值得注意的设计细节:

  1. dsh-base 的 patch 会按平台门控 shell 栈bash-sandbox/tool-bash 行携带 disabled: !!js process.platform === 'win32',其孪生行 pwsh-sandbox/tool-pwsh 以取反的表达式仅在 win32 挂载------同一份 patch 文件,每个宿主恰好挂载一个 shell 栈。
  2. patch 替换整行 config,不做深度合并 。因此某个按模式取值不同的行不会放在 dsh-base 里,而应放在各模式捆绑包中,由模式捆绑包重述完整配置。用户覆盖同理:必须重述该行需要保留的每个字段。

可选的 Codex 与 Claude Code subagent 提供方不属于 dsh-base 及其生产依赖闭包;profile 仅在需要时通过 dsh plugin --profile <name> add ... 安装对应的产品 provider 捆绑包,重启后生效。

2.2.3 叠加顺序与 patch 语义

各层按固定顺序应用在空条目列表之上:

text 复制代码
空条目列表
  → 按 profile 列出的顺序逐个应用每个捆绑包的 patch
  → 应用 profile 的 cordis.patch.yml
  → 应用 home 级的 cordis.patch.yml(~/.dsh/cordis.patch.yml)
  → 应用任意 --patch overlay

后应用者优先级更高(最后写入者按行获胜)。一条 patch 的两种操作:

  • 按 id 定位 某个条目并替换其整个 config
  • insert 插入新条目。

!!js 表达式在挂载时插值,可用于环境选择(如平台门控)。如果 patch 指定的条目 id 不在组合后的树中,会输出一条 stderr 警告。

每次 profile 启动都会持续监视 cordis.patch.yml 的变更(watchUserPatches):变更串行处理,按层次顺序重新组合用户 patch 层(捆绑包层在下、overlay 在上);读取或解析失败时,最后一个可用树继续运行。

2.2.4 查看你机器的实际启动树

要查看你的机器实际启动的配置树,执行:

sh 复制代码
dsh --profile web --dump-config

这条命令打印出 profile 组合器结算后的完整条目树。它有一个重要含义:它打印出的任何条目,都可以由你自己的 patch 替换 。换句话说,--dump-config 的输出就是完整的「可替换面」------发行版交付的每一行都在你的覆盖范围之内。

组装机制的完整文档在 packages/boot/app-boot 的 README(Profile 一节);每个可配置字段的完整参考见 docs/config-catalog.zh.md(插件配置目录,由 pnpm run gen-config-catalog 从源码生成)。

2.3 核心包

以下是向 Cordis 树贡献内容的部分核心包。它们共同构成 dsh 的「骨架」------注意,即便这个骨架本身也是插件,只是恰好随发行版交付。

职责 ctx
core/session 仅追加的 SessionEvent 日志和内存存储 ctx.sessions
core/system-prompt 提示词片段与工具 schema 的组装 ctx.systemPrompt
core/tools 作用域化的工具注册表和带把关的执行流水线 ctx.tools
core/agent Agent 接口、活跃 agent 注册表和 agent/* 事件 ctx.agents
core/agent-loop 实现该接口的默认驱动器 ctx.agentLoop
core/scope 按 agent 划分作用域的注册原语 库,无 ctx 键
llm/llm 消息与流式词汇表,以及适配器 seam ctx.llm

2.3.1 core/session:事件溯源的会话日志

dsh-sessionSession 类与 SessionStore 服务所在的包。Session 是 agent 全部交互历史的仅追加真源 ,采用事件溯源(event sourcing):LLM 消息历史由日志派生 而非独立维护。原始日志之上维护一个 surface 层(产生消息事件的有序投影),用于高效派生和压缩(compaction)。

关键 API 包括:

  • ctx.sessions.create(id?, { seed?, meta? }?):创建并发布会话;
  • ctx.sessions.flush(session):通过会话捕获的作用域分发一个需等待完成的并行持久性检查点;
  • ctx.sessions.fork(source, boundary?, childSessionId?):fork 会话------选取截至 boundary 事件序号(含该事件,默认为当前最后一个事件)的前缀,要求所选前缀结束时没有开放轮次,再创建带谱系元数据的子会话;
  • session.append(type, data, opts?):追加持久事件(快照、冻结、校验、同步提交);
  • session.deriveMessages():对每个新的 surface 条目做增量投影,返回完整、带标识且冻结的消息数组。

这个包有意不实现持久化 :持久化是另一个 seam(ctx.sessionPersistence),插件订阅 session/event、在 session/flush 时刷新即可。这个拆分让「日志形态」与「存储介质」彻底解耦------JSONL 与 SQLite 后端是平替的。

2.3.2 core/system-prompt:提示词组装注册表

dsh-system-prompt 是一个组装注册表 :插件可以向它贡献有序段(section)、工具 schema 和具名变量(variable);循环在每个步骤组装一次,把结果渲染为完整的模型提示词。该包拥有静态 harness 身份和全局部署 persona;agent 作用域的 persona 会遮蔽全局默认值。

注册接口按调用上下文的作用域分层:

  • ctx.systemPrompt.section(section):贡献一个段;complete: true 的段在组装 waterfall 之后成为精确的完整提示词(多个 complete 段并存会被拒绝);
  • ctx.systemPrompt.context(context):贡献有序动态上下文,每次符合条件的组装都会求值;
  • ctx.systemPrompt.tools(provider):贡献工具 schema,每次组装用该次组装的上下文求值;
  • ctx.systemPrompt.variable(name, provider):贡献 {``{name}} 形式的提示词变量。

配置层面有两个关键项:persona(全局部署 persona 模板,渲染为顺序 0 的 deployment:persona 段,支持 {``{...}} 变量插值)与 toolOrder(显式指定面向模型的工具顺序列表,必须恰好包含一个 '<unlisted-tools>' 其余项标记)。工具顺序之所以采用中心列表而非每插件权重,是为了让「模型看到的工具列表」成为一个可审计、可 patch 的单点。

组装本身经过一次 system-prompt/assemble waterfall:监听器可以改写段与工具列表,其返回值具有权威性。

2.3.3 core/tools:工具注册表与执行流水线

dsh-tools 是作用域化的工具注册表和带把关的执行流水线所在。工具插件注册各自的 schema 和执行器;agent loop 依次让每次工具调用经过:

text 复制代码
tools/pre-execute  (可扩展的允许/拒绝门禁)
  → 已注册的单调守卫(guard)
  → tools/execute   (供超时/重试/指标插件使用的环绕分发包装层)
  → tools/post-execute (检查/替换结果、附加上下文)
  → 由工具定义持有的 finalizeContent 边界
  → 仅观测的 tools/result 通知

流水线有两个关键性质值得强调:

  • 门禁与守卫的单调性tools/pre-execute 之后注册的执行守卫,返回理由即拒绝调用、返回 undefined 则保持原决定;后续 waterfall 监听器无法将守卫的拒绝重新变为允许
  • 呈现模式(mode) :注册表决定工具以何种方式向模型呈现------native(原生 Function Calling,默认)、code(Code Mode,提供保留的 run_code 传输与生成的 tools:sdk 段)或 both。单个 agent 可用 ctx.tools.presentAs(mode) 为自己遮蔽全局默认值。Code Mode 下模型直接调用其他任何工具会在策略运行前被解析为 UNKNOWN_TOOL

注册作用域同样由调用上下文决定:普通插件上下文全局注册;agent.ctx 注册的只对那个 agent 生效并遮蔽同名全局工具;ctx.tools.restrict(filter) 提供 agent 作用域的允许/拒绝掩码(注意:这是实时可见性组合,不是权限边界)。

2.3.4 core/agent:接口与注册表

dsh-agent 定义 Agent 接口、活跃 agent 注册表(ctx.agents)与 agent/* 实时事件词汇。它的设计目标是:每个插件(UI、钩子、编排器)都面向 Agent handle 编程,而不依赖具体循环包------因此循环本身可以替换。

注册表的核心能力:

  • ctx.agents.register(agent) / get(id) / list() / roots():实时 agent 的增查列举;
  • ctx.agents.currentInitiator() / withInitiator(agent, operation)发起方 agent 作用域 ------AgentLoop 在发起方边界内运行每个驱动器的完整生命周期,并发驱动器彼此隔离,子驱动器的 continuation 携带子 agent;
  • ctx.agents.setFactory(factory):注册创建工厂。dsh-agent-loop 通过它把自己注册为工厂,于是消费方只需面向 ctx.agents.create() / ctx.agents.resume() 编程,完全不需要导入循环包。

Agent.ctx 是 agent 的作用域上下文(由 dsh-scope 提供,键 = 该 agent)。通过它注册的工具/段/变量/监听器只对该 agent 生效,并在 agent dispose 时全部撤销------这是「让某个会话拥有不同能力集合」的基础机制。

2.3.5 core/agent-loop:唯一的循环实现

dsh-agent-loop 是 agent 的唯一具体实现插件和循环驱动器 ,其包内部实现满足 Agent 接口,并驱动会话、轮次和步骤的生命周期。这是 harness 中唯一包含具体循环逻辑的包,文档里有一句明确的原则:

其他所有内容要么是抽象服务,要么是针对扩展点的插件:新行为应放入插件,而不是这里

创建与恢复属于同一个受回滚保护的事务 :构造私有会话、具体 agent 和带作用域的上下文;等待可选 setup;进入会话与 agent 两个注册表;依次宣告 session/createdagent/created;发出 agent/session-start;此后才启动驱动器。任何一步失败,失败方都会回滚其私有会话/作用域/驱动器。

所有权模型是「调用方 fiber 与 AgentLoop 提供方共同拥有 agent」:调用方卸载、handle 的 dispose() 或提供方卸载都会汇合到同一个记忆化的完全停稳边界(停止循环 → 等待退出 → 注销 agent → 从存储移除会话 → 撤销作用域)。

2.3.6 core/scope:作用域注册原语

dsh-scope 是支撑上述「按 agent 划分作用域」的底层库,没有 ctx 键。核心 API:

  • createScope(ctx, key):创建一个带标签的 Cordis 上下文,其底层 fiber 拥有通过该上下文进行的每项注册;
  • 键可以构成可选的父链(bindScopeParent),形成两条方向相反的路由规则:
    • 注册视图沿链向下继承------子作用域看得见祖先各层,近者遮蔽远者;
    • 事件放行沿链向上扩展------标签为祖先的监听器能收到子孙键的事件,反向永不成立;
  • scopeTarget(base, key):为按作用域筛选的事件构造不透明分发载体,让无作用域监听器保持全局可见,标签匹配的监听器才收到事件。

agent loop 为每个存活的 agent 创建一个作用域;agent preset 的常驻挂载则是其各 agent 的父作用域。该机制与键的具体含义无关,底层包无需依赖两者即可使用。

2.3.7 llm/llm:模型词汇与适配器 seam

dsh-llm 是提供方无关的 LLM 词汇与抽象服务:本包定义 agent loop、会话日志和所有插件共同使用的规范词汇(消息、流式 chunk 等)。

LlmRuntime 服务(ctx.llm)是「适配器注册表 + 单一流式调用接口」,可通过 waterfall 拦截:

  • ctx.llm.registerAdapter(providers, adapter):为给定的提供方路由注册适配器;注册要么全部成功要么全部不生效;句柄的 replace(providers) 在一次同步操作中替换路由,不出现可观察的空档;
  • ctx.llm.registerConfigurableProviders(entries):声明可通过配置激活 的提供方路由(休眠态)------这正是 Web UI 的 Models 页面背后的机制:settings 文档中的 llm-pi-ai: 分节一旦提供 provider profile,对应路由即刻注册为存活,分节清空则回落为休眠;
  • ctx.llm.discoverModels(settingsNs, request) / listModels(provider):询问端点公布的模型;
  • ctx.llm.providerRetryPolicy(provider):返回注册时捕获的重试策略。

一个重要的职责划分:dsh-llm 存储有效策略,但不执行重试 ------重试是 dsh-llm-retry 插件的职责。省略提供方配置时使用有界 normal 模式,在首次请求后最多重试五次。

2.4 事件模型:三个领域

事件就是扩展点,而选对事件域是大多数改动的第一个决定。dsh 的事件分属三个领域:

2.4.1 会话事件(持久事实)

会话事件是追加到会话日志并通过 session/event 广播的持久事实turn/*step/*user/messageassistant/*tool/* 都属此类。它们的共同特征是:当某个事实必须在重新加载后仍然存在时,就使用会话事件。会话事件是 fork、恢复、transcript(文本记录)、遥测和持久化的共同来源------2.6 节会展开讲它为什么是模型的上下文来源。

2.4.2 agent 域事件(agent/*,实时控制)

agent 事件携带活跃 Agent,覆盖 inbox、步骤、状态、请求、验证与续跑:agent/createdagent/inbox/*agent/pre-stepagent/requestagent/request-erroragent/turn-stopping 等。它们不落入会话日志 ,是进程内实时扩展点。要观察或拦截进行中的工作时,使用它------拦截器通常修改一个共享的请求或决策对象再委托(waterfall 语义),或作为 terminal checkpoint 直接终止流程(serial 语义)。

2.4.3 能力事件(无需导入循环)

能力事件无需导入循环即可向某个 seam 附加策略和适配器,典型如 fs/*tools/*telemetry/*。例如给文件系统加一个审计策略,只需监听 fs/* 事件,不必触碰 agent loop 的任何代码。

三个领域的选择可以浓缩成一句话:

需要跨重启存活 → 会话事件;需要拦截进行中的轮次/步骤 → agent 域事件;需要给某个能力面附加策略或实现 → 能力事件。

docs/event-producer-consumer.zh.md(事件映射)列出了每个事件的生产方与消费方,是排查「谁在发、谁在听」的索引。

2.5 轮次(turn)与步骤(step)流程

2.5.1 两个基本单位

  • 一个步骤(step) :一次模型请求加上它调用的工具
  • 一个轮次(turn) :包含零个或多个步骤;它在领取首条输入之前打开,并在不再欠下任何工作时关闭。

「零个步骤」是合法状态:当领取的首条输入被 agent/pre-step 拒绝、或被改写为空时,轮次会关闭而不含任何步骤------但日志仍会记录这次尝试(一个不含步骤的持久轮次),因此「模型收到了什么」永远是可审计的。

2.5.2 完整流程

text 复制代码
turn/start
  claim next-step input plus one queued message
  assemble prompt sections + tool schemas
  -> agent/pre-step                   reject | enter(messages)
     reject, or a first enter rewritten empty -> close the turn with no step
     step/start
     append entered messages as user/message
     derive model history from the log
     agent/request -> llm/stream -> assistant/chunk* -> assistant/message
     tool/call* -> tools/pre-execute -> tools/execute -> tools/post-execute -> tool/result*
     step/end
     tools owe another request, or next-step input arrived -> claim -> next step
  -> agent/turn-stopping
turn/end

逐段解读:

  1. turn/start:轮次打开。驱动器从 inbox 领取「下一步骤输入 + 一条排队消息」。
  2. 组装 :从插件注册的提示词片段与工具 schema 组装提示词(经 system-prompt/assemble waterfall)。
  3. agent/pre-step (waterfall):决定模型看到什么。监听器可以改写已领取的消息,也可以直接拒绝它们;首个领取被拒绝或改写为空时,关闭一个不含步骤的轮次。
  4. step/startuser/message :被放行的消息作为 user/message 会话事件追加进日志;模型历史从日志派生。
  5. 模型请求agent/request(waterfall)→ llm/stream(waterfall)→ 流式 assistant/chunk* 事件 → 收尾的 assistant/message 事件。assistant/message 会记录每次成功的提供方调用,包括返回空内容或以 max-tokens 结束的调用;空内容不进入派生历史,但持久事件仍保留用量与 sourceEventSeqs 指向。
  6. 工具执行 :每个 tool/call 依次经过 tools/pre-executetools/executetools/post-executetool/result
  7. step/end:若工具要求再来一次模型请求(如工具结果需要模型消化),或有新的下一步骤输入到达,则再次领取、进入下一个步骤。
  8. agent/turn-stopping (serial,terminal checkpoint):在本可完成的轮次关闭前运行;它没有 next(),监听器直接返回决策。
  9. turn/end:轮次关闭,驱动器回到等待。

2.5.3 事件域与分发模式的分布

把流程图中的事件按域和分发模式分类:

事件 分发模式
turn/startturn/end 会话(持久) session/event 广播
step/startstep/end 会话(持久) session/event 广播
user/messageassistant/chunk*assistant/message 会话(持久) session/event 广播
tool/call*tool/result* 会话(持久) session/event 广播
agent/pre-step agent 域(实时) waterfall (监听器须调用 next() 委托)
agent/request agent 域(实时) waterfall
agent/request-error agent 域(实时) waterfall(恢复决策:返回 { kind: 'retry' } 且不调 next()
agent/turn-stopping agent 域(实时) serial (无 next()
llm/stream 能力域(实时) waterfall
tools/pre-execute / tools/execute / tools/post-execute 能力域(实时) waterfall

一个易错点:waterfall 的监听器必须调用 next() 才能把请求委托下去;忘记调用等价于短路整个下游。serial 事件则相反,它本来就是终点检查点,没有委托概念。

2.5.4 输入通道:inbox

输入通过同一个 inbox 到达驱动器。有些消息会立即唤醒它;注入的上下文 (如 agent.inject() 提交的内容)会留在 inbox 中,直到另一条消息将驱动器唤醒------即注入的上下文总是落到下一次获准的请求中。

2.5.5 错误恢复与压缩

模型请求失败时走 agent/request-error(waterfall):拥有恢复权的监听器返回重试动作,否则保留原始错误。dsh 的上下文压缩(compaction,dsh-compaction-basic)则通过 agent/pre-step 处理上下文压力:任一触发条件满足后,先执行可选的工具结果剪枝,再选择摘要;恢复发生在失败步骤结束之后、失败轮次结束之前。

更完整的时序图见 docs/agent-lifecycle.zh.md,工具流水线细节见 docs/tool-execution-pipeline.zh.md,取消与错误恢复见 docs/subsystems/core.zh.md(the agent handle 一节)。

2.6 会话日志与派生态

2.6.1 会话日志是模型所见上下文的来源

会话日志(2.3.1 节的仅追加 SessionEvent 日志)是模型所见上下文的唯一来源deriveMessages() 从中投影出模型历史;原始 assistant/chunk 事件则保证回放和 UI 保真(逐 token 的流式重现)。fork、恢复、transcript、遥测和持久化全部派生自该事件流------不存在第二条「真实的历史」。

2.6.2 模型可见即已记录

dsh 有一条核心运行时不变量:

模型可见即已记录。 抵达模型请求的一切都必须能从日志重建,并由一项运行时不变量断言这一点。

这条不变量的工程含义非常直接:新增一项模型可见输入,就需要新增一个会话事件 ------具体操作是扩展 SessionEventMap(会话事件的类型联合),并从日志渲染该事件。提示词组装的动态上下文(ctx.systemPrompt.context 的贡献)、工具可见性变化、persona 切换......凡是模型在请求中「看到」的东西,都必须以某种事件形式留在日志里。

这一设计带来的收益:

  1. 可回放:任何一次模型请求的输入都能从日志精确重建,调试与审计不再依赖进程内状态;
  2. fork 有边界ctx.sessions.fork(source, boundary) 的切点就是日志中的一个事件序号,且要求该前缀没有开放轮次;
  3. UI 与模型永远一致:Web GUI 从同一条事件流渲染,用户看到的和模型看到的天然同源。

2.7 能力接缝(seam)

2.7.1 三角色模型

一个 seam(能力接缝) 是一项可替换能力,包含三种角色:

角色 职责 例子
Service Definition(服务定义) 声明接口:键、方法签名、配置 ctx.llm 的适配器注册表接口
Service Provider(服务提供方) 实现该接口 dsh-llm-deepseekdsh-llm-pi-ai、远程沙箱提供方
Consumer(消费方) 使用该接口,通常是面向模型的工具 agent-loop 消费 ctx.llm 发起请求

两个容易误解的点:

  • 一个包可以合并承担多个角色 ,但单一角色本身不是 seam------只有接口、实现、使用三者齐备,才构成一项可替换能力;
  • 添加一项能力意味着把三者一并设计。先定义接口(Definition),再决定默认实现(Provider),最后明确谁来消费(Consumer),缺一不可。

docs/capability-seams.zh.md(能力图)由脚本从源码生成,展示了拥有服务声明的包、已知实现包与直接消费该服务的包之间的关系图。

2.7.2 为什么 seam 能改变整个产品

seam 正是「替换一个提供方就能改变整个产品」的原因。一个典型例子是执行世界 :文件系统与进程提供方共享同一个执行世界,因此把 ctx.fs / ctx.subprocess 指向远程沙箱,就把 Bash、PTY 和 LSP 一并搬了过去,无需为提供方做专用 fork------因为消费方(工具)只面向接口编程,它们不知道也不关心执行发生在本地还是远端。

subagent 提供方 是另一个例子。ctx.subagents 之后千差万别的实现可以共存:

提供方 行为
subagent-spawn-in-process 启动全新的进程内子 agent
subagent-fork-in-process 从父 agent 已完成的历史记录启动进程内子 agent
subagent-acp 通过 ACP(Agent Client Protocol)启动进程外子 agent
subagent-codex 启动真实的 Codex app-server 子 agent
subagent-claude-code 通过官方 Claude Agent SDK 启动真实的 Claude Code 子 agent
subagent-dsh-sdk 通过 TypeScript SDK 启动进程外 Harness 子 agent

它们都注册到同一个 ctx.subagents 键,对 tool-subagent 工具而言完全同构。实验性的 Agent Teams 则是在 ctx.agentTeams 上的私有显式启用协作 seam,在可继续 subagent 之上提供持久 roster、任务板和 mailbox。

2.8 「新行为放哪里」决策表

当你想给 dsh 增加新行为时,第一问永远是「它该落在哪个扩展点」。下表把常见目标映射到机制(改动循环本身时,本表随文档更新):

目标 机制
添加模型提供方 ctx.llm 上注册其适配器
添加面向模型的能力 ctx.tools 上注册;其 schema 加入提示词组装
让某个会话拥有不同的能力集合 组装一个 agent preset;其中的服务行需要 isolate realm
添加 shell 执行 注册 ctx.shell 后端;本地后端通过 ctx.subprocess spawn 进程
添加持久化终端执行 注册 ctx.terminals 后端和 dsh-tool-terminal
添加用户命令 ctx.commands 上注册;它无需模型轮次即可分派
添加后台工作 ctx.jobs 上注册;job_* 工具负责收集或停止
添加文件系统访问或策略 注册 ctx.fs 提供方,或监听 fs/* 事件
限制所启动的进程 使用 ctx.sandbox 后端;消费方在启动进程前包装 argv
拦截请求、工具或轮次 使用相应的 agent/*tools/* 事件;agent/turn-stopping 会停止轮次
添加模型可见上下文 调用 agent.inject();它会落到下一次获准的请求中
添加 UI 或编辑器集成 驱动 ctx.agents 并从 session/event 渲染
添加 Web Client Chat 节点 注册 ConversationNodeDefinition + keyed renderer
添加持久会话状态 扩展 SessionEventMap;从日志渲染和回放
生成会话标题 注册唯一的 ctx.sessionTitle 提供方
管理同会话目标 使用 ctx.goals;通过 agent/* 续跑
fork 活跃会话 ctx.sessions.fork(source, boundary?, childSessionId?)
将注册项限定到单个 agent 使用该 agent 的 agent.ctx

使用这张表的几个实践提示:

  1. 区分「注册」与「监听」。直接能力调用(注册提供方/工具/命令)用服务方法;拦截和策略(改写、审批、审计)优先用事件。
  2. 注意作用域 。同一注册接口在普通上下文与 agent.ctx 下语义不同:后者只影响单个 agent 并遮蔽同名全局注册。
  3. 每个注册都要想好 disposer。插件卸载时副作用必须能干净撤销;如果 teardown 顺序有要求,把相关工作放在同一个 effect 中。
  4. 模型可见的东西必须过日志 。如果你新增的机制会让模型看到新内容,先扩展 SessionEventMap(2.6.2 节的不变量)。

分步实操(添加一个包、一个工具、一个 LLM 适配器、一个 Chat 节点、一张设置卡片)见 docs/cookbook/extension-cookbook.zh.md(扩展实操手册),它把功能映射到能力并索引各分步指南。

2.9 本章小结

本章的核心论断只有一句:dsh 没有特权内核,一切能力都是挂载在 Cordis 上下文上的插件,一切替换都发生在配置层。围绕它,本章给出了五张地图:

地图 回答的问题
档位/捆绑包叠加顺序(2.2.3) 我机器上实际跑的是哪棵树?我能在哪一层覆盖?
核心包表(2.3) 关键服务由哪个包提供、占据哪个 ctx 键?
三事件域(2.4) 我的改动该用哪种事件?
turn/step 流程(2.5) 一次模型交互中,哪些事件是持久的、哪些是实时的、哪些能短路?
新行为决策表(2.8) 从目标到扩展点,一步定位。

下一章将沿着这些接缝深入具体子系统------工具流水线、模型适配器、会话持久化与压缩------演示如何在这张地图上动手。

附录 A:本章术语速查

英文 中文 备注
agent 智能体 本文统一用「智能体」
session 会话 日志与持久化的单位
plugin 插件 Cordis Service 的实现
tool 工具 面向模型的可调用能力
turn 轮次 零个或多个步骤
step 步骤 一次模型请求及其工具调用
event 事件 分持久会话事件与实时事件
provider 提供方 seam 的实现角色
model 模型 LLM 实例
profile 档位 Harness home 中的具名组装
bundle 捆绑包 配置项 + 挂载代码的分发格式
seam 接缝(能力接缝) 可替换能力的三角色结构
LLM 大语言模型 首次出现标注全称
waterfall 瀑布式事件 监听器须调用 next() 委托
effect 副作用 可逆的注册,随插件卸载撤销
disposer 资源释放函数 effect 的撤销端
fork 分叉 从事件边界派生子会话
compaction 压缩 上下文压力下的摘要/剪枝

附录 B:相关文档索引

主题 文档
Cordis 框架入门 docs/cordis-primer.zh.md
Cordis 教程 docs/cordis-tutorial/
配置字段目录 docs/config-catalog.zh.md
事件生产者-消费者映射 docs/event-producer-consumer.zh.md
turn/step 时序图 docs/agent-lifecycle.zh.md
工具执行流水线 docs/tool-execution-pipeline.zh.md
能力 seam 图 docs/capability-seams.zh.md
子系统参考 docs/subsystems/(core / session / tools / llm-streaming / subagent 等)
扩展实操手册 docs/cookbook/extension-cookbook.zh.md
组装机制(app-boot) packages/boot/app-boot/README.zh.md(Profiles 一节)
相关推荐
海兰2 小时前
【插件】OpenClaw 上下文引擎指南
人工智能·agent·openclaw
云烟成雨TD2 小时前
LlamaIndex 系列【5】智能体开发:大语言模型接入与基础调用
ai·agent·rag·llamaindex
海兰2 小时前
【原理】OpenClaw Agent 运行时回顾一文清
人工智能·agent
小白跃升坊2 小时前
DeepSeek 多模态 API 调用完全指南:从调用到效果,一篇讲透
ai·大语言模型
VIP_CQCRE2 小时前
用 Ace Data Cloud 快速接入 MiniMax H3:把 AI 视频生成能力变成可调用的生产力
ai·aigc·api·视频生成·acedatacloud
新知图书2 小时前
11.4 基于扣子编程的实现过程(AI 数据质检工作流)
人工智能·agent·ai agent·智能体
weixin_471383033 小时前
22 多 Agent 架构
agent
Justin3go3 小时前
DeepSeek Harness 如何做到 99% 缓存命中率(原理详解)
人工智能·开源·agent·deepseek
赵大仁3 小时前
AI 限流 UX 设计:排队、降级、告知与用户预期管理
前端·ai·限流·用户体验·产品设计