《DeepSeek Harness 设计与实现》

作者:deepseek harness


摘要

随着大语言模型(LLM)从"对话工具"演进为"自主行动体",如何工程化地承载 agent 的推理—行动循环(reason-act-observe loop)成为系统设计的关键课题。本文对 DeepSeek AI 开源的 agent harness 框架——DeepSeek Harness(dsh)——进行系统性的设计与实现分析。dsh 以"一切皆插件"(everything is a plugin)为核心范式,在底层 Cordis 框架的"时空可组合性"编程模型之上,将模型适配、工具执行、会话持久化、沙箱安全、人机审批、Web 界面等全部产品能力构建为可插拔、可替换、可热更新的插件树。本文从五个维度展开分析:(1)架构分层——应用入口层、配置组装层、核心服务层、能力实现层与基础设施层的五层模型;(2)事件驱动内核——会话事件(持久、可回放)、agent 事件(实时控制)与能力事件(seam 扩展)三域划分,以及轮次/步骤生命周期的完整事件序列;(3)工具执行流水线——从模型 tool-call 到规范化结果的十级把关流水线;(4)会话持久化——基于事件溯源(event sourcing)的只追加日志模型与"模型可见即已记录"架构红线;(5)前后端类型安全通信——Typert 代码生成驱动的 Host/Client RPC 网关。分析表明,dsh 通过"无特权内核 + 可逆副作用 + seam 可替换性"三原则,实现了 agent harness 领域罕见的可组合性与可观测性统一,为 LLM agent 基础设施的工程化提供了具有借鉴价值的参考实现。

关键词:agent harness;大语言模型;插件架构;事件溯源;工具执行流水线;可组合性;DeepSeek Harness;Cordis


1 引言

1.1 研究背景

大语言模型的快速发展使其能力边界从"文本生成"扩展到"与环境交互"。[R4] 的 ReAct 工作确立了"推理与行动交错"的 agent 范式:模型在每一步决定"想什么"与"做什么",执行动作后观察结果并继续推理。[R5] 的 Toolformer 工作则展示了模型可以学会调用外部工具。这两条研究路线共同指向一个工程事实:要让 LLM 在真实环境中可靠地工作,需要一个承载其推理循环、工具调用、状态记忆与安全边界的运行时基础设施——即 agent harness(智能体框架)。

业界已有多种 agent 框架形态(如单机脚本式、编排器式、微内核插件式),但普遍面临三重挑战:

  1. 可组合性挑战:模型适配器、工具、策略、UI 各自演进,如何让它们像积木一样自由组合而不互相破坏?
  2. 可观测性挑战:agent 的每一步行为(思考、调用、结果)如何被完整记录、可回放、可审计?
  3. 安全性挑战:agent 拥有执行能力(运行命令、改文件)时,如何在"能力"与"约束"之间取得平衡?

DeepSeek Harness(dsh)是 DeepSeek AI 于 2026 年开源的 agent harness 框架,其设计目标直指上述挑战:以"一切皆插件"架构解决可组合性,以事件溯源会话日志解决可观测性,以可插拔的沙箱与审批 seam 解决安全性。[R1] 的 Cordis 框架为其提供了"时空可组合性"的编程范式基础。

1.2 研究问题

本文围绕 dsh 提出以下研究问题:

  • RQ1:dsh 的总体架构是如何分层组织的?各层职责与依赖关系如何?
  • RQ2:dsh 如何用事件驱动模型统一"持久记录"与"实时控制"两种需求?
  • RQ3:dsh 的工具执行流水线如何保证"能力可扩展"与"安全可把关"并存?
  • RQ4:dsh 的会话持久化如何实现可回放、可恢复且不丢失模型可见信息?
  • RQ5:dsh 如何在前端(浏览器)与后端(Node 进程)之间实现类型安全的远程调用?

1.3 论文组织

本文结构如下:第 2 章介绍相关工作与理论背景;第 3 章给出 dsh 的总体架构五层模型;第 4 章分析配置组装机制(profile/bundle/patch);第 5 章深入事件驱动内核与 agent 生命周期;第 6 章分析工具执行流水线;第 7 章分析会话持久化与事件溯源;第 8 章分析 Typert 类型安全通信;第 9 章给出评估与讨论(含 4 篇事故复盘的工程启示);第 10 章总结并展望。


2 相关工作与理论背景

2.1 LLM Agent 范式演进

[插图说明:本论文的架构图均为依据官方文档绘制的原创 SVG→PNG 图,非截图。]

ReAct 与工具使用。 [R4] 提出的 ReAct 让模型在推理轨迹(thought)与行动(act)之间交替,观察(observe)结果后继续。这一"推理—行动—观察"循环构成了几乎所有现代 agent 框架的运行时骨架。dsh 的 agent loop 正是这一循环的工程化:一次"步骤"(step)= 一次模型请求 + 其请求的工具执行;一个"轮次"(turn)= 零到多个步骤,直到不再欠下任何工作。[R5] 的 Toolformer 则确立了模型与工具交互的接口形态——模型输出结构化的工具调用(function call),系统执行后把结果回注上下文。dsh 的 ctx.tools 注册表、JSON Schema 工具定义(defineTool)与工具结果规范化机制,正是该接口范式的生产级实现。

