让 deepseek harness 做一只电子橘猫,跟跑完全程,我看清了这 8 件事!

让 deepseek harness 做一只电子橘猫,跟跑完全程,我看清了这 8 件事!

文章集更新 · 2026-08-2411 min read
大家好!我是瓜哥。前互联网技术副总裁,现在带队死磕 AI 编程。

前两篇分别跟大家介绍了 deepseek harness 是什么?以及有哪些当下值得你安装的 dsh 插件。

文章回顾:

3 天 127k star!DeepSeek Harness 把整个 agent 拆成了 133 个 plugin,确实够彻底!

dsh 上线 3 天,我把 5966 个仓库翻了一遍,为你精选推荐这 7 个真插件!

这篇带大家一起拆开看,deepseek harness 它是 怎么跑起来的

我之前以为 dsh 是个 Python 智能体框架,看了仓库才发现,它是TypeScript 写的(23MB TS + 172KB Python),基于自研框架 Cordis,口号是:Everything is a Plugin

这是 DeepSeek 公开发布的第一个智能体运行 - deepseek harness,跑在 Node.js + pnpm 上。启动后,在浏览器打开 http://127.0.0.1:3080,就是这张图:一个对话窗口,一个侧边栏,一个设置入口。

主 UI 全景
主 UI 全景

一、dsh 鸟瞰全景图

先快速扫一眼,仓库结构里有 39 个 packages,按能力分成 core/api/typert/llm/shell/fs/lsp/skill/web/compaction/context/subagent/bundle/workflow/todo/plan/preset/guard/session/interaction/... 等等。

每个包都是一个插件,挂载到 Cordis 上下文里。

官方核心三条工作流:

这三层事件是全部真相之源:会话事件(durable)、智能体事件(live)、能力事件(capability policy)。

二、最值得你知道的 3 个特性

1. 一切皆插件

打开 dsh 的官方仓库 deepseek-ai/deepseek-harness,你会看到 README 第一句话:

DeepSeek Harness(dsh)是一个开源的 Agent 测试运行框架(Agent Harness)... 它采用 万物皆插件 的架构,并由 Cordis 提供底层驱动支持。.

Everything is a Plugin。连"模型适配器"、"工具注册表"、"会话日志"、"智能体主循环"自己都是插件,没有不能替换的核心。

这样的设计有什么好处?

你想加个新工具 /换模型 / 改持久化后端,都是写一个插件挂上去,不动 dsh 本身一行代码。

2. 日志优先,事件就是真相

Event Sourcing ,这是个工程模式,直译 事件溯源

Session.append() 是唯一的写入入口,模型能看到的所有内容都必须能从日志重建,这一条是显式的运行时不变量。

这样的设计有什么好处?

你暂停 / 中断 / 切窗口再回来,dsh 不丢任何一步,因为它是从头 replay 日志重建的,而不是从某个内存对象读出来的。

3. 随时会有破坏性更新

注意 README 里还写了一句:

后续将包含不向下兼容的重大变更.

敢这么说,是因为它底层真的允许所有东西被替换。

所以这里说的破坏性变更当下不是 bug,是 feature。

瓜哥给大家的建议是可以玩,别上生产环境。


三、跟着一句话、一只猫、一个网页、拆开来看

上周我让 dsh 做一张橘猫名片。给它一句话:

code
帮我做一张橘猫程序员名片,要会眨眼,要能点按钮投喂。

dsh 花了 2 分钟,吐出 449 行 HTML,浏览器打开能用:尾巴会摇、按一下按钮投喂成功、屏幕上飘过 5 条小鱼干。

1. 跟着一句话走全程

这次我们不先讲架构再讲实现。换个思路 —— 跟着一句话走全程

每一节都用一张真实的代码片段 + 这一刻 dsh 在背地里干了什么。

提示词那句话从进 dsh,到吐出一个能跑的 HTML,中间要经过 8 个环节:

