【DeepSeek Harness 研究】一个基于“一切皆插件“范式的 LLM Agent Harness
《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 框架形态(如单机脚本式、编排器式、微内核插件式),但普遍面临三重挑战:
- 可组合性挑战:模型适配器、工具、策略、UI 各自演进,如何让它们像积木一样自由组合而不互相破坏?
- 可观测性挑战:agent 的每一步行为(思考、调用、结果)如何被完整记录、可回放、可审计?
- 安全性挑战: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)编程范式,其三个支柱是:
- 服务(Service):插件通过稳定的
ctx.<key>向共享上下文贡献命名能力;其他插件通过 key 查找服务,而非导入具体实现——解耦了"接口"与"实现"。 - 类型化事件(Typed Events):插件通过 TypeScript 声明合并注册事件名,以
emit(广播)、waterfall(可改写流水线)、parallel(并行)、serial(顺序终结)四种模式分发。 - 可逆副作用(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.md、docs/module-graph.zh.md 的分析,dsh 的总体架构可归纳为五层模型(图 1):

图 1:DeepSeek Harness 总体架构五层模型。自上而下:应用与入口层 → 配置组装层 → 核心服务层 → 能力实现层 → 基础设施层。箭头标注层间关系。
L5 应用与入口层:面向用户的运行形态。apps/cli 提供 dsh 命令行启动器(dsh web、dsh --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),分为四层:
- 基础工具层:
invariants(唯一零依赖包,被几乎所有包依赖)、util/*、scope、typert-*、storage、subprocess。 - 核心服务/抽象 seam 层:
llm、session、system-prompt、agent、tools、fs、shell、sandbox等——被上层消费且通常有多个实现。 - 能力实现层:llm-deepseek/pi-ai/replay、bash-local/sandbox、fs-local/sandbox、persistence-jsonl/sqlite 等。
- 组合/编排层: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-shell(ctx.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.json 的 dsh 字段中声明):
- profile(配置档):
$DSH_HOME/profiles/<name>/下的目录,含package.json(dsh.profile.bundles有序列表 + 树外插件依赖)与用户自己的cordis.patch.yml。web与headless作为模板随发行版交付。 - bundle(组合包):声明
dsh.bundle的 npm 包,附带一个cordis.patch.yml补丁层。dsh-base是每个 profile 的第一层(模型适配器、工具、持久化、沙箱与审批策略、设置、凭据、遥测);dsh-web-app增加浏览器应用;dsh-headless增加无服务器一次性运行器。
4.2 配置层叠加顺序与 patch 语义
生效配置在空条目列表之上按以下顺序叠加(后应用的层按行胜出):
- profile 的
dsh.profile.bundles列表(先dsh-base,再各组合包按加入顺序); - profile 自己的
cordis.patch.yml; - home 级
$DSH_HOME/cordis.patch.yml(机器本地偏好); - 每个
--patch <path>overlay(按 argv 顺序)。
patch 语义(关键设计决策):
- 一条 patch 按 id 定位条目并替换其整个
config,而不是深度合并各键——这迫使组合包作者"重述需要保留的每个字段",避免隐式继承造成的歧义。 insert添加新条目;!!js表达式在挂载时插值(仅在插件config内求值——事故 0002 的教训,见第 9 章)。- 验证工具:
dsh --profile web --dump-config打印合成后的完整配置树(含# == 层名注释);app-boot的boot()挂载 include 树后,assertEntriesLoaded/assertEntriesActivated把"已启用但未加载/未激活"的条目转化为明确的启动失败。
4.3 启动器的角色
apps/cli 的 dsh 命令(src/args.ts + src/bin.ts)是"轻量自执行组合":它只解析自己的 flag,把其余参数交给已启动 profile 的插件解析(@deepseek-ai/dsh-cmdline)。启动流程由 @deepseek-ai/dsh-app-boot 的 boot() 统一承载:创建根上下文 → 暴露 dshHomePath() → 安装 Loader → 挂载 include 树 → 断言加载与激活 → 返回根上下文;失败时 dispose 部分构造的上下文并以带标签的错误 reject。watchUserPatches 提供 cordis.patch.yml 的热重载:文件变更时以事务方式重新组合 patch 列表,失败则保留最后一个可用树继续运行。
5 事件驱动内核与 Agent 生命周期
5.1 三个事件域:持久与实时的统一
dsh 架构文档(docs/architecture.zh.md)明确将事件划分为三个域,这是理解其事件体系的第一把钥匙:
| 事件域 | 事件示例 | 性质 | 用途 |
|---|---|---|---|
| 会话事件(持久) | turn/start、turn/end、step/start、step/end、user/message、assistant/chunk、assistant/message、tool/call、tool/result |
追加到日志,经 session/event 广播;可回放 |
需要在重新加载后仍然存在的事实 |
Agent 事件(agent/*) |
agent/pre-step、agent/request、agent/request-error、agent/turn-stopping、agent/status、agent/created、agent/disposed、agent/inbox/* |
携带活跃 Agent;实时控制与状态 | 观察或拦截进行中的工作 |
| 能力事件(seam) | fs/write-intent、fs/edit-intent、fs/observed、tools/pre-execute、tools/execute、tools/post-execute、tools/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
关键语义分析:
- 步骤与轮次的包含关系:步骤 = 一次模型请求 + 其工具执行;轮次 = 零到多个步骤。轮次在"不再欠下任何工作"时关闭(
turn/end记录TurnEndReason)。 - waterfall 把关:
agent/pre-step决定模型看到什么——监听器可改写已领取消息或拒绝;首次领取被拒/改空时仍关闭一个不含步骤的持久轮次(日志记录尝试)。agent/request、llm/stream、三个tools/*事件同为 waterfall,监听器必须调用next()委托;agent/turn-stopping是 serial 终结检查点(无next())。 - 输入通道:
followup()立即唤醒驱动器;inject()追加上下文留在 inbox 直到另一条消息唤醒;steer()中途引导当前步骤。排队消息与注入上下文在下一步骤批次中经同一 pre-step waterfall。 - 失败与恢复:适配器/终端带内请求失败 → 写
step/end→agent/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 数据(parentSession、delegationDepth、subagentDepth)表达。
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.tools,packages/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 十级把关的语义分析
流水线按顺序执行以下阶段:
- 入口:模型发出 tool-call → 写会话事件
tool/call(执行前记录)→ UI pending 卡片presentCall(args)。 tools/pre-execute(waterfall):hooks、权限策略、沙箱在此介入。结果:allow(放行)→ 守卫;deny→ 拒绝路径;ask→ 审批;throw→ 规范化。- 单调守卫(monotonic guards):deny 或 abstain,身份受保护,后续监听器无法撤销。deny → 跳过工具体(fail-closed);allow →
tools/execute。 ctx.approval一次性审批:allowed-once→ 回守卫;rejected/cancelled/unavailable→ 拒绝。应答者缺失或不可回答时默认 deny(ApprovalPolicy为'ask'默认;'never'则永不询问、确定性地拒绝,适用于 CI/无人值守)。tools/execute(waterfall 环绕分发):timeout、retry、metrics 等横切关注点包装核心分发;包装层可替换exec.signal(施加截止时间)但不可移除。- 工具体执行:
execute()body;tool-fs 的 mutation 经过fs/write-intent、fs/edit-intent守卫(先读后编辑检查,可循环回工具体);发出工具自有会话事件(todo/write、fs/observed、hook/invoked、hook/result、tool/code-dispatch)。 tools/post-execute(waterfall):accept、block、replace、add context(附加上下文)。- 注册表外层规范化:流水线/结果快照抛出的异常 →
isError(无损 JSON 快照 + 冻结)。 ToolDefinition.finalizeContent:最后的内容不变式(content-only),随快照固定。tools/result:同步通知,冻结的权威结果(frozen authoritative outcome)→ 写会话事件tool/result(单一模型可见结果)→ UI completed 卡片presentResult(args, result)。工具批次全部结算后,additionalContextsFIFO 注入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/session 与 packages/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/message、assistant/message、tool/result,产生 LLM 消息、可入有序 surface)与 41 个 log-only 事件(可持久化、可回放但不参与派生历史)。SessionEvent 信封含 type、seq(单调递增)、time、data,以及可选的 ignorable 标记(读者可安全跳过的未知类型;缺省=必需,遇未识别类型必须拒绝重建而非静默丢弃)。
7.2 持久化后端与共享写入协调器
两个第一方后端共享同一套写入协调器(PersistenceCoordinator),通过小型 PersistenceBackend 存储钩子接口组合:
- JSONL(
session-persistence-jsonl):顺序产物,支持 zstd 压缩与校验和,可打包存储行。 - SQLite(
session-persistence-sqlite):可寻址读取后缀,无需解析完整日志。
每个后端必须遵守的不变量:
- 仅追加;崩溃轮次被关闭而非截断:已 flush 事件绝不重写。崩溃可留下未关闭的最终轮次,
load保留它们并持久追加合成 closer(为每个未获回答的 assistant 调用添加带风险分类错误的tool/result,再添加step/end?+turn/end {interrupted})以平衡日志。 - 连续 seq:
load拒绝日志中间的 seq 缺口/解析错误;append的第一个 seq 必须等于已存储 next-seq。 - JSON 可序列化:
append通过共享单遍无损 JSON 边界实体化每个批次。 - 持久性:
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 生成流水线与运行时调用
构建期(严格生成流水线,按序执行):
tsc -b tsconfig.host.json→tsdown --env.DSH_BUILD_FACE host:Typert generator 以 Host aggregate 为唯一ts.Program种子运行,从类型图严格分析 Remote 签名、类型、lookup、Context 与源码位置。tsc -b tsconfig.client.json→tsdown --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 default 丢 inject |
插件导出形态与 Loader 语义不符 + 测试未走真实入口 | 删 export default;ctx.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 的完整运行时——从模型适配、工具执行、会话记忆到安全沙箱与人类审批——构建为统一的可组合、可替换、可审计的插件系统。
其最具借鉴价值的三个设计决策是:
- 无特权内核 + 可逆副作用:以"每一部分都是插件"换取极致的可组合性与热替换能力,避免了传统插件系统"内核不可替换"的妥协。
- 事件溯源会话日志 + "模型可见即已记录"不变量:把 agent 的可审计性从"事后日志"提升为"架构红线",由运行时不变式强制执行。
- seam 三角色抽象:用统一的 Definition/Provider/Consumer 结构封装全部可替换能力,使安全策略(沙箱、审批)与能力实现正交演进。
10.2 展望
基于本文的分析,dsh 的后续发展空间包括:
- 多模型路由策略深化:
llmseam 已支持多适配器,可进一步探索成本/质量感知的动态路由。 - 评测闭环:结合 [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 项目;文中所有工程细节均可在官方仓库与文档中核实。
更多推荐
所有评论(0)