官方资料:DeepSeek Harness · 中文架构文档 · Cordis 入门 · 核心包 · Session · 工具
本文基于 2026-10-15 官方 master 分支的架构文档与子系统源码整理。目前官方仍将其标注为 Developer Preview(开发者预览版),存在破坏性 API 变更的可能;文中涉及 English Studio 的部分仅为工程取舍分析,不代表已落地事实。
先建立心智模型
DeepSeek Harness(简称 dsh)是一个构建在 Cordis 插件内核之上的可组合 Agent Harness。 它并没有把模型调用、工具管理、会话存储、执行循环和 UI 写死在某个庞大宿主类的成员字段里,而是将它们彻底拆解为在统一上下文(Context)中注册与消费服务的独立插件。在扩展系统能力时,其核心原则不是“修改大内核源码”,而是“挂载、组合或替换一项插件服务”。
如果说 yoagent 与 Pi 的核心哲学是“让执行循环保持极简核心”,那么 dsh 则更进一步,追求整个产品级运行时的插件化组合与热替换边界。
Cordis 内核本身负责什么?
Cordis 本身是一个控制反转(IoC)与生命周期管理微内核,负责插件的装载与卸载、依赖注入、服务注册、强类型事件分发以及可逆副作用(Reversible Effect)回收。它对英语学习、大模型推理或具体工具执行等业务语义完全无感。
所有插件通过共享的 Context(即 ctx)对外暴露服务,并按需声明对其他服务的依赖。所有注册动作均受生命周期托管——当某个插件被卸载或某个 Agent 作用域销毁时,其注册的服务、事件监听与工具会自动被干净地撤销。
| 核心术语 | 架构角色与直观理解 |
|---|---|
Context(ctx) |
插件注册服务、派发事件、声明依赖与管理生命周期的统一中枢 |
Plugin |
向 ctx 声明、消费或实现某项具体能力的最小模块化单元 |
Service / Seam |
对外暴露的抽象服务契约,底层可由不同的 Provider 插件替换实现 |
Typed Events |
插件之间解耦通信与拦截干预的强类型事件扩展点 |
Scope / Realm |
限定插件能力、工具可见性与注册项生命周期的隔离作用域 |
Profile / Bundle |
面向不同宿主形态(如 Web、CLI、SDK、Headless)按需装配的插件集合 |
例如,当我们需要“将本地文件读写与命令执行迁移到远程隔离沙箱”时,在 dsh 中只需替换底层的文件系统与进程执行 Provider 插件,而无需侵入任何上层工具或 Agent Loop 的代码。
一、核心包如何协作?模块拓扑与运行期联系
官方核心子系统文档将一次完整的 Agent 运行拆解为多个挂载在 ctx 上的正交子包:
| 核心模块包 | Context 挂载接口 | 核心职责 |
|---|---|---|
core/agent |
ctx.agents |
暴露公共 Agent 句柄接口(AgentHandle)与驱动器注册表(AgentFactory) |
core/agent-loop |
ctx.agentLoop |
默认的 Agent 循环驱动器实现,向 ctx.agents 注册工厂并推进 Turn / Step |
core/session |
ctx.sessions |
维护仅追加(Append-only)的强类型 SessionEvent 事件日志与状态投影 |
core/system-prompt |
ctx.systemPrompt |
按作用域收集并组装 Prompt 模板片段、动态变量与工具 Schema |
core/tools |
ctx.tools |
管理系统与实例级工具注册、并发屏障策略及守卫执行管线 |
core/scope |
基础原语库(非独立服务) | 为单个 Agent 实例划定独立的资源、工具与生命周期隔离域 |
llm/llm |
ctx.llm |
统一多厂商流式协议、模型路由解析(prepareCall)与适配器管理 |
模块间的联系:以 ctx 为中枢的解耦拓扑
不同于传统的“上层包直接 import 下层包并实例化”的硬编码调用,dsh 的所有模块都围绕 Cordis Context(ctx) 进行间接协作:
+-------------------------------------------------------------------------+
| 宿主接入与配置层 (Profile / Bundle) |
| (Web UI / CLI / SDK / Headless 装配清单) |
+-------------------------------------------------------------------------+
|
调用 ctx.agents.create() / resume()
v
+-------------------------------------------------------------------------+
| core/agent (ctx.agents 公共注册表与契约) |
| 对外返回具备 send / cancel / dispose 的 AgentHandle 句柄 |
+-------------------------------------------------------------------------+
^
注册默认 AgentFactory (控制反转)
|
+-------------------------------------------------------------------------+
| core/agent-loop (ctx.agentLoop 默认驱动器) |
| 管理 Inbox 队列,驱动外层 Turn 与内层 Step 状态机 |
+-------------------------------------------------------------------------+
| | | |
| 1. 读写持久事件 | 2. 组装提示词与 Schema | 3. 路由与流式推理 | 4. 守卫管线与并发调度
v v v v
+--------------+ +-------------------+ +-----------------+ +--------------+
| core/session | | core/system-prompt| | llm/llm | | core/tools |
| (ctx.sessions| | (ctx.systemPrompt)| | (ctx.llm) | | (ctx.tools) |
+--------------+ +-------------------+ +-----------------+ +--------------+
^ ^ ^
+---------------------+-----------------------------------------+
|
由 core/scope 提供单 Agent 实例级隔离与销毁回收 (dispose)理解 dsh 各模块之间的联系,需要把握以下四条核心架构纽带:
- 驱动器与调用方的控制反转(
core/agent与core/agent-loop解耦): 外部调用方(如 UI、CLI 或上层业务插件)永远只依赖ctx.agents接口,绝不直接耦合core/agent-loop的具体实现。core/agent-loop在启动时作为插件向ctx.agents注册一个AgentFactory。这意味着如果你未来需要为特定场景替换一套全新的状态机循环(例如专门的图编排循环),只需替换驱动器插件,所有上层 UI、会话日志与工具插件无需改动一行代码。 - 基于
core/scope的实例级生命周期隔离: 当调用方通过ctx.agents.create()创建一个 Agent 实例时,系统会利用core/scope为该实例开辟一个独立的子作用域(Agent Scope)。针对该 Agent 挂载的专属工具(注册到ctx.tools)和专属提示词片段(注册到ctx.systemPrompt)都受限于该 Scope。一旦调用agentHandle.dispose(),Cordis 的可逆副作用机制会自动清理该实例名下的所有内存句柄与注册项,从根源上杜绝多 Agent 并发时的状态串扰与内存泄漏。 - 单步执行(Step)的横向服务编排: 在运行期,
core/agent-loop扮演“指挥官”角色,横向调度其余四个核心服务完成闭环:- 从
ctx.sessions读取并投影当前会话的事件历史; - 向
ctx.tools查询当前 Agent Scope 内可见的工具定义,并提取面向模型的安全 Schema; - 将历史上下文与工具 Schema 交由
ctx.systemPrompt渲染出完整的本轮系统提示词; - 调用
ctx.llm解析模型路由并发起流式推理;若模型返回工具调用,再交回ctx.tools执行受守卫的并发/独占管线,最终将产生的所有事实事件写回ctx.sessions。
- 从
- Profile 与 Bundle 的按需装配: 顶层的
Profile通过叠加多层Bundle与用户配置来决定哪些插件进入ctx。例如 Headless 批处理模式只需加载核心 Loop、Session 与 LLM 插件,而不必加载任何与 Web UI 相关的沉重依赖。
二、Agent 的 Turn 与 Step 如何运转?
在阅读源码时,首先需要对齐 dsh 与其他 Agent 框架在执行粒度命名上的差异:
| 核心概念 | DeepSeek Harness 中的精确语义 |
|---|---|
Session |
一份持久化的、仅追加的 Agent 交互事件日志(Event Log) |
Turn |
从接纳外部输入任务开始,直到当前没有剩余工作为止的一整轮运行(内含 0 到多个 Step) |
Step |
单次模型推理请求,以及该次响应所触发的全部工具调用与结果回填 |
Agent |
具备 send、cancel、whenIdle、dispose 等生命周期控制能力的运行期句柄(AgentHandle) |
切勿将部分框架中“Turn = 单次模型调用”的习惯直接套用到 dsh 上——在 dsh 中,Step 才是单次“模型推理 + 工具执行”的最小闭环单位,而 Turn 对应的是 Pi 中的一轮完整内层循环。
一次标准 Step 在各模块间的完整流转路径如下:
- 认领输入与开启轮次:
core/agent-loop从内部 Inbox 队列中认领待处理输入,向ctx.sessions写入并广播turn/start与step/start。 - 上下文与 Schema 装配:
ctx.systemPrompt汇总当前 Scope 下的提示词片段、动态变量以及由ctx.tools投影出的工具 Schema,构建本步请求上下文。 - 前置拦截与修正: 触发
agent/pre-step瀑布流事件,允许外部插件审查、修改甚至直接拦截当前输入。 - 模型路由与请求冻结: 触发
agent/request事件后,通过ctx.llm.prepareCall()解析实际模型路由与参数,生成冻结的请求快照。 - 流式推理与工具管线:
ctx.llm流式输出助手响应;若包含工具调用,则移交ctx.tools依次经过受守卫的执行管线(pre-execute→execute→post-execute)。 - 事实落盘与循环裁决: 将模型可见的助手消息、工具调用及执行结果作为
SessionEvent追加写入ctx.sessions;若仍需继续推理则开启下一个Step,否则触发agent/turn-stopping并写入turn/end关闭当前 Turn。
事件总线如何拦截与干预执行?
dsh 的强类型事件总线按职责划分为三个正交领域:
| 事件域 | 核心职责与触发时机 |
|---|---|
session/event |
广播已经正式提交并写入 Session Log 的持久化事实事件 |
agent/* |
观察并干预 Agent 的实时运行态生命周期(如请求前、步骤边界、流式增量、停止裁决) |
tools/* / fs/* 等 |
挂载具体能力子系统的准入守卫、结果改写与底层资源访问策略 |
需要特别区分的是,dsh 中许多关键生命周期事件采用 Waterfall(瀑布式责任链) 处理器模式:拦截插件必须显式调用 next(),执行控制权才会继续向下游传递。例如 tools/pre-execute 负责工具调用的权限准入与参数校验,tools/post-execute 负责输出清洗与脱敏——它们是具备控制权的有状态中间件,而非仅仅是无返回值的旁路观察器(Observer)。
三、事件溯源:为什么 SessionEvent 是核心设计?
与直接把一份可变 Message[] 数组覆盖写入文件或数据库的做法不同,dsh 的 core/session 采用严格的事件溯源(Event Sourcing)架构:它将所有交互过程记录为仅追加(Append-only)的强类型 SessionEvent 日志,而模型推理所需的上下文以及前端展示的 UI 状态,全部由这份事件日志按需**投影(Projection)**生成。
典型的内置持久化事件包括:
- 生命周期边界:
turn/start、turn/end、step/start、step/end - 交互与执行事实:
user/message、assistant/message、tool/call、tool/result - 审计与扩展事实:提示词快照、请求路由元数据,以及由业务插件声明扩展的强类型自定义事件
例如,在 English Studio 的语法诊断场景中,我们可以设计如下业务级事件结构:
{
"kind": "illustrative-learning-result",
"session_id": "grammar-session-001",
"weakness": "present_perfect_continuous",
"outcome": "needs_practice"
}需要注意,上述 JSON 仅用于演示 English Studio 可自定义的业务事件形态,并非 dsh 官方 SessionEventMap 内置的标准事件。在实际工程集成时,必须通过框架的事件类型扩展契约进行声明与 Schema 校验,切忌绕过类型系统向日志中随意塞入无契约的裸 JSON。
事件溯源为 Agent 系统带来了什么?
| 核心工程诉求 | 事件溯源(Event Log)的架构优势 | 落地时仍需防范的工程边界 |
|---|---|---|
| 历史会话重放 | 可随时从同一份日志重新投影出模型上下文视图与多端 UI 视图 | 投影器(Projector)必须妥善处理事件 Schema 的跨版本演进兼容 |
| 进程中断恢复 | 能精确识别哪个 Turn / Step 或哪次工具调用已提交落盘 | 对外产生真实副作用的工具仍需外部幂等机制配合 |
| 线上故障排查 | 完整保留输入接纳、请求快照、工具返回与异常栈的时序因果链 | 必须在落盘前对隐私凭证与大体积载荷做脱敏和裁剪 |
| 业务状态扩展 | 插件可通过扩展 SessionEventMap 写入业务专属事实 |
新增自定义事件不得破坏既有上下文投影器的确定性 |
此外,必须严格区分实时流式事件与持久化事实事件:agent/assistant-stream 用于向 UI 实时推送低延迟的临时文本块(Chunk),而真正构成历史事实的,是最终收敛并写入 ctx.sessions 的完整消息或执行尝试(Attempt)。如果进程在流式输出中途遭遇断电或硬崩溃,绝不能假定此前流经屏幕的每一个 Token 都已经完成了持久化提交。
四、工具为何也必须成为可替换的受管能力?
在 dsh 中,工具绝不是一组裸露的回调函数数组。core/tools 中的每个 ToolDefinition 都由四部分严密组成:面向模型的输入 ToolSchema、规范化的输出 Schema 契约、底层执行函数,以及仅供宿主调度的运行期元数据。
官方工具子系统文档明确划定了以下安全与调度边界:
- 模型视野隔离: 模型只能看到由
ctx.systemPrompt投影出的工具名称、功能描述与入参 JSON Schema,完全看不到宿主层的权限策略、超时配置或内部上下文。 - 输出契约与双向投影: 工具执行成功后的原始返回值需符合声明的输出 Schema,并经过投影转换生成真正暴露给模型的精简内容(避免把庞大的内部状态直接塞爆模型上下文)。
- 三段式守卫管线: 每次工具调用都必须穿过
tools/pre-execute → tools/execute → tools/post-execute瀑布流管线,便于安全插件统一注入权限审批、参数清洗或结果脱敏逻辑。 - 显式并发安全声明(
isConcurrencySafe): 只有显式标记为并发安全的只读或无状态工具才允许并行执行;未声明安全性的工具一律按独占(Exclusive)模式串行调度。 - 并发度水位控制(
maxParallelToolCalls): 限制同一时刻处于执行中的并发安全工具上限;当遇到独占工具时,调度器会自动设置排序屏障(Barrier),等待在途并发工具全部收敛后再单独执行。
以下 YAML 片段展示了 dsh 的声明式装配思想(注:此为 dsh 的插件配置示例,而非 Rust 配置文件):
- name: '@deepseek-ai/dsh-agent-loop'
config:
maxParallelToolCalls: 10
agents:
- id: 'main'
provider: deepseek
model: deepseek-chat
reasoningEffort: high
cwd: /workspace这段配置直观地印证了前文的架构结论:就连最核心的 agent-loop 本身,也只是挂载在 ctx 上的一项可配置插件。上层业务通过 ctx.agents.create() 或 ctx.agents.resume() 创建及恢复实例,并获取带有明确 dispose() 销毁责任的 AgentHandle,从架构层面告别了“用一个全局可变单例塞下所有 Agent 状态”的脆弱模式。
五、Extension 与插件边界该如何划分?
在实际开发中,当面临一项新需求时,可以通过以下对照表快速判断应该将代码写在哪一层:
| 业务演进需求 | 推荐接入的 dsh 扩展点 |
应当避免的反模式(Anti-pattern) |
|---|---|---|
| 接入新的大模型厂商或网关 | 向 ctx.llm 注册新的 Model Adapter |
在 agent-loop 内部用 if-else 硬编码不同厂商 API |
| 新增英语语法规则查询能力 | 向 ctx.tools 注册受作用域管理的 ToolDefinition |
将语法查询的业务逻辑直接耦合进核心执行循环 |
| 实现多租户或多用户会话隔离 | 结合 core/scope 与业务身份规则创建独立 Agent 实例 |
让所有在线用户共享同一个可变全局 Context |
| 引入跨周期的长期学习记忆 | 封装独立的 Memory Service,再通过 Tool 与生命周期事件桥接 | 强行把追加式的 Session Event Log 当成全量关系型数据库查询 |
| 增加全链路进度展示与安全审计 | 订阅 agent/*、tools/* 与 session/event 编写遥测/审计插件 |
在每一个工具的执行函数内部重复手写日志与埋点代码 |
同时,任何插件的设计都必须具备生命周期对称性:装载时注册了哪些服务、挂载了哪些事件监听、向 Agent Scope 注入了哪些工具,在插件卸载或 Scope 销毁(dispose)时,就必须依靠 Cordis 的 Effect 机制完整回收。
六、对 English Studio 的架构借鉴与取舍
鉴于 English Studio 已经确立了 Rust 自研 Harness + 独立 OpenViking 记忆服务 的技术路线,我们完全没有必要为了复用 Cordis 的设计而将后端重写为 TypeScript。真正值得迁移到 Rust 架构中的,是 dsh 划分模块职责与解耦服务边界的工程思想:
| DeepSeek Harness 的设计精髓 | English Studio(Rust 架构)的落地建议 |
|---|---|
面向接口消费 Agent(ctx.agents) |
在 Rust 中定义统一的 AgentRuntime Trait,对外暴露 create、send、cancel、resume 与生命周期回收句柄 |
| Provider / Tool / Loop 彻底解耦 | 将模型协议适配(LlmProvider)、工具注册表(ToolRegistry)与执行驱动器(LoopDriver)拆分为独立 Trait |
SessionEvent 追加式持久事实 |
使用 Durable Session 记录细粒度的 Turn / Step / Tool 事件日志,支持随时派生出模型上下文与前端视图 |
基于 Scope 的实例级权限隔离 |
为听力(Listening)、语法(Grammar)、词汇(Vocabulary)三个专业 Agent 分配独立的工具可见域与数据修改权限 |
ToolSchema 投影 + 守卫管线 + 并发屏障 |
区分只读检索工具(可并发)与学习记录写工具(独占 + 幂等校验),并在执行前后挂载统一的参数审计与结果裁剪中间件 |
| 插件生命周期托管 | 让 OpenViking 客户端网关、数据库连接池与沙箱执行器实现统一的启动装配与优雅停机(Graceful Shutdown)契约 |
声明式 Profile / AgentDefinition |
通过配置化的 AgentDefinition 装配不同学科角色的 Agent,彻底杜绝为三个学科复制三套大同小异的 Loop 代码 |
核心架构红线:切勿将三类存储系统混为一谈
在借鉴 dsh 的事件溯源机制时,English Studio 必须保持以下三个存储子系统的物理与逻辑边界清晰:
- Session Store(会话事件存储): 仅负责按时间序追加记录当前 Agent 运行期的交互事件、模型请求与工具调用轨迹(对应
ctx.sessions)。 - OpenViking(长期记忆与知识检索): 负责跨会话、跨周期的非结构化/半结构化学习记忆沉淀、语义向量化与按需上下文召回。
- Learning DB(核心业务关系库): 负责持久化错题本、生词卡状态、练习正确率、艾宾浩斯复习排期等强一致性结构化业务事实。
这三套系统可以通过事件 ID 或会话 ID 相互关联,但职责绝不可互相替代——引入 dsh 风格的 Event Sourcing 是为了提升会话的可恢复性与可观测性,绝不意味着要用事件日志去取代结构化的 Learning DB。
总结:记住四个核心结论
- 模块解耦: “一切皆插件”的本质是通过
ctx实现控制反转——调用方只面向ctx.agents编程,而agent-loop、sessions、tools、systemPrompt与llm均作为可替换的服务插件在Scope隔离域内协同工作。 - 执行粒度:
Turn代表从接收任务到完全收敛的一整轮交互,内部包含一个或多个Step(单次模型推理 + 工具执行);默认agent-loop只是实现公共 Agent 接口的一种驱动器 Provider。 - 事件溯源:
SessionEvent是仅追加的持久化事实源,模型上下文与 UI 状态均由事件日志按需投影派生;但涉及外部世界修改的工具副作用,依然需要幂等键与业务事务兜底。 - 受管工具: 工具通过 Schema 投影对模型隐藏宿主策略,通过
pre/post-execute瀑布流管线实现安全准入与输出清洗,并借助isConcurrencySafe严格划分并发与独占执行边界。
进一步阅读:官方架构说明 · Cordis 基础 · 核心 Agent 语义 · Pi Harness 技术笔记 · yoagent 技术笔记