code
1. 把这句话"记下来"                ← inbox、append-only 日志2. 把它剪成"模型能看的样子"          ← surface 层3. dsh 从"闲着"切到"在跑"          ← phase 状态机4. 发请求给模型                     ← llm.stream5. 模型回三个工具调用                ← capability seam、并行调度6. 工具结果太长 / 对话太长           ← 剪枝、压缩7. 中途想删个文件                    ← approval 弹窗8. 任务结束,整件事长什么样           ← 收尾

2. 把这句话先"记下来"

敲完回车,dsh 第一件事不是立刻发给模型。

它先把这句话写进一个小本子里

dsh 里管这个小本子的叫 session(会话)。它有个铁律:只能往后写,写完就改不了。每个事件进来按时间顺序排好,dsh 给你一个永远不会丢的历史记录

这件事的工程价值在于,哪怕你中途关掉 dsh、切窗口、切电脑,明天再回来,dsh 都能从那条记录从头 replay,把对话一字不差地重建出来。

dsh 官方文档 docs/architecture.md 把这件事拎出来明确说明:

Model-visible means logged. Anything that reaches a model request must be reconstructable from the log, and a runtime invariant asserts it.

模型能看到什么、日志里就得有什么。

每一条要发给模型的输入,理论上都能从那条日志里重新拼回来,dsh 用这段运行时代码强制守住这条线。

3. 有个问题:写的时候,可能会改主意

敲完 "做一张橘猫名片" 那一刻,dsh 在干这件事的同时,可能又敲一句 "再加个投喂的功能"。

这句话需要不等当前指令跑完,dsh 要接住它。这个能力,在 codex 里叫 “引导”。

dsh 代码里这一段在 packages/core/agent/src/inbox.ts

code
type InboxState = Record<InboxTarget, UserMessage[]>// InboxTarget = 'next-turn' | 'next-step'

Inbox 维护两个独立的待办列表

队列意思啥时候生效
next-turn"等我这一轮说完再听"当前 turn 完整结束之后
next-step"现在就插队"下一个 step(更小粒度)边界立即生效

每次插入、删除、重排,dsh 都先写日志,再改内存

code
private mutate(...): UserMessage[] {  const event = this.session.append('agent/inbox/spliced', splice)  const removed = inbox.splice(actualStart, actualDeleteCount, ...event.data.inserted)  ...}

跟 session 一样的 "日志优先" 原则,重启 dsh,从日志就能把 inbox 完整重建。

4. 插话对应的三个动作

提示词做了什么dsh 把它送进啥时候生效
"这个橘猫眼睛再大点"followup → next-turn当前轮跑完
"等下别保存,我改一下"steer → next-step下一个 step 边界
(系统偷偷加的,模型看不见用户)inject → next-step下一个 step 边界,但不唤醒

这三个动作对应三种语义:什么时候合并、是不是立刻打断、要不要吵醒模型。

5. 把 "对话" 剪成 "模型能看的样子"

提示词进了 dsh,下一步要做的事:裁剪对话

dsh 在小本子上老老实实记了每一句话、一个 tool_call、一个 tool 结果。但发给模型的不是原样,是从日志里挑出来的、当前上下文能装下的那一段

为什么?

模型有上下文窗口,聊久了,对话就装不下。

dsh 要根据当前装得下的容量,一段给模型。在表面层(Surface),模型看的不是全部,具体的代码在 packages/core/session/src/surface.ts

code
const SURFACE_EVENT_TYPES = new Set<string>([  'user/message',  'assistant/message',  'tool/result',])// 模型看的消息 = "surface" 这层视图

还有个关键,小本子上记的事件带两种标记:

  • surfaceOp: 'append':"写日记",写完就是历史,谁也改不了
  • surfaceOp: 'replace':"贴便签",贴在旧日记上面说"这事现在改成了这样

两条流同源不同形。

也就是说:用户看的是 "我们说过什么",模型看的是 "现在该让你看什么"。

