Skip to content
登录

DeepSeek Harness 技术笔记:Cordis 插件内核、事件溯源与 Agent 执行

Updated View as Markdown

官方资料: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 各模块之间的联系,需要把握以下四条核心架构纽带:

  1. 驱动器与调用方的控制反转(core/agent 与 core/agent-loop 解耦): 外部调用方(如 UI、CLI 或上层业务插件)永远只依赖 ctx.agents 接口,绝不直接耦合 core/agent-loop 的具体实现。core/agent-loop 在启动时作为插件向 ctx.agents 注册一个 AgentFactory。这意味着如果你未来需要为特定场景替换一套全新的状态机循环(例如专门的图编排循环),只需替换驱动器插件,所有上层 UI、会话日志与工具插件无需改动一行代码。
  2. 基于 core/scope 的实例级生命周期隔离: 当调用方通过 ctx.agents.create() 创建一个 Agent 实例时,系统会利用 core/scope 为该实例开辟一个独立的子作用域(Agent Scope)。针对该 Agent 挂载的专属工具(注册到 ctx.tools)和专属提示词片段(注册到 ctx.systemPrompt)都受限于该 Scope。一旦调用 agentHandle.dispose(),Cordis 的可逆副作用机制会自动清理该实例名下的所有内存句柄与注册项,从根源上杜绝多 Agent 并发时的状态串扰与内存泄漏。
  3. 单步执行(Step)的横向服务编排: 在运行期,core/agent-loop 扮演“指挥官”角色,横向调度其余四个核心服务完成闭环:
    • 从 ctx.sessions 读取并投影当前会话的事件历史;
    • 向 ctx.tools 查询当前 Agent Scope 内可见的工具定义,并提取面向模型的安全 Schema;
    • 将历史上下文与工具 Schema 交由 ctx.systemPrompt 渲染出完整的本轮系统提示词;
    • 调用 ctx.llm 解析模型路由并发起流式推理;若模型返回工具调用,再交回 ctx.tools 执行受守卫的并发/独占管线,最终将产生的所有事实事件写回 ctx.sessions。
  4. 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 在各模块间的完整流转路径如下:

  1. 认领输入与开启轮次: core/agent-loop 从内部 Inbox 队列中认领待处理输入,向 ctx.sessions 写入并广播 turn/start 与 step/start。
  2. 上下文与 Schema 装配: ctx.systemPrompt 汇总当前 Scope 下的提示词片段、动态变量以及由 ctx.tools 投影出的工具 Schema,构建本步请求上下文。
  3. 前置拦截与修正: 触发 agent/pre-step 瀑布流事件,允许外部插件审查、修改甚至直接拦截当前输入。
  4. 模型路由与请求冻结: 触发 agent/request 事件后,通过 ctx.llm.prepareCall() 解析实际模型路由与参数,生成冻结的请求快照。
  5. 流式推理与工具管线: ctx.llm 流式输出助手响应;若包含工具调用,则移交 ctx.tools 依次经过受守卫的执行管线(pre-execute → execute → post-execute)。
  6. 事实落盘与循环裁决: 将模型可见的助手消息、工具调用及执行结果作为 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 必须保持以下三个存储子系统的物理与逻辑边界清晰:

  1. Session Store(会话事件存储): 仅负责按时间序追加记录当前 Agent 运行期的交互事件、模型请求与工具调用轨迹(对应 ctx.sessions)。
  2. OpenViking(长期记忆与知识检索): 负责跨会话、跨周期的非结构化/半结构化学习记忆沉淀、语义向量化与按需上下文召回。
  3. Learning DB(核心业务关系库): 负责持久化错题本、生词卡状态、练习正确率、艾宾浩斯复习排期等强一致性结构化业务事实。

这三套系统可以通过事件 ID 或会话 ID 相互关联,但职责绝不可互相替代——引入 dsh 风格的 Event Sourcing 是为了提升会话的可恢复性与可观测性,绝不意味着要用事件日志去取代结构化的 Learning DB。

总结:记住四个核心结论

  1. 模块解耦: “一切皆插件”的本质是通过 ctx 实现控制反转——调用方只面向 ctx.agents 编程,而 agent-loop、sessions、tools、systemPrompt 与 llm 均作为可替换的服务插件在 Scope 隔离域内协同工作。
  2. 执行粒度: Turn 代表从接收任务到完全收敛的一整轮交互,内部包含一个或多个 Step(单次模型推理 + 工具执行);默认 agent-loop 只是实现公共 Agent 接口的一种驱动器 Provider。
  3. 事件溯源: SessionEvent 是仅追加的持久化事实源,模型上下文与 UI 状态均由事件日志按需投影派生;但涉及外部世界修改的工具副作用,依然需要幂等键与业务事务兜底。
  4. 受管工具: 工具通过 Schema 投影对模型隐藏宿主策略,通过 pre/post-execute 瀑布流管线实现安全准入与输出清洗,并借助 isConcurrencySafe 严格划分并发与独占执行边界。

进一步阅读:官方架构说明 · Cordis 基础 · 核心 Agent 语义 · Pi Harness 技术笔记 · yoagent 技术笔记

Navigation

Type to search…

↑↓ navigate↵ selectEsc close