Agent 评测与综述。 [R6] 的 AgentBench 构建了覆盖操作系统、数据库、知识图谱等场景的 agent 评测基准;[R7] 的系统综述则梳理了 LLM agent 的架构组件——规划、记忆、工具使用、多 agent 协作。dsh 作为承载 agent 运行的 harness,其 Python SDK 与 JSONL 会话日志天然支持基准评测的可复现运行;其 subagent 委派、workflow 与 Ralph 循环机制则对应综述中的多 agent 协作范式。本文后续章节将把 dsh 的组件与 [R7] 的通用 agent 架构框架进行对照。

2.2 插件架构与可组合性

插件化软件架构是软件工程中的经典主题(微内核架构、OSGi、Eclipse RCP 等),其核心承诺是:通过定义良好的扩展点,让功能模块可插拔、可替换、可独立演进。然而传统插件系统通常存在一个"特权内核"——核心功能不可被插件替换。dsh 的架构哲学明确反对这一点:产品每一部分都是插件,包括模型适配器、工具注册表、会话日志,甚至 agent loop 本身,不存在需要打补丁的特权内核。

Cordis 与时空可组合性。 dsh 的底层框架 Cordis [R1] 提出"时空可组合性"(spatiotemporal composability)编程范式,其三个支柱是:

  1. 服务(Service):插件通过稳定的 ctx.<key> 向共享上下文贡献命名能力;其他插件通过 key 查找服务,而非导入具体实现——解耦了"接口"与"实现"。
  2. 类型化事件(Typed Events):插件通过 TypeScript 声明合并注册事件名,以 emit(广播)、waterfall(可改写流水线)、parallel(并行)、serial(顺序终结)四种模式分发。
  3. 可逆副作用(Reversible Side Effects):一切注册(监听器、工具、提示词片段、适配器)都是副作用,随插件卸载自动撤销——热替换不残留旧实例注册。

Cordis 在 dsh 中以 vendor 方式引入(源码在 vendor/cordis/),并整体改名发布为 @deepseek-ai/cordis(见 docs/rescope.zh.md 的映射表),确保所有 harness 包与框架绑定同一版本。

2.3 事件溯源与会话状态

事件溯源(Event Sourcing)[R8][R9] 是一种持久化模式:不保存状态的"当前值",而保存产生状态的全部事件;任何时刻的状态可由事件流重放得到。其优势是完整审计轨迹、时间旅行、可重放。dsh 将会话系统设计为事件溯源的工程范例:SessionEvent 日志是模型所见上下文的唯一真源(source of truth),deriveMessages() 从日志投影模型历史,fork、恢复、transcript、遥测、持久化全部派生自同一事件流。

dsh 对该模式的关键强化是"模型可见即已记录"(log what the model sees)不变量:抵达模型请求的一切都必须能从日志重建,由运行时不变式(ctx.invariants)断言。这一设计解决了 agent 场景特有的可审计性问题——人类需要知道"模型到底看到了什么"才能判断其行为是否合理。

2.4 类型安全的远程过程调用

前后端分离的现代应用通常通过 REST/WebSocket 交换 JSON。Typert(dsh 的类型系统组件)将这一交换升级为编译期类型安全:业务服务用 @Remote/@RemoteScope 装饰器声明开放给前端的远程方法,构建期由 Typert 生成器从 TypeScript 类型图生成 Host 与 Client 两端的严格描述符与 codec,Client 获得带类型的函数调用,运行时经统一 Connection 的 RPC 通道完成调用、校验与取消。这一设计可视为"类型化 RPC"路线的工程实践,本文第 8 章详细分析。


3 dsh 总体架构:五层模型

3.1 架构总览

依据对仓库 packages/(52 个分组、219 个包)、apps/(cli、web)、python/(SDK)、native/(Landlock 沙箱)与 docs/architecture.zh.mddocs/module-graph.zh.md 的分析,dsh 的总体架构可归纳为五层模型(图 1):

在这里插入图片描述

图 1:DeepSeek Harness 总体架构五层模型。自上而下:应用与入口层 → 配置组装层 → 核心服务层 → 能力实现层 → 基础设施层。箭头标注层间关系。

L5 应用与入口层:面向用户的运行形态。apps/cli 提供 dsh 命令行启动器(dsh webdsh --profile <name>dsh --profile headless "任务"dsh plugin ... 四种入口模式);apps/web 是 Vite 构建的浏览器应用;examples/ 提供 acp-agent、headless-agent、jsonrpc-agent、mcp-memory、web-cordis、web-schedule 等可运行示例;python/sdk 提供 Python SDK(deepseek_harness 包);headless 模式提供"跑一次任务打印答案退出"的无服务器形态。

L4 配置组装层:将"配方"变成"插件树"。profile(配置档)、bundle(组合包)、cordis.patch.yml 补丁与 app-boot 启动器协同,把用户选择的组合包按序叠加为生效配置(详见第 4 章)。

L3 核心服务层:Cordis 服务注册表上的核心服务,以 ctx.* 键暴露:ctx.agents(Agent 注册表)、ctx.agentLoop(唯一循环驱动器)、ctx.sessions(会话日志)、ctx.tools(工具注册表)、ctx.llm(LLM 适配器 seam)、ctx.systemPrompt(提示词组装)。这六个服务构成 agent 运行的核心四件套(agent/session/llm/tools)+ 两翼(agentLoop/systemPrompt)。

