官方资料:DeepSeek Harness · 中文架构文档 · Cordis 入门 · 核心包 · Session · 工具
本文按 2026-10-15 官方 master 的架构文档、包说明整理。官方仍标注 Developer Preview,存在破坏性 API 变化风险。English Studio 部分仅是工程取舍分析,非已实现事实。
先建立心智模型
DeepSeek Harness(dsh)是一个基于 Cordis 的可组合 Agent Harness。 它不把模型、工具、会话、Loop、UI 都写成固定类的字段,而是把它们拆成在统一 Context 中注册服务的插件。扩展新功能时,原则上不是“修改大内核”,而是“安装或替换一项插件能力”。
与 yoagent “尽量让执行循环保持小核心”不同,dsh 更强调整个产品的插件化组合边界。
Cordis 内核本身负责什么?
Cordis 负责插件的装载/卸载、依赖与上下文、服务注册、带类型的事件以及可逆副作用(effect)。它不直接承担英语学习、模型调用或工具执行业务。
插件向共享 Context 提供服务,按需依赖别的服务。注册项受生命周期管理,插件卸载时相应效果可以被撤销。
| 术语 | 直观理解 |
|---|---|
Context(ctx) |
插件连接服务、事件和能力的共同入口 |
| Plugin | 向 Context 声明或实现一项能力 |
| Service / Seam | 对外公开接口,背后可由不同 Provider 实现 |
| Typed Events | 插件之间的事件型扩展点 |
| Scope / Realm | 限制能力和注册项作用范围 |
| Profile / Bundle | 按场景装配的插件配置集合 |
比如“将文件执行迁移到隔离沙箱”,可以通过更换相关文件系统和进程执行 Provider 来实现,而不应在每一种工具中分别硬编码远程执行逻辑。
一、核心包如何协作?
官方 核心子系统文档 将一次执行拆成多个相互配合的包:
| 组件 | Context 接口 | 负责什么 |
|---|---|---|
core/session |
ctx.sessions |
追加式会话事件日志 |
core/system-prompt |
ctx.systemPrompt |
组装 Prompt 片段与工具 Schema |
core/tools |
ctx.tools |
工具注册、作用域及守卫执行 |
core/agent |
ctx.agents |
公共 Agent 接口和注册表 |
core/agent-loop |
ctx.agentLoop |
默认 Agent 驱动器 |
core/scope |
非服务库 | 为能力按 Agent 限定作用域 |
llm/llm |
ctx.llm |
消息/流协议和模型适配器 |
其中一个细节尤其重要:外部消费者依赖 ctx.agents,而不是直接依赖具体 agent-loop 的实现。 默认 Loop 向注册表提供 AgentFactory。这使未来替换循环实现时,UI、工具与其它插件不需要全部跟着改。
dsh 的 Profile 由多层 Bundle 和用户配置组合而成,例如 Web、SDK 或 Headless 等不同启动方式。并不是每种运行模式都要加载完整 Web 产品。
二、Agent 的 Turn 与 Step 如何运转?
dsh 使用与部分 Agent 框架不同的粒度定义:
| 概念 | dsh 中的含义 |
|---|---|
| Session | 一份持久的 Agent 交互事件历史 |
| Turn | 从接纳任务到没有剩余工作的一轮执行,可能包含零个或多个 Step |
| Step | 一次模型请求以及该次请求产生的工具执行 |
| Agent | 有 send、cancel、whenIdle 等能力的运行句柄 |
因此不能把其它框架的 “Turn = 一次模型调用” 不加说明地移植过来。dsh 的 Step 更接近这个单次模型和工具的单位。
一次 Step 的简化执行路径:
agent-loop从 Inbox 中认领输入,开启turn/start。ctx.systemPrompt根据片段、变量和工具 Schema 构建上下文。agent/pre-step插件决定是否接受或修正当前输入。agent/request之后通过ctx.llm.prepareCall()解析实际模型路由,确定本次请求。- Stream 输出助手回复,工具调用经过受守卫的工具管线。
- 模型可见的消息、工具结果等追加进 Session 日志;如果还有工作,进入下一 Step,否则触发
agent/turn-stopping并关闭 Turn。
关键约束:每次模型看到的事实,应该能由持久日志重新构建。 这样才能做可信的回放、恢复和问题排查。真实实现对于取消、重试、失败的记录时机和请求冻结还有更严格约定,不能简单等同于“收到一个 token 就落盘一个 token”。
它如何拦截执行?
dsh 的事件主要分成三个领域:
| 事件域 | 职责 |
|---|---|
session/event |
发送已经写入 Session Log 的持久事件 |
agent/* |
观察和干预实时 Agent 行为:请求、步骤、状态、继续 |
tools/* / fs/* 等 |
挂接工具、文件系统等具体能力的策略 |
部分事件采用 Waterfall(瀑布式)处理器:插件调用 next() 才能把执行继续交给后继逻辑。这不同于单纯的“监听通知”事件。比如 tools/pre-execute 参与工具准入,tools/post-execute 可以做结果处理;不要把它们都当成无返回值的观察器。
三、事件溯源:为什么 SessionEvent 是核心设计?
dsh 的 Session 不把一份“最终消息数组”作为唯一持久状态,而将交互写成仅追加的类型化 SessionEvent 日志。模型历史由事件投影而来。
典型持久事件包括:
turn/start与turn/endstep/start与step/enduser/message、assistant/message、tool/call、tool/result- 提示词和请求相关事件,以及其它插件注册的类型化事件
例如一次语法诊断任务:
{
"kind": "illustrative-learning-result",
"session_id": "grammar-session-001",
"weakness": "present_perfect_continuous",
"outcome": "needs_practice"
}上面的 JSON 只是 English Studio 可以设计的业务事件示例,不是 dsh 官方 SessionEventMap 已定义的事件格式。真正集成时应通过框架提供的事件扩展契约登记并校验,而不是直接追加任意 JSON。
事件溯源带来什么?
| 需求 | 事件日志的优势 | 仍需注意 |
|---|---|---|
| 重新打开历史 | 由日志重新投影模型上下文与 UI | 回放器须理解事件版本 |
| 恢复中断 | 识别已提交的 Turn/Step/结果 | 外部工具副作用仍需幂等 |
| 定位问题 | 追踪输入接纳、请求、结果和失败 | 需谨慎记录敏感数据 |
| 扩展业务事件 | 通过插件扩展 SessionEventMap | 不能随意破坏已有投影 |
实时模型流和已持久化历史也不是同一种东西:agent/assistant-stream 可以给 UI 推送临时 chunk,而稳定记录以最终落入日志的消息或 attempt 为准。若进程在提交前硬中断,不能假定流过的每个 token 都已持久化。
四、工具为何也必须成为可替换的能力?
dsh 的工具不是裸函数数组。ToolDefinition 包含面向模型的 ToolSchema、规范化的输出契约、执行函数和可选运行元数据。
官方 工具子系统 特别强调:
- 模型只能看到工具名、描述与参数 Schema 等允许字段,不能看到宿主专属的执行策略。
- 工具成功输出要符合声明的 JSON Schema,并通过投影生成模型可见内容。
- 工具执行经
tools/pre-execute → tools/execute → tools/post-execute管线,可附加权限或输出修正。 isConcurrencySafe明确声明允许并发的工具;没有安全声明的操作会按独占约定执行。maxParallelToolCalls限制并行安全工具的同时在途数量;独占工具仍需要排序屏障。
下面是官方文档中的配置思想(该 YAML 是 dsh 声明式配置示例,不是 Rust 配置):
- name: '@deepseek-ai/dsh-agent-loop'
config:
maxParallelToolCalls: 10
agents:
- id: 'main'
provider: deepseek
model: deepseek-chat
reasoningEffort: high
cwd: /workspace这展示了 Loop 也只是被挂载的一项插件。调用方还可以通过 ctx.agents.create() 和 ctx.agents.resume() 创建与恢复 Agent,拿到有明确 dispose() 责任的 AgentHandle。这比“全局单例里放所有 Agent 状态”更利于隔离与回收。
五、Extension 和插件边界怎么设计?
可以用三个问题判断该写在哪一层:
| 需求 | 最接近的扩展点 | 不推荐 |
|---|---|---|
| 更换模型后端 | ctx.llm 的 Adapter |
在 Agent Loop 内硬编码不同 API |
| 新增语法查询工具 | ctx.tools 注册 ToolDefinition |
把业务逻辑混进 agent-loop |
| 会话按不同用户隔离 | Agent Scope 和业务身份规则 | 所有 Agent 复用同一可变全局上下文 |
| 新增长期学习记忆 | 独立 Memory Service 与 Tool/事件适配 | 强迫 Session Log 承载全部长期业务数据库 |
| 添加进度和审计 | 事件消费者、投影或遥测扩展 | 每个工具都手写一套日志系统 |
插件注册还有生命周期:加载后注册服务和事件,在卸载时应撤销效果;Agent 自己的 Scope 负责本地注册项随销毁而清理。
六、对 English Studio 的借鉴与取舍
你已经选择了 Rust Harness 和独立 OpenViking 服务,因此不必为了借鉴 Cordis 就整体迁移到 TypeScript dsh。真正值得复用的是其能力边界:
| dsh 的原理 | English Studio 的建议 |
|---|---|
| 基于接口访问 Agent | Rust AgentRuntime 公开统一创建、执行、取消和恢复能力 |
| Provider / Tool / Loop 解耦 | 模型适配、工具注册、循环由接口或 Trait 隔离 |
| SessionEvent 持久事实 | Durable Session 保存会话与工具状态,支持派生与追踪 |
| Agent Scope | 听力/语法/词汇三个 Agent 独立消息与工具权限 |
| Tool Schema + Guard | 对修改学习数据的工具进行参数校验、授权和幂等 |
| Plugins + Lifecycle | OpenViking Gateway 等服务可独立托管及关闭 |
| 组合配置 | 以 AgentDefinition 配置不同专业 Agent,而不是复制三套 Harness |
特别注意三个系统不要合并
- Session Store:当前 Agent 的事件和消息历史。
- OpenViking:跨会话长期学习记忆及检索。
- Learning DB:错题、词汇卡、正确率、复习排期等结构化业务事实。
这三者可以关联,但职责不相同。dsh 的 Event Sourcing 不代表需要取消 English Studio 的结构化 Learning DB。
总结:记住三个结论
- 组合:“一切皆插件”意味着服务由 Context 发现,由 Profile/Bundle 组合,由生命周期管理。
- 执行:Agent Loop 只是实现公共 Agent 接口的一个 Provider;Turn 包含多个 Step,工具经过受保护的管线。
- 状态:SessionEvent 是持久事实;模型上下文和 UI 状态由投影派生,真实工具副作用仍需业务级恢复策略。
进一步阅读:官方架构说明 · Cordis 基础 · 核心 Agent 语义 · Pi Harness 技术笔记 · yoagent 技术笔记