从工程价值来看:dsh 可以放心地做任何改写上下文的骚操作(压缩、剪枝、改写),改不动原始日志。哪天你想看原始对话,dsh 直接给你。

这也是为什么 dsh 跑一会儿能自我总结却不会改坏你的聊天记录。

6. dsh 从 "闲着" 切到 "在跑"

指令记好了,dsh 要开始动手了。

但动手前,dsh 自己有个状态机。现在在什么状态、能不能接活、干到一半能不能被打断。

代码 packages/core/agent-loop/src/agent.ts 里只有三种状态:

code
type Phase =  | { kind: 'idle'; lastTurn: number }                        // 闲着  | { kind: 'maintenance'; abort: AbortController; ... }      // 在维护(比如压缩)  | { kind: 'running'; abort: AbortController; turn; step }   // 在跑

指令敲下回车的那一刻,dsh 从 idle 切到 running。这是状态机的转换。

三层节奏:Turn / Step / Phase

在 running 状态里,dsh 还有三个词要分清:

  • Turn(轮):用户问 + 智能体答完的一次完整来回
  • Step(步):一次模型请求 + 它调用的工具们
  • Phase(相位):智能体现在在什么状态

一个 Turn 拆成若干 Step,每个 Step 是 "问模型一次 + 跑它要的工具"。

跑起来是这样:

Trajectory 视图
Trajectory 视图

这张图是我在第一篇文章里跑 "橘猫名片" 任务留下的真实轨迹。

顶上那张彩色甘特图,三条轨道:Input(输入)/ Model(模型)/ Tools(工具) 并行跑。下面是 SYSTEM、Turn 1、USER、CONTEXT、ASSISTANT、TOOL 的完整调用链。每一次工具调用、每一次思考、每一条上下文注入,都摆在这里。

7. 发请求给模型

dsh 切到 running,第一件事:把当前对话发给模型

这一步在代码里其实就一行,调一个叫 llm.stream() 的函数。这个函数是个 适配器:你给 DeepSeek 用,给 Anthropic 用,给 OpenAI 用,调的是同一个函数。

代码 packages/core/agent-loop/src/agent.ts

code
const stream = preparedCall?.stream(request) ?? this.loopCtx.llm.stream(request)forawait (const chunk of stream) {  this.session.append('assistant/chunk', { turn, step, chunk })  assembler.push(chunk)}

注意这一行

code
this.session.append('assistant/chunk', { turn, step, chunk })

模型每吐一段,dsh 就立刻写一行进小本子

8. 模型说 "我要调三个工具"

模型收到对话,思考了一下,回了一串工具调用

code
[  { "name""read_file",  "arguments": { "path""." } },  { "name""read_file",  "arguments": { "path""./assets/cat.svg" } },  { "name""write_file""arguments": { "path""card.html""content""..." } }]

三个工具调用同时过来。dsh 怎么办?

8.1 dsh 怎么知道有什么工具可用

dsh 自己 不是一个静态的工具清单。它的工具是注册上来的。

具体体现:每个能力 = 三个角色

角色干啥的
Service Definition(接口声明)这能力有哪些方法
Service Provider(接口实现)具体怎么做
Consumer(消费者)一般是模型看得见的工具

8.2 并行调度的玄学

回到"三个工具同时来"。dsh 怎么处理?

代码 packages/core/agent-loop/src/tool-calls.ts

code
let next = 0while (next < planned.length) {  const first = planned[next]  const mode = ctx.tools.executionMode(first.exec).kind  const group = mode === 'parallel' ? planned.slice(next) : [first]  const outcome = await runGroup(ctx, turn, step, group, mode, signal, acceptContext)  next += outcome.consumed}

两个工具模式:

  • 独占(exclusive):只能一个一个跑(比如修改全局状态的工具)
  • 并行(parallel):可以一池子同时跑(比如三个 read_file)

dsh 用一个有界并行池子派发,结果按模型看到的顺序逐个 commit

"又快又对" —— 快,说的是并行能力;对,说的是顺序记录。

9. 对话太长,模型塞不下

