DeepSeek Harness 深度分析:用途、问题、架构原理与使用指南

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.fsctx.shellctx.subprocess:执行环境;
  • ctx.sandboxctx.approval:安全和人机确认;
  • ctx.jobsctx.subagents:后台任务与子 Agent;
  • ctx.settingsctx.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 错误归一化为稳定错误码,例如 AUTHQUOTARATE_LIMITCONTEXT_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-basedsh-web-appdsh-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 底层的插件框架。理解它可以抓住五个概念:

  1. Plugin :函数或 Service 子类,通过 apply(ctx) 挂载到当前上下文。
  2. Context :服务容器,通过 ctx.llmctx.tools 等稳定键查找能力。
  3. Inject:声明依赖;依赖未就绪时插件等待,无需手工控制启动顺序。
  4. Typed Events :插件通过声明合并扩展事件类型,使用 emitwaterfallparallelserial 等不同语义通信。
  5. 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-flashdeepseek-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

然后:

  1. 打开 Settings → Models
  2. 在 DeepSeek 配置卡中填写 API Key 并保存;
  3. 选择 Choose workspace,添加并选中当前项目目录;
  4. 新建 Session,输入任务,例如"分析该仓库并修复失败测试";
  5. 对权限策略要求确认的操作,在 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.fsctx.subprocessctx.shell Provider
添加审批、审计或策略 监听 tools/pre-executetools/post-executetools/result
添加重试或请求改写 监听 agent/request-erroragent/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. 技术优点与代价

优点

  1. 高度可替换:模型、工具、存储、沙箱、UI 都不是特权核心。
  2. 可审计和可恢复:事件日志保留执行事实,messages 只是派生视图。
  3. 协议边界清晰:Provider 特殊行为留在 Adapter,Loop 面向统一语义。
  4. 安全策略可组合:权限、approval、sandbox、guard 和结果后处理拥有独立扩展点。
  5. 多宿主复用:Web、Headless 与 Python SDK 使用相同运行时组合。
  6. 生命周期严谨:注册是可逆 effect,插件卸载会清理监听器和服务,降低 HMR/动态配置的状态泄漏。
  7. 并发和取消语义明确:并行工具有界,exclusive 调用有屏障,取消会收敛已启动工作并记录可恢复状态。

代价与局限

  1. 开发者预览:官方明确承诺之前仍会有破坏性变化,现阶段不适合对内部 API 做重度长期绑定。
  2. 学习成本高:Cordis Context、fiber、effect、typed events、waterfall、Profile/Bundle/Patch 都是额外概念。
  3. 包与构建复杂:细粒度插件带来庞大的 monorepo、Host/Client 双构建和严格的依赖图治理。
  4. 默认权限不等于强隔离workspace-write 限制部分写操作,但不完全隔离读取、网络和进程可见性;Python minimal 示例甚至是 danger-full-access
  5. 没有内置 Turn 预算:恶性工具循环需部署方添加策略。
  6. DeepSeek Adapter 仍有 MVP 限制 :例如当前未映射 tool_choice,用户和工具结果内容会被扁平化为文本,原始 fetch 也没有统一 HTTP 代理插件。
  7. 配置覆盖容易踩坑:Patch 替换整行 config,数组字段也是整体替换;不了解优先级时容易丢失默认项。
  8. 运行成本与 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. 推荐的落地路线

阶段一:体验和验证

  1. 在一个无敏感信息、可恢复的测试仓库中运行 Web UI;
  2. 配置 DeepSeek Key,验证读文件、运行测试、修改文件和审批流程;
  3. 使用 --dump-config 理解实际 Profile;
  4. 查看 session 日志,确认它是否满足团队的审计要求。

阶段二:最小生产化

  1. 固定经过验证的版本,不跟随 latest 自动升级;
  2. 明确 workspace、临时目录、网络、子进程和凭据边界;
  3. 保持 telemetry 关闭,或先实现脱敏后再开启;
  4. 配置最大输出 token、上下文压缩、重试和超时;
  5. 增加 Turn/成本预算策略;
  6. 对有副作用的工具使用 approval,并确保崩溃恢复不盲目重试;
  7. 将外部 MCP Server 视为可信可执行代码进行供应链审计。

阶段三:平台化扩展

  1. 把企业模型网关做成 LLM Adapter 或 custom provider;
  2. 将远程容器/Kubernetes workspace 实现为 FS/Subprocess/Sandbox Provider;
  3. 添加组织级权限、审计和数据防泄漏插件;
  4. 选用 JSONL/SQLite 或实现企业持久化 Provider;
  5. 使用 Profile 区分开发、CI、只读审计和高权限运维 Agent;
  6. 通过同一事件流驱动 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 基础设施,但生产使用必须固定版本、收紧权限、补齐预算与隔离策略,并预留升级适配成本。

参考资料

以下均为项目官方一手资料:

相关推荐
风流 少年1 小时前
Spring AI 2.0:Flux
java·人工智能·spring
小马过河R1 小时前
Graph Engineering 深度解析:模型越强,越需要给它画好“地图”
人工智能·langchain·graph·ai工程化·harness·驾驭工程
leisoo80971 小时前
涨停板次日表现因子怎么挖掘本地化Python全流程实战
大数据·人工智能·python
lucas_AI2 小时前
喂张白纸也能吐出证件号?文档 MLLM 的"关系级泄露"被测出来了
人工智能·算法·掘金技术征文
Old Uncle Tom2 小时前
手机银行用户画像设计
人工智能·智能手机
kyriewen2 小时前
前端切图仔被 AI 新闻淹死的第 N 天,我用 TRAE Work 定时任务救了自己
前端·人工智能·trae
手写码匠2 小时前
华为云Flexus+DeepSeek征文|Dify 多 Agent 灰度发布实战:让每一次变更都“小步快跑、随时可回滚“
人工智能·深度学习·算法·aigc
火云牌神2 小时前
分层整洁架构:标准化工程目录结构,防范 AI 越界调用
人工智能·架构·ai编程·分层架构·vibecoding
新知图书2 小时前
8.1 智能体的心跳执行模式:以定时器为核心(智能体工程)
人工智能·agent·ai agent·智能体