DeepSeek Harness 深度分析:用途、问题、架构原理与使用指南
项目地址:https://github.com/deepseek-ai/deepseek-harness
分析日期:2026-08-15
分析口径:官方仓库
master分支、官方 README、架构文档、核心包说明与用户指南。仓库当前处于 Developer Preview ,本文观察到根包版本为0.1.0-rc.5;命令、配置和接口后续可能发生破坏性变化。
一句话结论
DeepSeek Harness(命令名 dsh)不是一个简单的 DeepSeek API SDK,也不只是聊天网页。它是一个面向软件工程任务的、可组合的 AI Agent 运行时与宿主框架:负责把模型、提示词、工具、权限、沙箱、会话、持久化、子 Agent、任务、Web UI 等能力装配成一个能够安全执行真实操作的智能体。
它最有辨识度的设计不是"能调用 DeepSeek",而是 Everything is a Plugin(一切皆插件):模型适配器、Agent 循环、工具注册表、会话日志、文件系统、Shell、审批策略和 UI 都是 Cordis 插件树中的服务,可以通过配置组合、替换、拦截和卸载。
因此,可以把它理解为:
text
DeepSeek 模型 = 大脑
工具(Bash、编辑器、搜索等)= 手脚
DeepSeek Harness = 神经系统 + 工作记忆 + 权限系统 + 运行容器 + UI
1. 它是做什么用的
DeepSeek Harness 的直接用途是让大模型成为能够在代码工作区内持续工作的 Agent,而不只是完成一次问答。官方 Web 模式中,Agent 可以读取和编辑文件、执行命令、维护计划、委派工作,并在权限策略要求时向用户申请批准。
它提供多种使用面,但底层复用同一个插件化运行时:
| 使用面 | 适用场景 | 入口 |
|---|---|---|
| Web UI | 人机交互式编码、查看流式输出和工具调用 | npx @deepseek-ai/dsh web |
| Headless CLI | CI、脚本、一次性无人值守任务 | dsh --profile headless "任务" |
| Python SDK | 把 Harness 嵌入 Python 服务或自动化程序 | deepseek-harness-sdk |
| 源码/插件开发 | 自定义模型、工具、策略、持久化或 UI | Cordis 插件与 Profile |
尽管名称中有 DeepSeek,它并不被限定为只能使用 DeepSeek。Web 配置支持官方 DeepSeek,也可以添加 OpenAI、Anthropic 等目录型 Provider,以及企业网关、自托管模型等 OpenAI-compatible 自定义 Provider。DeepSeek 官方适配器只是 LLM 能力缝隙的一种实现。
2. 它解决什么问题
2.1 从"调用模型"到"可靠执行任务"的鸿沟
普通 SDK 主要解决 HTTP 请求:发送 messages,接收 completion。真正的 Agent 还要处理:
- 模型多轮调用与工具调用循环;
- 文件、Shell、搜索等能力的注册和参数描述;
- 工具执行前审批、执行中超时、执行后结果规范化;
- 并行工具调用、取消、重试、上下文压缩;
- 会话恢复、分叉、崩溃修复、审计与 UI 回放;
- 不同模型 Provider 的流式协议和 reasoning 差异;
- 不同宿主(Web、CLI、Python)之间复用同一套行为。
DeepSeek Harness 把这些横切问题放进运行时,让业务侧主要关注"装配哪些能力"和"给 Agent 什么任务"。
2.2 单体 Agent 框架难扩展、难替换的问题
很多 Agent 实现把模型调用、工具、存储、安全策略和循环写死在同一套核心代码中。新增 Provider 或替换本地文件系统时,经常需要修改主循环,最终形成大量条件分支。
Harness 将每项能力拆成"服务接口---Provider 实现---Consumer 使用者":
ctx.llm:模型适配器注册表;ctx.tools:工具注册与受控执行流水线;ctx.sessions:会话及事件日志;ctx.fs、ctx.shell、ctx.subprocess:执行环境;ctx.sandbox、ctx.approval:安全和人机确认;ctx.jobs、ctx.subagents:后台任务与子 Agent;ctx.settings、ctx.credentials:动态配置和密钥。
消费者依赖稳定的 ctx.<key>,而不是直接导入具体实现。替换 Provider 就能让所有消费者切换能力,不必分别修改 Bash、PTY、LSP 或 Agent Loop。
2.3 长任务的状态一致性、恢复与审计问题
Agent 的真实状态不只是聊天消息,还包括 turn/step 边界、流式 chunk、工具调用、工具结果、重试、压缩和取消。只保存最终 messages 会丢失执行事实,也难以判断中断的副作用是否已经发生。
Harness 使用 append-only 的事件溯源会话日志作为事实源,再从日志投影出发给模型的消息历史。这带来几项关键能力:
- 可以从日志重建模型实际看到的上下文;
- 可以恢复会话、分叉会话和回放 UI;
- 崩溃后可区分"工具尚未启动"和"工具结果未知",避免盲目重试有副作用的操作;
- 压缩可以替换模型可见投影,但不删除原始事件,因此兼顾上下文控制和审计;
- 请求头记录 Provider、模型、系统提示词、工具 schema 和有效参数,使请求可重建。
这套设计的核心约束是:凡是模型可见的内容,都必须能从持久日志中重建。
2.4 工具副作用与权限安全问题
Agent 的风险主要不在生成文本,而在执行写文件、Shell、网络或外部系统操作。Harness 把工具执行设计成多阶段管线:
text
模型生成 tool call
→ 记录 tool/call
→ tools/pre-execute(策略、权限、沙箱)
→ 单调 guard(只允许拒绝或不表态,不能推翻已有拒绝)
→ approval(必要时一次性人工确认)
→ tools/execute(超时、重试、指标等 around middleware)
→ 工具本体
→ tools/post-execute(接受、阻止、替换或补充上下文)
→ finalizeContent
→ tools/result
→ 记录唯一的模型可见 tool/result
"策略在循环外以插件方式挂载"使权限、审计、沙箱和 UI 无需侵入主循环。默认新会话采用 workspace-write:写操作被限制在会话工作区和平台临时目录;但官方文档明确说明,读访问、网络访问和进程可见性并没有因此被完全隔离。因此它是权限控制基础设施,不应被误认为完整的强隔离安全容器。
2.5 DeepSeek 协议细节与多 Provider 差异
官方 dsh-llm-deepseek 适配器直接使用 fetch + SSE,把 DeepSeek Chat Completions 协议转换为 Harness 的统一 StreamChunk 协议。它处理了多项不应泄漏到 Agent Loop 的 Provider 细节:
- reasoning effort 的
off/high/max与 wire format 映射; - reasoning 模式下,含 tool calls 的 assistant 消息要回传
reasoning_content; - usage 可能位于结束 chunk 或额外的 usage-only chunk;
- 空 reasoning 首 chunk、异常 finish reason、流未收到
[DONE]; - Provider 错误归一化为稳定错误码,例如
AUTH、QUOTA、RATE_LIMIT、CONTEXT_WINDOW_EXCEEDED; - DeepSeek prompt cache 命中 token 的统一计量;
- 超时、取消和重试边界。
这体现了它的分层原则:Agent Loop 只理解统一 LLM 语义,协议兼容性由适配器负责。
3. 总体架构
#mermaid-svg-tklLHH2q0Hgo9Q4x{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tklLHH2q0Hgo9Q4x .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tklLHH2q0Hgo9Q4x .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tklLHH2q0Hgo9Q4x .error-icon{fill:#552222;}#mermaid-svg-tklLHH2q0Hgo9Q4x .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tklLHH2q0Hgo9Q4x .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tklLHH2q0Hgo9Q4x .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tklLHH2q0Hgo9Q4x .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tklLHH2q0Hgo9Q4x .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tklLHH2q0Hgo9Q4x .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tklLHH2q0Hgo9Q4x .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tklLHH2q0Hgo9Q4x .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tklLHH2q0Hgo9Q4x .marker.cross{stroke:#333333;}#mermaid-svg-tklLHH2q0Hgo9Q4x svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tklLHH2q0Hgo9Q4x p{margin:0;}#mermaid-svg-tklLHH2q0Hgo9Q4x .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tklLHH2q0Hgo9Q4x .cluster-label text{fill:#333;}#mermaid-svg-tklLHH2q0Hgo9Q4x .cluster-label span{color:#333;}#mermaid-svg-tklLHH2q0Hgo9Q4x .cluster-label span p{background-color:transparent;}#mermaid-svg-tklLHH2q0Hgo9Q4x .label text,#mermaid-svg-tklLHH2q0Hgo9Q4x span{fill:#333;color:#333;}#mermaid-svg-tklLHH2q0Hgo9Q4x .node rect,#mermaid-svg-tklLHH2q0Hgo9Q4x .node circle,#mermaid-svg-tklLHH2q0Hgo9Q4x .node ellipse,#mermaid-svg-tklLHH2q0Hgo9Q4x .node polygon,#mermaid-svg-tklLHH2q0Hgo9Q4x .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tklLHH2q0Hgo9Q4x .rough-node .label text,#mermaid-svg-tklLHH2q0Hgo9Q4x .node .label text,#mermaid-svg-tklLHH2q0Hgo9Q4x .image-shape .label,#mermaid-svg-tklLHH2q0Hgo9Q4x .icon-shape .label{text-anchor:middle;}#mermaid-svg-tklLHH2q0Hgo9Q4x .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tklLHH2q0Hgo9Q4x .rough-node .label,#mermaid-svg-tklLHH2q0Hgo9Q4x .node .label,#mermaid-svg-tklLHH2q0Hgo9Q4x .image-shape .label,#mermaid-svg-tklLHH2q0Hgo9Q4x .icon-shape .label{text-align:center;}#mermaid-svg-tklLHH2q0Hgo9Q4x .node.clickable{cursor:pointer;}#mermaid-svg-tklLHH2q0Hgo9Q4x .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tklLHH2q0Hgo9Q4x .arrowheadPath{fill:#333333;}#mermaid-svg-tklLHH2q0Hgo9Q4x .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tklLHH2q0Hgo9Q4x .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tklLHH2q0Hgo9Q4x .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tklLHH2q0Hgo9Q4x .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tklLHH2q0Hgo9Q4x .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tklLHH2q0Hgo9Q4x .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tklLHH2q0Hgo9Q4x .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tklLHH2q0Hgo9Q4x .cluster text{fill:#333;}#mermaid-svg-tklLHH2q0Hgo9Q4x .cluster span{color:#333;}#mermaid-svg-tklLHH2q0Hgo9Q4x div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tklLHH2q0Hgo9Q4x .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tklLHH2q0Hgo9Q4x rect.text{fill:none;stroke-width:0;}#mermaid-svg-tklLHH2q0Hgo9Q4x .icon-shape,#mermaid-svg-tklLHH2q0Hgo9Q4x .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tklLHH2q0Hgo9Q4x .icon-shape p,#mermaid-svg-tklLHH2q0Hgo9Q4x .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tklLHH2q0Hgo9Q4x .icon-shape .label rect,#mermaid-svg-tklLHH2q0Hgo9Q4x .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tklLHH2q0Hgo9Q4x .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tklLHH2q0Hgo9Q4x .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tklLHH2q0Hgo9Q4x :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Agent 核心脊柱
可替换能力
LLM Adapters
FS / Shell / Subprocess
Sandbox / Permission / Approval
JSONL / SQLite Persistence
Jobs / Subagents / Compaction
Cordis 插件运行时
共享 Context / 服务容器
类型化事件与 Waterfall
可逆 Effect / 生命周期
组合与启动层
Profile
Bundles
profile/home/--patch 覆盖层
App Boot + Cordis Loader
交互与集成面
Web UI
Headless CLI
Python SDK / JSON-RPC
可扩展 UI / ACP 等
用户或业务系统
Agent Registry
Agent Loop
System Prompt Assembly
Tool Registry + Pipeline
Event-sourced Session
3.1 Profile、Bundle 与 Patch:声明式组装系统
一个运行中的 dsh 是启动时组合出的插件树,而不是固定程序。
- Profile :一套具名运行配置,位于
$DSH_HOME/profiles/<name>,声明按顺序叠加的 bundles,并容纳用户安装的外部插件和本 Profile 的cordis.patch.yml。 - Bundle :插件和 Cordis 配置行的发行单元。官方内置
dsh-base、dsh-web-app、dsh-headless。 - Patch:按行 ID 替换配置或插入新行的覆盖层。
有效配置按以下顺序形成,越靠后优先级越高:
text
空配置
→ Profile 中声明的各 Bundle(按顺序)
→ Profile 的 cordis.patch.yml
→ $DSH_HOME/cordis.patch.yml
→ 命令行 --patch 覆盖层(按出现顺序)
重要细节:patch 命中某一行时会替换该行的整个 config,而不是递归深合并。所以定制配置时要保留原来仍需要的键和 !!js 表达式,否则可能无意中删除动态参数绑定。
可以用下面的命令检查实际插件树,而不启动应用:
bash
npx @deepseek-ai/dsh --profile web --dump-default-config
npx @deepseek-ai/dsh --profile web --dump-config
npx @deepseek-ai/dsh --profile web --patch ./extra.yml --dump-config
3.2 Cordis:时空可组合的插件内核
Cordis 是 Harness 底层的插件框架。理解它可以抓住五个概念:
- Plugin :函数或 Service 子类,通过
apply(ctx)挂载到当前上下文。 - Context :服务容器,通过
ctx.llm、ctx.tools等稳定键查找能力。 - Inject:声明依赖;依赖未就绪时插件等待,无需手工控制启动顺序。
- Typed Events :插件通过声明合并扩展事件类型,使用
emit、waterfall、parallel、serial等不同语义通信。 - Reversible Effects:服务、工具、Prompt section 和监听器都以 effect 注册;插件卸载时自动反向清理,避免热重载后的残留状态。
所谓"时空可组合":
- 空间维度:不同插件在同一 Context 上贡献服务和行为;Agent 还可拥有自己的 scoped context,实现每个 Agent 不同的工具和人格。
- 时间维度:插件有明确的挂载、依赖就绪、重载和卸载生命周期,注册效果可以撤销。
这种架构的代价是学习曲线较高:开发者不仅要理解普通依赖注入,还要理解事件 dispatch mode、scope/fiber 生命周期、配置行和 effect 回滚。
3.3 Agent Loop:极薄的 ReAct 驱动器
官方强调,agent-loop 是唯一包含具体循环逻辑的包,其他行为应通过插件接入,而不是继续膨胀主循环。
核心执行语义是:
- Step:一次模型请求,加上它触发的一组工具调用;
- Turn:从接纳一次用户输入开始,由零到多个 Step 组成;当不再欠模型请求或工具结果时结束。
Tool Pipeline LLM Adapter Prompt & Tool Assembly Session Log Agent Loop 用户/调用方 Tool Pipeline LLM Adapter Prompt & Tool Assembly Session Log Agent Loop 用户/调用方 #mermaid-svg-b0S6Jk39emoX1q42{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-b0S6Jk39emoX1q42 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-b0S6Jk39emoX1q42 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-b0S6Jk39emoX1q42 .error-icon{fill:#552222;}#mermaid-svg-b0S6Jk39emoX1q42 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-b0S6Jk39emoX1q42 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-b0S6Jk39emoX1q42 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-b0S6Jk39emoX1q42 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-b0S6Jk39emoX1q42 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-b0S6Jk39emoX1q42 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-b0S6Jk39emoX1q42 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-b0S6Jk39emoX1q42 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-b0S6Jk39emoX1q42 .marker.cross{stroke:#333333;}#mermaid-svg-b0S6Jk39emoX1q42 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-b0S6Jk39emoX1q42 p{margin:0;}#mermaid-svg-b0S6Jk39emoX1q42 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-b0S6Jk39emoX1q42 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-b0S6Jk39emoX1q42 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-b0S6Jk39emoX1q42 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-b0S6Jk39emoX1q42 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-b0S6Jk39emoX1q42 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-b0S6Jk39emoX1q42 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-b0S6Jk39emoX1q42 .sequenceNumber{fill:white;}#mermaid-svg-b0S6Jk39emoX1q42 #sequencenumber{fill:#333;}#mermaid-svg-b0S6Jk39emoX1q42 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-b0S6Jk39emoX1q42 .messageText{fill:#333;stroke:none;}#mermaid-svg-b0S6Jk39emoX1q42 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-b0S6Jk39emoX1q42 .labelText,#mermaid-svg-b0S6Jk39emoX1q42 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-b0S6Jk39emoX1q42 .loopText,#mermaid-svg-b0S6Jk39emoX1q42 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-b0S6Jk39emoX1q42 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-b0S6Jk39emoX1q42 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-b0S6Jk39emoX1q42 .noteText,#mermaid-svg-b0S6Jk39emoX1q42 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-b0S6Jk39emoX1q42 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-b0S6Jk39emoX1q42 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-b0S6Jk39emoX1q42 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-b0S6Jk39emoX1q42 .actorPopupMenu{position:absolute;}#mermaid-svg-b0S6Jk39emoX1q42 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-b0S6Jk39emoX1q42 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-b0S6Jk39emoX1q42 .actor-man circle,#mermaid-svg-b0S6Jk39emoX1q42 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-b0S6Jk39emoX1q42 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt 模型请求工具 followup / steer turn/start claim inbox 组装 system prompt、history、tool schemas step/start + user/message + request/header llm/stream assistant/chunk * assistant/message tool/call * 策略 → guard → approval → execute → post tool/result * 从日志重建下一步上下文 下一次模型请求 step/end turn/end
输入箱还区分三种语义:
followup():进入下一 Turn 的队列并唤醒 Agent;steer():进入下一 Step 的输入箱并立即唤醒,用于改变进行中的工作;inject():注入下一 Step,但不主动唤醒,适合补充上下文。
并行工具调用采用有界 rolling pool,默认最大并行数为 10;标记为 exclusive 的调用形成屏障。取消会停止新调用、等待已启动调用收敛,并为未分派调用生成可重放的错误结果。框架没有内置的 Turn 次数预算,部署方若要避免无限工具循环,需要在生命周期事件上添加策略插件。
3.4 Session:事件溯源而非 messages 数组
Session 是最值得借鉴的设计之一。它保存不可变的追加事件,例如:
text
turn/start
step/start
user/message
request/header
assistant/chunk ...
assistant/message
tool/call
tool/result
step/end
turn/end
deriveMessages() 从事件日志的 surface 投影生成下一次模型请求所需的消息。原始 chunk、生命周期边界、usage 等只用于回放与审计,不会重复进入模型上下文。
上下文压缩并不物理删除历史,而是追加一个 replacement 事件,使旧节点在模型可见的 surface 中被遮蔽。这样同时满足:
- 模型上下文长度可控;
- 原始日志仍然完整;
- UI、人类 transcript 和模型投影可以选择不同视图;
- 可以准确分析 KV Cache 从哪个位置开始失效。
持久化也不是 Session Core 写死的功能。JSONL、SQLite 等后端监听 session/event 异步写入,并在 session/flush 处提供明确的耐久性屏障。这符合"接口稳定、实现可换"的整体架构。
3.5 LLM 适配层
ctx.llm 提供统一的消息、流式 chunk、模型信息和错误词汇。具体 Provider 插件把外部协议适配进来。
官方 DeepSeek 路由名为 deepseek-official,默认公开 deepseek-v4-flash 和 deepseek-v4-pro。模型列表是给 UI/客户端使用的建议目录;未列出的模型 ID 仍可以透传。连接参数和 credentials 会在每次操作开始时重新解析,因此下一次请求即可使用新配置,无需重启;一个已经开始的流仍使用启动时的配置快照。
设计上还有两个合理的边界:
- Adapter 每次
stream()只发起一次 Provider 请求;重试由独立的llm-retry插件在可持久化的 Step 边界执行。 - Agent Loop 在
request/header中记录实际生效的模型、参数和适配器默认值,保证恢复后能区分"用户显式参数"和"适配器当时补齐的默认值"。
3.6 Host/Client 与 Web UI
仓库的 TypeScript 工程被分成 Host 和 Client 两个聚合构建:
- Host 运行 Agent、工具、文件系统、持久化、Provider 等可信后端能力;
- Client 运行浏览器 UI;
- Host 通过带类型信息的 Remote/API Gateway 向 Client 暴露业务服务;
- Web UI 主要消费 session events 渲染流式消息和工具卡片,同时通过 agent events 发控制指令。
这意味着 UI 不是另起一套 Agent 实现,而是 Agent 状态机的事件投影。换 UI 不需要复制会话和工具逻辑。
4. 目录与模块职责
仓库是 pnpm monorepo,顶层主要包括:
| 路径 | 作用 |
|---|---|
apps/ |
CLI 与 Web 等产品入口 |
packages/core/ |
Agent、Agent Loop、Session、Tools、Prompt、Scope 等核心服务 |
packages/llm/ |
通用 LLM seam、DeepSeek 和其他 Provider 适配器、重试/回放 |
packages/boot/ |
Cordis 配置加载、启动、环境与 patch 处理 |
packages/client/、packages/host/ |
浏览器端和宿主端能力 |
packages/* |
文件系统、沙箱、权限、工具、任务、子 Agent、持久化等细粒度插件 |
python/ |
Python SDK 与打包运行时 |
native/ |
原生安全/运行辅助组件,例如 Linux Landlock 相关实现 |
examples/ |
JSON-RPC Agent、ACP 等可运行组合 |
docs/ |
架构、子系统、用户、插件与开发文档 |
vendor/ |
仓库内维护的 Cordis 等依赖 |
"包很多"不是偶然的工程膨胀,而是其插件边界的直接体现。但这也会增加构建图、版本发布、类型声明合并和文档同步的复杂度。
5. 使用方式
5.1 最快方式:启动 Web UI
前置条件:Node.js 22.19+ 或 24+。官方根 package.json 当前声明 ^22.19.0 || >=24.0.0。
在准备交给 Agent 操作的项目目录执行:
bash
cd /absolute/path/to/your-project
npx @deepseek-ai/dsh web
默认打开:
text
http://127.0.0.1:3080
然后:
- 打开 Settings → Models;
- 在 DeepSeek 配置卡中填写 API Key 并保存;
- 选择 Choose workspace,添加并选中当前项目目录;
- 新建 Session,输入任务,例如"分析该仓库并修复失败测试";
- 对权限策略要求确认的操作,在 UI 中审核后批准或拒绝。
密钥是 write-only:UI 保存后只读取脱敏描述,不会取回明文;密钥保存在 $DSH_HOME/.credentials.yaml,settings 只保存 credential reference。
自定义端口:
bash
npx @deepseek-ai/dsh web --port 8080
当前 CLI 不支持直接绑定 --host 0.0.0.0。这有助于避免用户无意中把带本地操作能力的 Agent 服务暴露到公网。若确需远程部署,应先审计官方的 trusted-host、反向代理、鉴权和网络边界方案,而不是简单开放监听地址。
5.2 Headless 一次性任务
适合脚本或 CI:
bash
export DEEPSEEK_API_KEY='sk-...'
npx @deepseek-ai/dsh --profile headless "运行测试并解释所有失败原因"
Headless 模式创建一个新的持久化 Session,等待 Agent 静止,flush 日志,输出最后一条非空 assistant 文本并退出。成功完成时退出码为 0,其他 Turn 结束原因返回 1;它不启动 HTTP Server 或浏览器端。
5.3 从源码运行与开发
bash
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
corepack enable
pnpm install
pnpm run typecheck
pnpm run build
pnpm dsh web
开发要求包括 Git 2.26+、Node.js 22.19+/24+,仓库固定使用 pnpm@11.7.0。首次安装还会配置 worktree-local Lefthook 和翻译文件 merge driver。
常见质量命令:
bash
pnpm run typecheck
pnpm run lint
pnpm test
pnpm run check:all
构建顺序不是普通单一 TypeScript 项目:先 Host tsc + tsdown,再 Client tsc + tsdown,最后构建 Web。Host 与 Client 分开是因为双方会用不同实现声明合并同名 Cordis Context 服务;强行塞进一个 TypeScript Program 会产生类型冲突。
5.4 Python SDK
前置条件:Python 3.10+;官方发布的 bundled runtime 不要求系统另装 Node.js。当前支持 Linux x64、Linux arm64,以及 macOS 14+ arm64。
bash
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
python -m venv .venv
. .venv/bin/activate
python -m pip install deepseek-harness-sdk
export DEEPSEEK_API_KEY='sk-...'
python examples/jsonrpc-agent/minimal.py \
--workspace /absolute/path/to/workspace \
--session-root /absolute/path/to/sessions \
--session-id example-001 \
"检查仓库并修复失败测试"
在代码中调用:
python
from pathlib import Path
from deepseek_harness import DeepSeekHarness
config = Path("examples/jsonrpc-agent/minimal.cordis.yml").resolve()
workspace = Path("/absolute/path/to/workspace").resolve()
sessions = Path("/absolute/path/to/sessions").resolve()
with DeepSeekHarness(
provider="deepseek-official",
model="deepseek-v4-flash",
max_tokens=49_152,
cwd=str(workspace),
session_root=str(sessions),
cordis=str(config),
) as harness:
result = harness.run(
"检查仓库并修复失败测试",
session_id="example-001",
)
print(result.final_response)
复用同一个 Harness 和 session_id 会延续持久会话以及 session-owned Bash 进程状态,包括当前目录、环境变量和 shell functions。独立任务应使用新的 session ID。
需要特别注意:官方 minimal.cordis.yml 示例使用 danger-full-access,且 bare local filesystem 能访问运行进程可见的绝对路径。它适合一次性容器或可丢弃 checkout,不应直接用于包含敏感数据的主机环境。生产嵌入应改用更严格的 sandbox/permission profile。
5.5 配置 DeepSeek、自托管服务或企业网关
Web UI 可直接保存 DeepSeek Key。环境变量方式常用:
bash
export DEEPSEEK_API_KEY='sk-...'
export DEEPSEEK_BASE_URL='https://api.deepseek.com'
DeepSeek Adapter 的 Cordis 配置示意:
yaml
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
config:
apiKeyEnv: DEEPSEEK_API_KEY
baseURL: https://api.deepseek.com
thinking: enabled
reasoningEffort: high
maxTokens: 256000
streamIdleTimeoutMs: 300000
defaultContextWindow: 1000000
models:
- id: deepseek-v4-flash
name: DeepSeek-V4-Flash
- id: deepseek-v4-pro
name: DeepSeek-V4-Pro
也可以在 Settings 中添加 custom provider,填写:
- 永久的、小写 Provider ID;
- Base URL;
- API protocol;
- Credential;
- 至少一个 model。
自定义 Provider ID 会进入请求、已保存 Session、默认模型和 credential reference,因此不能原地改名;正确做法是新增 Provider,再删除旧 Provider。
5.6 安装外部插件和自定义 Profile
插件管理实际转发给 Profile 目录中的 pnpm:
bash
npx @deepseek-ai/dsh plugin --profile my-agent add <package-or-git-spec>
npx @deepseek-ai/dsh plugin --profile my-agent remove <package>
npx @deepseek-ai/dsh --profile my-agent
官方示例:
bash
dsh plugin --profile tui add github:deepseek-harness/turtle-ui
dsh --profile tui
如果 npm 包在 package.json 中声明 dsh.bundle.patch,安装后会自动进入该 Profile 的 bundle layer;普通插件依赖则需由 patch 配置显式挂载。
6. 如何扩展:选对扩展点
| 目标 | 推荐扩展方式 |
|---|---|
| 新增模型 Provider | 在 ctx.llm 注册 Adapter |
| 新增模型可调用工具 | 在 ctx.tools 注册定义和 schema |
| 每个 Agent 使用不同能力 | 使用 agent-scoped context / preset |
| 替换文件系统或远程执行环境 | 实现 ctx.fs、ctx.subprocess、ctx.shell Provider |
| 添加审批、审计或策略 | 监听 tools/pre-execute、tools/post-execute、tools/result |
| 添加重试或请求改写 | 监听 agent/request-error 或 agent/request |
| 添加上下文压缩 | 在 agent/pre-step 处理压力,并写入 surface replacement |
| 添加持久化 | 监听 session/event,在 session/flush 完成耐久写入 |
| 添加后台任务 | 注册 ctx.jobs,再提供模型工具 |
| 添加子 Agent | 实现 ctx.subagents;进程内实现可复用 ctx.agents.create() |
| 添加 Web 交互节点 | 注册 ConversationNodeDefinition 和对应 Renderer |
| 添加人类 CLI 命令 | 注册 ctx.commands,无需触发模型 Turn |
插件开发的基本原则是:如果新行为能挂在现有事件或能力 seam 上,就不要修改 Agent Loop。只有核心语义确实改变时,才应修改循环和事件图。
7. 技术优点与代价
优点
- 高度可替换:模型、工具、存储、沙箱、UI 都不是特权核心。
- 可审计和可恢复:事件日志保留执行事实,messages 只是派生视图。
- 协议边界清晰:Provider 特殊行为留在 Adapter,Loop 面向统一语义。
- 安全策略可组合:权限、approval、sandbox、guard 和结果后处理拥有独立扩展点。
- 多宿主复用:Web、Headless 与 Python SDK 使用相同运行时组合。
- 生命周期严谨:注册是可逆 effect,插件卸载会清理监听器和服务,降低 HMR/动态配置的状态泄漏。
- 并发和取消语义明确:并行工具有界,exclusive 调用有屏障,取消会收敛已启动工作并记录可恢复状态。
代价与局限
- 开发者预览:官方明确承诺之前仍会有破坏性变化,现阶段不适合对内部 API 做重度长期绑定。
- 学习成本高:Cordis Context、fiber、effect、typed events、waterfall、Profile/Bundle/Patch 都是额外概念。
- 包与构建复杂:细粒度插件带来庞大的 monorepo、Host/Client 双构建和严格的依赖图治理。
- 默认权限不等于强隔离 :
workspace-write限制部分写操作,但不完全隔离读取、网络和进程可见性;Python minimal 示例甚至是danger-full-access。 - 没有内置 Turn 预算:恶性工具循环需部署方添加策略。
- DeepSeek Adapter 仍有 MVP 限制 :例如当前未映射
tool_choice,用户和工具结果内容会被扁平化为文本,原始 fetch 也没有统一 HTTP 代理插件。 - 配置覆盖容易踩坑:Patch 替换整行 config,数组字段也是整体替换;不了解优先级时容易丢失默认项。
- 运行成本与 KV Cache 需要治理:每个 Step 都会重发系统 Prompt、工具 schema 与累积历史;Prompt/schema 变化或上下文 replacement 会从变化点破坏缓存复用。
8. 与常见框架的定位差异
| 对比对象 | 核心差异 |
|---|---|
| OpenAI/DeepSeek SDK | SDK 负责协议调用;Harness 负责完整 Agent 生命周期、工具、安全与持久化 |
| 简单 ReAct Demo | Demo 往往是 while-loop;Harness 将每个检查点事件化、持久化并可插拔 |
| LangChain/LlamaIndex | 后两者更偏应用编排与数据/RAG 生态;Harness 更像可运行、可治理、可换宿主的 Agent 操作系统内核 |
| Claude Code/Codex 等成品 CLI | 成品工具强调开箱体验;Harness 同时把底层组合与扩展机制作为产品能力开放 |
| MCP Server | MCP 是向宿主暴露工具/资源的协议;Harness 是宿主和 Agent Runtime,本身还可以挂载 MCP Client 插件 |
因此,Harness 最适合以下团队:
- 想构建自有编码 Agent,而不是只嵌入一次 completion;
- 需要把模型、权限、工具、审计、会话恢复做成平台能力;
- 希望同一 Agent Core 同时服务 Web、CLI 和 Python 自动化;
- 需要企业网关、自托管模型、远程沙箱或自定义持久化;
- 愿意接受开发者预览期的快速变化和较高架构学习成本。
如果需求只是"调用 DeepSeek 完成一次文本生成",直接使用官方 API/SDK 会更轻量;如果需求是"让模型在真实环境中连续工作,并且这套系统可扩展、可审计、可控制",Harness 才真正体现价值。
9. 推荐的落地路线
阶段一:体验和验证
- 在一个无敏感信息、可恢复的测试仓库中运行 Web UI;
- 配置 DeepSeek Key,验证读文件、运行测试、修改文件和审批流程;
- 使用
--dump-config理解实际 Profile; - 查看 session 日志,确认它是否满足团队的审计要求。
阶段二:最小生产化
- 固定经过验证的版本,不跟随 latest 自动升级;
- 明确 workspace、临时目录、网络、子进程和凭据边界;
- 保持 telemetry 关闭,或先实现脱敏后再开启;
- 配置最大输出 token、上下文压缩、重试和超时;
- 增加 Turn/成本预算策略;
- 对有副作用的工具使用 approval,并确保崩溃恢复不盲目重试;
- 将外部 MCP Server 视为可信可执行代码进行供应链审计。
阶段三:平台化扩展
- 把企业模型网关做成 LLM Adapter 或 custom provider;
- 将远程容器/Kubernetes workspace 实现为 FS/Subprocess/Sandbox Provider;
- 添加组织级权限、审计和数据防泄漏插件;
- 选用 JSONL/SQLite 或实现企业持久化 Provider;
- 使用 Profile 区分开发、CI、只读审计和高权限运维 Agent;
- 通过同一事件流驱动 Web、IDE 或内部工作台。
10. 最终判断
DeepSeek Harness 的核心创新不在某一个模型调用技巧,而在于把 Agent 工程问题重新定义为 插件组合、事件事实和能力边界:
- 通过 Cordis Context 解耦"谁提供能力"和"谁消费能力";
- 通过 typed events/waterfalls 让策略可以观察、改写或短路流程;
- 通过 reversible effects 管理动态插件生命周期;
- 通过事件溯源 Session 统一模型上下文、恢复、回放和审计;
- 通过 Profile/Bundle/Patch 将同一内核组装成 Web、Headless 或定制 Agent;
- 通过 LLM Adapter 吸收 DeepSeek 和其他 Provider 的协议差异;
- 通过 Tool Pipeline 将副作用执行放在权限、沙箱和审批之后。
从架构成熟度看,它已经展示出平台级 Agent Runtime 的系统性设计;从产品成熟度看,它仍是 RC 阶段的开发者预览项目。现阶段最合理的态度是:适合研究、试点和构建可替换的 Agent 基础设施,但生产使用必须固定版本、收紧权限、补齐预算与隔离策略,并预留升级适配成本。
参考资料
以下均为项目官方一手资料:
- DeepSeek Harness 官方仓库与 README
- 官方中文 README
- Architecture
- Cordis Primer
- Development Guide
- Web UI Guide
- Model Provider Configuration
- Python SDK Guide
dshCLI Reference- Agent Loop Package
- Event-sourced Session Package
- DeepSeek LLM Adapter
- Tool Execution Pipeline
- Capability Seams
- Python SDK Minimal Composition