L2 能力实现层:可替换能力的 Provider 实现。文件系统(fs-local/fs-sandbox/fs-e2b)、Shell(bash-local/bash-sandbox/pwsh-local)、沙箱(sandbox-local,POSIX Landlock + Windows ACL)、会话持久化(JSONL/SQLite)、LLM 适配器(deepseek/pi-ai/replay)。每个能力都是"Definition + Provider + Consumer"的 seam 结构(见 3.3 节)。

L1 基础设施层:Cordis 框架本体(Context/Service/Fiber/事件分发/Loader/HMR)、Typert 类型系统(protocol/generator/registry)、util 工具族(brand、atomic-write、home-paths)与运行时不变式注册表(invariants)、原生边界(native/landlock-run、node-pty、subprocess、e2b)。

3.2 模块依赖的 DAG 结构

依据 docs/module-graph.zh.md(按各包 peerDependencies 生成),dsh 的 219 个包构成无循环依赖的有向无环图(DAG),分为四层:

  1. 基础工具层invariants(唯一零依赖包,被几乎所有包依赖)、util/*scopetypert-*storagesubprocess
  2. 核心服务/抽象 seam 层llmsessionsystem-promptagenttoolsfsshellsandbox 等——被上层消费且通常有多个实现。
  3. 能力实现层:llm-deepseek/pi-ai/replay、bash-local/sandbox、fs-local/sandbox、persistence-jsonl/sqlite 等。
  4. 组合/编排层:agent-loop、agent-spine-demo、headless、web-app、base、acp-demo、api-remotes、client-runtime 等。

高扇入枢纽invariants(几乎所有包)、agent/session/llm(各约 40 个消费者)、tools(约 35)、system-prompt关键组合主链agent-spine-demo → agent-loop → (agent, tools, session, system-prompt, session-persistence, llm, scope, settings)

DAG 无环这一事实并非偶然:Cordis 的 inject 依赖声明机制要求服务提供方必须先于消费方加载,依赖图无环是插件树可解析的充分必要条件。dsh 通过构建期门禁(verify-workspace-constraints 等)维护这一性质。

3.3 能力 Seam:可替换性的单元

dsh 定义"能力 seam"作为可替换性的基本单元(图 6 右半):一个 seam 包含三种角色——

角色 职责 Shell 范例
Service Definition 声明接口(Cordis Service,拥有 ctx.<key> 与词汇类型;绝不是 TypeScript interface) dsh-shellctx.shell
Service Provider 实现该接口 dsh-bash-local / dsh-bash-sandbox / dsh-pwsh-local
Consumer 消费该服务(通常是面向模型的工具) dsh-tool-bash

seam 的设计价值在于"替换一个提供方 = 改变整个产品":文件系统与进程提供方共享同一执行世界,把它们指向远程沙箱,Bash、PTY、LSP 一并搬移,无需提供方专用 fork。subagent 提供方在同一接口之后同样千差万别——从新建一个子 agent,到把一个轮次委派给另一个产品(ACP/Codex/Claude Code)。

依据 docs/capability-seams.zh.md,dsh 共暴露 56 个 ctx 服务键,其中约 25 个是可替换 seam,其余为核心主干服务(core)与组合包(bundle)。这一规模体现了 dsh "把一切做成 seam"的设计力度。


4 配置组装:Profile、Bundle 与 Patch

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

dsh 没有"硬编码的启动序列"。运行中的 dsh 是一棵插件树,由启动时按序叠加的配置层组合而成(图 6 左半):

在这里插入图片描述

图 6:左侧为配置层叠加顺序(自下而上:bundles → profile patch → home patch → --patch overlay);右侧为能力 seam 三角色结构与 56 个 ctx 服务键节选。

两个核心概念(均在 package.jsondsh 字段中声明):

  • profile(配置档)$DSH_HOME/profiles/<name>/ 下的目录,含 package.jsondsh.profile.bundles 有序列表 + 树外插件依赖)与用户自己的 cordis.patch.ymlwebheadless 作为模板随发行版交付。
  • bundle(组合包):声明 dsh.bundle 的 npm 包,附带一个 cordis.patch.yml 补丁层。dsh-base 是每个 profile 的第一层(模型适配器、工具、持久化、沙箱与审批策略、设置、凭据、遥测);dsh-web-app 增加浏览器应用;dsh-headless 增加无服务器一次性运行器。

4.2 配置层叠加顺序与 patch 语义

生效配置在空条目列表之上按以下顺序叠加(后应用的层按行胜出):

  1. profile 的 dsh.profile.bundles 列表(先 dsh-base,再各组合包按加入顺序);
  2. profile 自己的 cordis.patch.yml
  3. home 级 $DSH_HOME/cordis.patch.yml(机器本地偏好);
  4. 每个 --patch <path> overlay(按 argv 顺序)。

patch 语义(关键设计决策):

  • 一条 patch 按 id 定位条目并替换其整个 config,而不是深度合并各键——这迫使组合包作者"重述需要保留的每个字段",避免隐式继承造成的歧义。
  • insert 添加新条目;!!js 表达式在挂载时插值(仅在插件 config 内求值——事故 0002 的教训,见第 9 章)。
  • 验证工具:dsh --profile web --dump-config 打印合成后的完整配置树(含 # == 层名 注释);app-bootboot() 挂载 include 树后,assertEntriesLoaded/assertEntriesActivated 把"已启用但未加载/未激活"的条目转化为明确的启动失败。

4.3 启动器的角色

apps/clidsh 命令(src/args.ts + src/bin.ts)是"轻量自执行组合":它只解析自己的 flag,把其余参数交给已启动 profile 的插件解析(@deepseek-ai/dsh-cmdline)。启动流程由 @deepseek-ai/dsh-app-bootboot() 统一承载:创建根上下文 → 暴露 dshHomePath() → 安装 Loader → 挂载 include 树 → 断言加载与激活 → 返回根上下文;失败时 dispose 部分构造的上下文并以带标签的错误 reject。watchUserPatches 提供 cordis.patch.yml 的热重载:文件变更时以事务方式重新组合 patch 列表,失败则保留最后一个可用树继续运行。


5 事件驱动内核与 Agent 生命周期

5.1 三个事件域:持久与实时的统一

dsh 架构文档(docs/architecture.zh.md)明确将事件划分为三个域,这是理解其事件体系的第一把钥匙:

事件域 事件示例 性质 用途
会话事件(持久) turn/startturn/endstep/startstep/enduser/messageassistant/chunkassistant/messagetool/calltool/result 追加到日志,经 session/event 广播;可回放 需要在重新加载后仍然存在的事实
Agent 事件agent/* agent/pre-stepagent/requestagent/request-erroragent/turn-stoppingagent/statusagent/createdagent/disposedagent/inbox/* 携带活跃 Agent;实时控制与状态 观察或拦截进行中的工作
能力事件(seam) fs/write-intentfs/edit-intentfs/observedtools/pre-executetools/executetools/post-executetools/result 无需导入循环即向 seam 附加策略/适配器 扩展能力 seam

设计要点:持久事实与实时控制分离——需要重放的信息进会话日志(事件溯源),需要即时响应的工作走 agent/能力事件(实时分发)。这避免了单一事件总线"什么都广播"导致的记录膨胀与顺序耦合。

5.2 轮次/步骤生命周期

依据 docs/agent-lifecycle.zh.md 的时序图(英文源由 scripts/gen-doc-graphs.ts 生成),图 2 呈现了完整生命周期:

在这里插入图片描述

图 2:轮次/步骤生命周期:turn/start → agent/pre-step 把关 → step/start → 模型请求(agent/request → llm/stream → assistant/chunk → assistant/message)→ 工具执行(tool/call* → tool/result*)→ step/end → agent/turn-stopping → turn/end。*

生命周期的事件序列(文字化):

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/end 记录 TurnEndReason)。
  2. waterfall 把关agent/pre-step 决定模型看到什么——监听器可改写已领取消息或拒绝;首次领取被拒/改空时仍关闭一个不含步骤的持久轮次(日志记录尝试)。agent/requestllm/stream、三个 tools/* 事件同为 waterfall,监听器必须调用 next() 委托;agent/turn-stopping 是 serial 终结检查点(无 next())。
  3. 输入通道followup() 立即唤醒驱动器;inject() 追加上下文留在 inbox 直到另一条消息唤醒;steer() 中途引导当前步骤。排队消息与注入上下文在下一步骤批次中经同一 pre-step waterfall。
  4. 失败与恢复:适配器/终端带内请求失败 → 写 step/endagent/request-error(waterfall,返回 retry action 或保留原始错误);上下文压缩的自动压力检查跑在串行 agent/pre-step,标准溢出恢复跑在 agent/request-error;取消与 dispose 经 AgentHandle 达到完全停稳(停止并排空 → 撤销作用域 → detach agent → detach 会话)。

不变量"模型可见即已记录"assistant/message 记录每次成功的提供方调用(包括空内容/max-tokens 结束的),空内容不进派生历史但持久事件保留用量与 sourceEventSeqs 引用;新增模型可见输入必须新增 SessionEventMap 事件并从日志渲染,由运行时不变式断言。

5.3 Agent 作用域与 subagent 委派

ctx.agents(AgentRegistry)跟踪活跃 agent,Agent.ctx 是 agent 的带作用域上下文(dsh-scope,键 = 该 agent):通过它注册工具/提示词段/变量/监听器只对该 agent 生效,dispose 时全部撤销。scope 机制(packages/core/scope)提供两级扁平结构(全局 vs 单 scope),带作用域注册不向下继承给 subagent;子树行为通过 lineage 数据(parentSessiondelegationDepthsubagentDepth)表达。

subagent 委派是 seam 化的:ctx.subagents 提供方注册表(subagent-spawn-in-process、subagent-fork-in-process、subagent-acp、subagent-codex、subagent-claude-code、subagent-dsh-sdk)→ dsh-tool-subagent 向模型暴露 subagent/subagent_fork 工具,dsh-tool-subagent-control 提供 send_message/interrupt_agent/list_agents 控制工具。这一设计让"同进程子 agent"与"委派给另一个产品"共享同一编程接口。


6 工具执行流水线:能力与安全的统一

6.1 流水线总体结构

dsh 的工具系统(ctx.toolspackages/core/tools)不是简单的"注册—调用"表,而是一条带多层把关的执行流水线。依据 docs/tool-execution-pipeline.zh.md(英文源含人工维护的 Mermaid 流程图),图 3 呈现了完整流程:

在这里插入图片描述

图 3:工具执行流水线:模型 tool-call → tool/call 会话事件 → tools/pre-execute(waterfall)→ 单调守卫 → ctx.approval 审批 → tools/execute(waterfall)→ 工具体 → tools/post-execute(waterfall)→ 规范化 → finalizeContent → tools/result → tool/result 会话事件。

6.2 十级把关的语义分析

流水线按顺序执行以下阶段:

  1. 入口:模型发出 tool-call → 写会话事件 tool/call(执行前记录)→ UI pending 卡片 presentCall(args)
  2. tools/pre-execute(waterfall):hooks、权限策略、沙箱在此介入。结果:allow(放行)→ 守卫;deny → 拒绝路径;ask → 审批;throw → 规范化。
  3. 单调守卫(monotonic guards):deny 或 abstain,身份受保护,后续监听器无法撤销。deny → 跳过工具体(fail-closed);allow → tools/execute
  4. ctx.approval 一次性审批allowed-once → 回守卫;rejected/cancelled/unavailable → 拒绝。应答者缺失或不可回答时默认 denyApprovalPolicy'ask' 默认;'never' 则永不询问、确定性地拒绝,适用于 CI/无人值守)。
  5. tools/execute(waterfall 环绕分发):timeout、retry、metrics 等横切关注点包装核心分发;包装层可替换 exec.signal(施加截止时间)但不可移除。
  6. 工具体执行execute() body;tool-fs 的 mutation 经过 fs/write-intentfs/edit-intent 守卫(先读后编辑检查,可循环回工具体);发出工具自有会话事件(todo/writefs/observedhook/invokedhook/resulttool/code-dispatch)。
  7. tools/post-execute(waterfall):accept、block、replace、add context(附加上下文)。
  8. 注册表外层规范化:流水线/结果快照抛出的异常 → isError(无损 JSON 快照 + 冻结)。
  9. ToolDefinition.finalizeContent:最后的内容不变式(content-only),随快照固定。
  10. tools/result:同步通知,冻结的权威结果(frozen authoritative outcome)→ 写会话事件 tool/result(单一模型可见结果)→ UI completed 卡片 presentResult(args, result)。工具批次全部结算后,additionalContexts FIFO 注入 user/message

6.3 设计要点与扩展点选择

  • 三个 waterfall 可以改写一次调用:pre/execute/post 各司其职,且不可重排序的所有者策略仍作为已注册守卫(守卫机制与 waterfall 并存,语义互补)。
  • 钩子跨越工具系列:策略与工具解耦,无需工具与某个策略服务耦合。
  • Code Mode:保留的 run_code 传输及其序列化子调用全部重进同一流水线,子调用携带父级 token、记录 tool/code-dispatch、将拒绝呈现为有约束力的驳回,并省略 additionalContexts 以保持调用与结果相邻。

扩展点选择指南(docs/cookbook/adding-a-tool.zh.md):

需求 扩展点
允许/拒绝/询问策略 tools/pre-execute
最终单调拒绝 ctx.tools.guard()
截止时间/重试/指标 tools/execute
替换展示/返回值/加上下文 tools/post-execute
观察不可变结果 tools/result

对 RQ3 的回答:dsh 通过"waterfall 可改写 + 守卫不可撤销 + 审批缺席即拒 + 结果规范化冻结"四层机制,实现了能力可扩展与安全可把关的并存——扩展点开放给策略插件,而不可变性与 fail-closed 语义由注册表强制执行。


7 会话持久化:事件溯源的工程实现

7.1 设计:日志是唯一真源

dsh 的会话系统(packages/core/sessionpackages/session/session-persistence-*)是事件溯源模式的完整工程实现(图 4):

在这里插入图片描述

图 4:会话持久化与事件溯源:生产者(agent-loop/插件/注入/seed)→ Session 内存日志(44 事件键)→ session/event 广播 → 派生消费方(read path)与持久化后端(write path)→ load/prepare/resume 恢复路径。

核心决策:持久化单元就是 SessionEvent——不存在另一套并行的"持久消息"类型。不参与回放对话的元数据(格式版本、cwd、血缘、种子边界、origin、委托深度)作为 SessionHeader 单独传输。

依据 docs/persistence-catalog.zh.md,会话事件共 44 个事件键:3 个 surface 事件(user/messageassistant/messagetool/result,产生 LLM 消息、可入有序 surface)与 41 个 log-only 事件(可持久化、可回放但不参与派生历史)。SessionEvent 信封含 typeseq(单调递增)、timedata,以及可选的 ignorable 标记(读者可安全跳过的未知类型;缺省=必需,遇未识别类型必须拒绝重建而非静默丢弃)。

7.2 持久化后端与共享写入协调器

两个第一方后端共享同一套写入协调器(PersistenceCoordinator),通过小型 PersistenceBackend 存储钩子接口组合:

  • JSONLsession-persistence-jsonl):顺序产物,支持 zstd 压缩与校验和,可打包存储行。
  • SQLitesession-persistence-sqlite):可寻址读取后缀,无需解析完整日志。

每个后端必须遵守的不变量

  1. 仅追加;崩溃轮次被关闭而非截断:已 flush 事件绝不重写。崩溃可留下未关闭的最终轮次,load 保留它们并持久追加合成 closer(为每个未获回答的 assistant 调用添加带风险分类错误的 tool/result,再添加 step/end? + turn/end {interrupted})以平衡日志。
  2. 连续 seqload 拒绝日志中间的 seq 缺口/解析错误;append 的第一个 seq 必须等于已存储 next-seq。
  3. JSON 可序列化append 通过共享单遍无损 JSON 边界实体化每个批次。
  4. 持久性append 只在批次持久后返回。

写入协调器coordinator.ts + write-behind.ts)负责:每 id 状态串行化、每个活动会话各自的有界写入 controller、延迟实体化、崩溃尾部修复、会话接管、完全停稳的 dispose。session/flush(parallel 事件)作为共享的完全停稳屏障:取消有界批处理窗口的等待,排空屏障运行期间接纳的事件。

7.3 读取、恢复与格式演进

SessionPersistence 服务(ctx.sessionPersistence)暴露 locate/create/append/prepare/load/inspect/readFrom/list/listSnapshots 等方法。load(id) 在转换同一格式版本中受支持的旧记录后返回不可变、平衡的逻辑日志并提交冷恢复;实时 load 先 flush 快照并在轮次开放时拒绝。prepare(id) 预留恢复所需的未发布 Session,提交待处理恢复。

格式演进策略:读取时会转换同一格式版本中明确支持的旧记录(范围受限的导入例外,如消息标识机制引入前的消息获得确定性 id legacy-message:<session-id>:<event-seq>);存储仍仅追加——读取不重写旧记录,此后追加的事件使用当前格式。这避免了"迁移重写"的破坏性操作。

对 RQ4 的回答:dsh 通过"事件即持久化单元 + 只追加 + 崩溃合成 closer + 受控格式转换"实现了可回放、可恢复且不丢失模型可见信息,并将"模型可见即已记录"提升为运行时不变式。


8 Typert:类型安全的 Host/Client 通信

8.1 问题:前后端类型契约的漂移

dsh 的 Web UI(Client,浏览器)与 Node 进程(Host)需要交换大量结构化数据(agent 状态、目标、会话、设置、凭据、任务)。传统 REST + 手写类型声明极易产生"前端不知道后端改了签名"的契约漂移。dsh 采用 Typert 代码生成路线(图 5):

在这里插入图片描述

图 5:Host/Client 通信架构:Client(ui 包、client-runtime、ctx.remote、client-connection)经 Connection(SSE 事件流 + POST /api/<ns>/<method> RPC)与 Host(web-server、apiproxy、api-gateway、typert-registry、业务 Remote 服务、Cordis 插件树)通信。底部为构建期严格生成流水线。

8.2 编程模型:@Remote 装饰器

业务服务通过 @Remote(调用根 Host Context 中注册的 Cordis 服务)或 @RemoteScope(key)(先解析作用域 Context 再调用)选择对 Client 开放的方法。未标记的方法不进生成的 Client 类型、不贡献运行时。Host 侧示例:

export class GoalService extends TypertRemoteService {
  constructor(ctx: Context) {
    super(ctx, 'goals')
  }

  @Remote('create')
  createForClient(agent: Agent, request: CreateGoalRequest, signal: AbortSignal): CreateGoalResult {
    signal.throwIfAborted()
    return this.create(agent, request)
  }

  @RemoteScope('agent', 'current')
  currentForClient(): CreateGoalResult {
    return { accepted: true }
  }
}

Lookup 机制:复杂 Host 对象不能直接跨 wire 传输;业务包通过 TypertLookupMap 声明它与 wire identity 的关联(如 Agent 参数在 wire 上名为 agentId),Gateway 在调用业务方法前将 id 解析为 Host 对象。api-remotes 负责 agent/session 的标准 agentFor() 语义:复用 live Agent、自动恢复普通冷会话、对并发恢复去重、拒绝由 subagent routing 拥有的 identity。

8.3 生成流水线与运行时调用

构建期(严格生成流水线,按序执行):

  1. tsc -b tsconfig.host.jsontsdown --env.DSH_BUILD_FACE host:Typert generator 以 Host aggregate 为唯一 ts.Program 种子运行,从类型图严格分析 Remote 签名、类型、lookup、Context 与源码位置。
  2. tsc -b tsconfig.client.jsontsdown --env.DSH_BUILD_FACE client:消费刚生成的 Remote Client 声明与运行时贡献,不再次启动 Typert

每个贡献业务包把生成文件写入自己的 lib/typert.host.js/.d.ts 供 Host;typert.remote-client.js/.d.ts 供 Client),并通过 ./typert(Host)与 ./remote(Host-for-Client)子路径暴露。声明 map 支持从 Client 调用跳转到真实实现(declaration-map)。

严格分析约束:Remote 必须是公开、非静态、有具体实现的实例方法;方法不能泛型;参数必须具名且必填的简单标识符(不能解构、默认值、rest、可选参数);可 JSON 表示的普通类型生成严格 schema;复杂对象必须具有唯一 TypertLookupMap 声明。

运行时:Client Remote 调用 connection.rpc.call('/api', '<namespace>/<method>', { args }, signal),HTTP carrier 对应 POST /api/<namespace>/<method>。Connection 在 HTTP bridge 前执行统一信任检查;Gateway 认领存在严格描述符的两段式 endpoint,未认领的请求回退到既有 API Proxy。职责分离:Connection 拥有传输、RPC id、响应 envelope 与请求取消;Gateway 只拥有 Remote 数据协议与业务分发——未来替换 Connection carrier 不要求改变 Remote 描述符或 Client 编程接口。

开发回退(SRC)node --import tsx/esm 从源码启动时不执行 Typert 编译插件;标准 decorator 初始化器把方法名与调用模式记录到模块私有 WeakMap,Gateway 构造较弱的临时描述符(从运行中函数解析简单参数名,不读取 TypeScript 类型、不生成 Zod schema)。Client 拒绝挂载缺少严格 codec 的 SRC 描述符。

对 RQ5 的回答:dsh 用"装饰器声明 + 构建期代码生成 + 运行时严格校验"实现了类型安全的远程调用,且通过 SRC 回退保持了开发期热迭代的灵活性。


9 评估与讨论

9.1 设计原则评估

dsh 的架构可提炼为三条贯穿性设计原则,本节评估其实现力度与效果:

原则一:无特权内核(No Privileged Kernel)。 产品的每一部分都是插件,包括模型适配器、工具注册表、会话日志与 agent loop 本身。评估:这一原则在 dsh 中得到彻底贯彻——packages/core/agent-loop 是"harness 中唯一包含具体循环逻辑的包",其他一切均为抽象服务或针对扩展点的插件;新行为通过 ctx.effect()/ctx.on() 注册,随卸载撤销。效果:插件热替换(HMR)不残留旧实例注册;用户可在 cordis.patch.yml 中按 id 覆盖任意内置行(如替换默认 Bash 为本地 pwsh)。

原则二:事件溯源与"模型可见即已记录"。 会话日志是唯一真源,模型可见输入必须可重建。评估:44 个会话事件键覆盖了从 token 级流块(assistant/chunk)到审批审计(approval/asked/decided)的全部事实;deriveMessages() 投影、fork/恢复/transcript/遥测/持久化全部派生自该流。效果:人类可完整审计"模型看到了什么、调了什么、结果如何"——这是 agent 系统可信度的关键。

原则三:seam 可替换性。 能力 = Definition + Provider + Consumer 三角色。评估:56 个 ctx 服务键中约 25 个为可替换 seam;替换提供方即可改变整个产品(本地 Bash → 沙箱 → 远程)。效果:安全策略(沙箱、审批)与能力实现正交演进,不互相侵入。

9.2 工程质量的量化观察

依据 docs/module-graph.zh.md 与仓库结构:

维度 数值 含义
包数量 219(49 个 group) 模块化粒度细
依赖图 无环 DAG 依赖可解析、可静态校验
ctx 服务键 56 能力面覆盖广
会话事件键 44(3 surface / 41 log-only) 事件词汇完整
面向模型工具 ~51(24 工具包) 能力丰富
文档 105 中 + 105 英(docs/) 双语全量文档化
测试门禁 check:all(lint/typecheck/test/verify-* 系列) 全量自动化校验

dsh 的工程严谨性还体现在自动生成的参考目录(config-catalog、tool-catalog、persistence-catalog、module-graph、capability-seams、event-producer-consumer、composition 等),这些目录由 scripts/gen-*.ts 从源码生成并由 verify-* 门禁强制新鲜度——文档与代码不会漂移。

9.3 事故复盘的工程启示

dsh 公开了 4 篇事故复盘(docs/postmortem/),它们是评估系统健壮性的真实证据:

# 事故 根因类别 防护措施
0001 ACP 服务器崩溃:export defaultinject 插件导出形态与 Loader 语义不符 + 测试未走真实入口 export defaultctx.get() 替代属性读取;无 key e2e 走真实 Loader;testing.md 规则"测试真实入口路径"
0002 文件系统工具被 !!js 永久禁用 对 Loader 插值范围(仅 config)的假设错误 + 快照框架缺陷 显式 overlay;AGENTS.md 说明;verify-cordis-config 拒绝条目元数据中的表达式;快照拒绝 UNKNOWN_TOOL
0003 Web agent 验收了替代服务器 Web 组合未向模型暴露当前 GUI 身份 + HTTP 200 ≠ 应用就绪 app:web-surface 提示词段 + $DSH_WEB_URL/$DSH_WEB_MODE;独立 Vite 配置期拒绝启动;分层真实路径测试
0004 Landlock 部分通知误分类子进程失败 进程归因仅凭共享前缀单证据 RunnerFailureRule(允许退出码 + 逐行致命签名 + 精确排除信息性行);ripgrep 移出沙箱 bash

启示:这四篇复盘共同指向一个工程文化特征——dsh 不仅记录"修复了什么",还记录"为什么流程放过了它",并把教训固化为可执行的门禁(verify-* 系列、AGENTS.md 规则、testing.md 约定)。这是 agent harness 这类安全敏感基础设施的正确工程姿态。

9.4 局限与讨论

局限性 1:开发者预览状态。 dsh 处于快速迭代期,破坏兼容性变更可能发生;SESSION_FORMAT_VERSION = 0 明确"预发布,不暗示兼容"。

局限性 2:平台覆盖。 沙箱的完整能力依赖平台(POSIX Landlock/Seatbelt/bwrap、Windows ACL);Python SDK 的持久 PTY 后端需要 POSIX 终端环境,Windows agent 不受支持(SDK 层面)。

局限性 3:复杂度门槛。 "一切皆插件 + seam 三角色 + 56 个 ctx 键"的学习曲线陡峭;新手需要 Cordis 概念(Fiber/waterfall/inject)才能有效扩展。这与其目标受众(开发者/研究者)相符,但对普通用户是负担(由 Web UI 与默认 profile 缓解)。

局限性 4:模型依赖。 harness 的能力上限受底层模型(默认 deepseek-v4-flash)驱动;视觉模态、工具调用质量等均受模型制约,harness 本身只能保证"给模型的能力是安全、可记录、可回放"的。

9.5 对研究问题的总结回答

研究问题 回答摘要
RQ1 总体架构 五层模型:应用入口 / 配置组装 / 核心服务 / 能力实现 / 基础设施;219 包构成无环 DAG
RQ2 事件驱动统一 三事件域分工:会话事件(持久可回放)/ agent 事件(实时控制)/ 能力事件(seam 扩展)
RQ3 工具流水线 十级把关:pre-execute → 守卫 → 审批 → execute → 工具体 → post-execute → 规范化 → finalize → result
RQ4 持久化 事件溯源:日志即唯一真源;只追加、崩溃合成 closer、受控格式转换
RQ5 类型安全通信 Typert 生成:@Remote 声明 + 构建期代码生成 + Connection 承载传输 + SRC 回退

10 结论与展望

10.1 结论

本文对 DeepSeek Harness 进行了系统性的设计与实现分析。dsh 的核心贡献可以概括为:在 Cordis 的"时空可组合性"编程范式之上,将 LLM agent 的完整运行时——从模型适配、工具执行、会话记忆到安全沙箱与人类审批——构建为统一的可组合、可替换、可审计的插件系统

其最具借鉴价值的三个设计决策是:

  1. 无特权内核 + 可逆副作用:以"每一部分都是插件"换取极致的可组合性与热替换能力,避免了传统插件系统"内核不可替换"的妥协。
  2. 事件溯源会话日志 + "模型可见即已记录"不变量:把 agent 的可审计性从"事后日志"提升为"架构红线",由运行时不变式强制执行。
  3. seam 三角色抽象:用统一的 Definition/Provider/Consumer 结构封装全部可替换能力,使安全策略(沙箱、审批)与能力实现正交演进。

10.2 展望

基于本文的分析,dsh 的后续发展空间包括:

  • 多模型路由策略深化llm seam 已支持多适配器,可进一步探索成本/质量感知的动态路由。
  • 评测闭环:结合 [R6] AgentBench 类基准构建内置评测工作流。
  • 分布式沙箱:fs/shell/subprocess 指向远程执行世界的机制已具备,可探索云沙箱编排。
  • 插件生态治理dsh-plugin 话题与组合包机制已就绪,可建设插件市场与签名验证。
  • 记忆系统深化:skill、spill、session-reference 已覆盖记忆雏形,可探索长期记忆与检索增强(RAG)集成。

(拓展方向的详细路线图见《未来计划.md》。)


参考文献

[R1] Cordis 团队. A Programming Paradigm for Spatiotemporal Composability. https://github.com/cordiverse/paper

[R2] DeepSeek AI. DeepSeek Harness(开源仓库与文档). https://github.com/deepseek-ai/DeepSeek-Harness

[R3] Floatboat AI. Cordis — The Plugin Kernel Behind DeepSeek Harness. https://floatboat.ai/blog/cordis-plugin-framework

[R4] S. Yao, J. Zhao, D. Yu, N. Du, I. Shafran, K. Narasimhan, Y. Cao. ReAct: Synergizing Reasoning and Acting in Language Models. ICLR 2023. arXiv:2210.03629. https://arxiv.org/abs/2210.03629

[R5] T. Schick, J. Dwivedi-Yu, R. Dessì, R. Raileanu, M. Lomeli, E. Hambro, L. Zettlemoyer, N. Cancedda, T. Scialom. Toolformer: Language Models Can Teach Themselves to Use Tools. NeurIPS 2023. arXiv:2302.04761. https://arxiv.org/abs/2302.04761

[R6] X. Liu, H. Yu, H. Zhang, et al. AgentBench: Evaluating LLMs as Agents. ICLR 2024. arXiv:2308.03688. https://arxiv.org/abs/2308.03688

[R7] Z. Xi, W. Chen, X. Guo, et al. The Rise and Potential of Large Language Model Based Agents: A Survey. 2023. arXiv:2309.07864. https://arxiv.org/abs/2309.07864

[R8] M. Fowler. The Many Meanings of Event-Driven Architecture. 2017. https://martinfowler.com/articles/201701-event-driven.html

[R9] M. Fowler. Event Sourcing(企业应用架构模式).2005. https://martinfowler.com/eaaDev/EventSourcing.html

[R10] sparanoid 等. 中文文案排版指北(Chinese Copywriting Guidelines). https://github.com/sparanoid/chinese-copywriting-guidelines

[R11] 36Kr. DeepSeek Self-Evolution Blueprint(媒体报道). 2026. https://eu.36kr.com/en/p/3938795963137411


本文为技术解析与教学目的的学术论文,分析对象为 DeepSeek AI 开源的 DeepSeek Harness 项目;文中所有工程细节均可在官方仓库与文档中核实。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