那个任务跑了 2 分钟。一开始还好,对话一长就出现两个问题:

  • 工具结果太长:grep 整个项目、读一个大文件,单条消息挤占一大半上下文
  • 整个对话太长:聊着聊着上下文窗口快满了

dsh 有两套方案,应对不同情况。

9.1 工具结果剪枝:不调 LLM

代码 packages/compaction/compaction-tool-result-pruner/

code
// Deterministic head/middle/tail pruning for current tool-result surface nodes.exportclass ToolResultPruner extends Service {  // Replay-safe, model-free tool-result pruning.}

按字符数切,保留头尾、把中间替换成省略标记。不用花 LLM 的钱,能挡掉 80% 的超长工具结果。

9.2 对话压缩:长对话总结成短的

如果整个对话已经撑爆(或快撑爆)窗口,dsh 会做真正的压缩

代码 packages/compaction/compaction/src/index.ts

code
// 触发原因只有两种exporttype CompactionTrigger = 'pressure' | 'context-overflow'
  • pressure:快到上下文窗口了,主动预防
  • context-overflow:已经超了,紧急收拾

dsh 默认实现走 LLM 摘要(packages/compaction/compaction-basic/)。但接口是独立的,你写一个纯规则的实现换上去也行 —— 压缩策略不绑死。

9.3 为什么 dsh 能这么干?

回扣第二节 surface 那段:用户看的是原始 append,模型看的是 replace 后的视图

所以 dsh 改写上下文时,改不动用户的历史对话。聊天记录保持原样,模型该看简版就看简版。两条流同源不同形。

9.4 还有手动按钮:/compact

如果不想等自动压缩,dsh 也提供手动命令 /compact。执行后会跑一次压缩,告诉你压缩了多少条目、节省了多少 token:

code
> /compactCompacted 23 history items (~12,400 tokens).

小结一下:这件事是怎么拼起来的

指令走完了。449 行 HTML 吐出来,浏览器打开能用。

跟全程一遍,dsh 的"智能体大脑"做的是这八件事:

code
1. inbox 把新消息接住,按 next-turn / next-step 排队
code
2. surface 从 session 日志里挑出"模型能看的"那一段
code
3. phase 状态机从 idle 切到 running
code
4. llm.stream 把当前对话发给模型
code
5. 模型回的工具调用,按 capability seam 找到 provider,并行跑 + 顺序 commit
code
6. 上下文窗口快满时,先剪枝、再压缩
code
7. 有副作用的动作走 approval 弹窗,fail-closed
code
8. 任务结束,phase 切回 idle

这八件事的,是 dsh 的底层哲学。

1. 它解决的是什么

智能体 = 模型 + harness。

模型这层大家基本达成共识(DeepSeek / Anthropic / OpenAI 那些 API),harness 这层没有标准

各家都在写自己一套:

  • Claude Code(Anthropic 自家)
  • Codex CLI(OpenAI 自家)
  • LangChain / LlamaIndex(SDK 抽象派)

dsh 押的注是:harness 也会像 npm 包一样被标准化

2. 它没解决 / 还做不到的

读源码我看到几个明确还没做完的地方:

  1. 版本是 0.1.0-rc.6,releases 数组空的,README 自己说会有破坏性变更。生产代码不现实,写 demo / 学习 / 自己 fork 改造正好。
  1. 学习曲线不低。"一切皆插件"既是卖点也是门槛 —— 想写 dsh 插件?先学 Cordis。
  1. 官方插件生态薄。npm 上能 dsh plugin add 装的真插件目前只有几十个。
  1. MCP 不是 dsh 的必选协议。它自家走 JSON-RPC + capability seam;MCP 适配需要自己写插件。

最后,想要深入研究学习的,直接去看源码吧,仓库地址:https://github.com/deepseek-ai/deepseek-harness。

提交反馈

图文让 deepseek harness 做一只电子橘猫,跟跑完全程,我看清了这 8 件事!

0 / 2000