第 1 章:总体架构
第一次接触 agent / 大模型?先花几分钟读零基础导读,再回来更顺。
本章回答「dsh 到底是什么、由哪些部分拼成」,建立后续所有章节共同依赖的词汇和心智模型。读完本章,你应该能画出 dsh 的高层结构图,并说出「为什么改一个 provider 就能换掉整个产品的某块能力」。


1.1 一句话定义
DeepSeek Harness(dsh)是一个开源的 agent harness(智能体框架):它负责把「大语言模型调用、工具调用、会话记录、权限、沙箱、人机协作」这些零件组装成一个可运行的 agent,并让每一个零件都可以从配置里替换。
它的架构原则只有一条:一切皆插件(everything is a plugin)。
1.2 一切皆插件
dsh 构建在 Cordis 之上(vendored 源码在 vendor/)。Cordis 是一个小型运行时:插件向一个共享的 Context(上下文) 贡献 服务(service)、类型化事件(typed event) 和可逆 effect(effect)。
关键推论:在 dsh 里没有需要打补丁的「特权核心」。模型适配器、工具注册表、会话日志、甚至 agent 循环本身,都是插件,因此都可以通过配置替换。你要扩展 dsh,就是在别的插件旁边挂载一个自己的插件;注册本身是可逆的 effect——插件卸载时会被撤销。
第 3 章会展开 Cordis 的全部机制;这里先记住这个结论,它决定了 dsh 的一切设计。
1.3 Profile 与 Bundle:运行中的 dsh 是一棵插件树
一个运行中的 dsh,是在启动时由若干有序分层拼出来的一棵插件树。
•
Profile(配置档案):存放在 Harness home 里的一个命名组合。它列出自己叠加哪些 bundle、安装了哪些树外插件,并保存用户自己的 cordis.patch.yml。web 和 headless 是随产品提供的两个模板。
•
Bundle(捆绑包):Cordis 配置行及其所挂载代码的分发格式。无论 bundle 插入了什么,都能被它上层的 layer 继续打补丁。
每个 bundle/profile 都在自己的 package.json 里通过 dsh 字段声明自己:dsh.profile 列出一个 profile 的 bundle,dsh.bundle 指向一个 bundle 的补丁文件。
三个内置 bundle:
•
dsh-base:每个 profile 的第一层——模型适配器、工具、持久化、沙箱与审批策略、设置、凭据、遥测。
•
dsh-web-app:加上浏览器应用(Web GUI)。
•
dsh-headless:加上一个完全不启动服务器的单次运行器。
分层的应用顺序
各 layer 作用在一个空的 entry 列表上,顺序固定:
profile 里按列出顺序的每个 bundle;
profile 自己的 cordis.patch.yml;
home 级的补丁;
--patch 覆盖层。
一个补丁按 row id 定位某一行并整体替换它的配置,或者插入新行。想看你机器实际启动出来的树:
代码块
Bash
dsh --profile web --dump-config
它打印出的任意一行,都可以被你自己的一份补丁替换掉。组合机制细节见 app-boot;配置字段在生成的配置目录。
1.4 能力接缝(capability seam)
dsh 最核心的抽象是 seam(接缝):一种可替换能力,由三种角色组成:
•
Service Definition:声明接口的 Cordis Service,拥有自己的 ctx.
•
Service Provider:实现该接口的一方。
•
Consumer:注入并使用该服务的一方(常见形态是面向模型的工具)。
三种角色构成完整的能力,缺一不可。packages/shell 是规范范例:
| |
| |
| |
| |
接缝是「换一个 provider,整个产品都跟着变」的原因:文件系统(ctx.fs)和子进程(ctx.subprocess)provider 共享同一个执行世界,把它们指向远程沙箱,Bash、PTY 终端、LSP 会一起跟着走,而无需为 provider 分叉出不同版本。Subagent 的 provider 同样在同一接口背后差别巨大——从全新子 agent 到「在另一个产品里委派一个 turn」。
第 6、7、8 章会逐个讲每个接缝;完整清单见能力接缝图。
1.5 事件:扩展点
事件是 dsh 的扩展点,选对事件域是大多数改动的第一步。三类事件:
•
Session 事件:追加到日志、并在 session/event 上广播的持久事实。当某个事实必须在重载后仍存在时使用。
•
Agent 事件(agent/*):携带一个活跃的 Agent(inbox、step、status、request、validation、continuation)。用来观察或拦截在途工作。
•
Capability 事件:把策略和适配器挂到一个接缝上(fs/*、tools/*、telemetry/*),而不引入 agent 循环。
事件地图列出了每个事件的生产者与消费者。
1.6 轮次(turn)流转
一个 step(步骤) 是「一次模型请求 + 它调用的工具」。一个 turn(轮次) 是零个或多个 step:它在第一条输入被认领前打开,在「没有任何待办」时关闭。流程图如下(turn/*、step/*、user/message、assistant/*、tool/* 是持久会话事件;其余是跨三个域的活跃扩展点):
代码块
Plain 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
几个关键点:
•
agent/pre-step、agent/request、llm/stream 和三个 tools/* 事件是 waterfall(其监听器必须调用 next() 才能继续委托);agent/turn-stopping 是 serial 且没有 next()。
•
输入通过一个 inbox 到达 driver。有些消息会立即唤醒它;注入的上下文则等在 inbox 里,直到另一条消息到来。
•
agent/pre-step 决定模型看到什么。监听器可以改写被认领的消息,或直接拒绝;被拒绝(或首次认领被改写为空)的首次认领仍会关闭一个不花任何 step 的持久 turn——日志记录了这次尝试。
1.7 会话日志:模型所见即日志
会话日志是模型所见上下文的来源。deriveMessages() 从日志投影出模型历史;原始 assistant/chunk 事件保留了回放和 UI 保真度。fork、resume、transcript、遥测、持久化全都从这条流派生。
模型可见 ⟺ 已入日志:任何进入模型请求的内容,都必须能从日志重建,且有一条运行时不变式(invariant)断言这一点。这就是「新的模型可见输入需要新的 session 事件」的原因——扩展 SessionEventMap 并从日志渲染。
1.8 新行为该放哪
新行为挂到一个文档化的扩展点上;改动 agent 循环本身才需要更新架构图。常用映射:
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
第 13 章会把这些映射变成可执行的步骤。
1.9 仓库地图
顶层目录(详见根 AGENTS.md):
代码块
Plain Text
vendor/ vendored 的 Cordis 源码(manifest + 同步流程见 vendor/README.md)
packages/ @deepseek-ai/dsh-
core/ 产品 API 主轴:session、system-prompt、tools、agent、agent-loop
api/ 远程 BFF 组装与 Typert RPC 网关
每个组的「包 / ctx key 映射」由各组 README 维护(packages/README.md)。
下一步
继续读第 2 章:环境与运行,把 dsh 真正跑起来,看看 --dump-config 打印出的插件树长什么样;然后进入第 3 章补上 Cordis 的机制细节。