177K star,扒开DeepSeek Harness的营销,我看到了什么

引子

前几天被DeepSeek Harness刷屏刷烦了。推特、油管、公众号、技术论坛全是它,标题一个比一个唬人------"干掉Claude Code"、"DeepSeek Just Killed Proprietary Coding Agents"。我去仓库瞄了一眼,star已经177K了(写这篇的时候还在涨,涨得比我写字快),一个标着Developer Preview、版本号还停在0.1.1-rc.1的项目,一周冲到这量级,有点意思。

先说清楚它是个啥。DeepSeek Harness就是个agent harness,包在大模型外面那层壳:模型负责动脑子,harness负责让它读文件、跑命令、调工具,一步步把活干完,Claude Code、Cursor底下都是这个东西。这类壳现在满地都是,大部分说穿了就是给chat API套个CLI再起个唬人的名字。我本来以为是又一个被吹起来的噱头,所以直接去扒了源码,还别说,真扒出了点东西。

一、不确定的LLM驱动的系统,怎么才算可信?

我刚开始是被"everything is a plugin"这个slogan营销的,所以感兴趣的点是看它的插件系统咋设计的,但在读源码的过程中,却被agent-loop里的一段代码吸引了:

ts 复制代码
ctx.on('llm/stream', (options: GenerateOptions, next) => {
  if (!isAgentLoopRequest(options)) return next()
  if (!Object.isFrozen(options)) fail('a loop-built request must be frozen')
  if (options.sessionId === undefined) fail('a loop-built request must carry a session id')
  const session = ctx.sessions.get(options.sessionId)
  if (!session) fail(`... must carry a live session id ...`)
  if (!Object.isFrozen(options.messages)) {
    fail('a loop-built request must carry a frozen messages array')
  }
  // ...省略若干前置检查...
  const expected = session.deriveMessages()
  if (JSON.stringify(options.messages) !== JSON.stringify(expected)) {
    fail(`llm request ... diverges from the dispatch-time durable derivation (log-reconstruction desync)`)
  }
  // ...下面还要逐字段比对model / system / temperature / tools...
}, { global: true, prepend: true })

这段代码挂在llm/stream上,也就是每次要把请求发给模型的那个瞬间。它不生成任何内容,不调用任何工具,就干一件事:请求真正发出去之前,拦下来做一轮校验,任何一项不对就fail,直接让这次调用崩掉。

它到底在校验啥?核心是中间这两行:

ts 复制代码
const expected = session.deriveMessages()
if (JSON.stringify(options.messages) !== JSON.stringify(expected)) fail(...)

options.messages是即将真正发给模型的内容,expected是用deriveMessages()从会话日志里重新推导出来的内容。两个东西JSON.stringify之后做全量字符串比对,不一致就报log-reconstruction desync(日志重建失真),直接抛异常。

要看明白这行代码的分量,得先弄清楚它在跟什么较劲。 做过LLM系统的同学都知道,线上出一次问题,定位根因最直接的就是还原现场:当时到底喂给模型的是啥?于是你去翻日志。但你翻到的那份日志,是打点那一刻记下来的,而请求真正发出去之前,内容往往还要再过好几道手------拼系统提示、塞进历史对话、截断超长上下文、注入一堆动态信息。日志里记的,和模型真正收到的,可能是两个东西。 你对着日志复现半天,一顿操作,结果复现的可能压根不是当时那一次。

DSH这段代码,强制从日志能推出来的实际发给模型的必须一字不差,不然不让发。这样一来,日志就不再是大概齐的记录,而是现场的精确复制,把还原现场这个坑完美解决。

二、这套东西是DSH的创新么?

从日志重新推导请求 的底层原理是event sourcing:不存请求快照 ,只存一条条原子事件,用的时候再折叠 出状态。这是几十年的老概念了,把它做到极致的是工作流引擎,比如Temporal,重放时生成的命令序列必须和历史逐条对上,对不上直接抛non-determinism error(官方文档),运行时强制,Temporal已经这么干了很多年了。

但有意思的是:这套严格的校验,在通用工作流引擎里是标配,但是在agent框架里是很少见的。

举个最主流的对照。LangGraph是现在用得最多的agent编排框架,它也有check pointer、也支持time-travel回放。但你去翻它的官方文档,原文写着:回放时节点会重新执行------LLM调用、API请求会真的再发一次,返回结果可能跟当初不一样 。也就是说,LangGraph的回放只保证每一步的输入状态能恢复不保证重建出来的和当时模型真正看到的一致,更没有任何运行时校验去卡这件事。确定性全靠开发者自己写代码时小心。

这不是说LangGraph差,大多数agent框架都是这个现状。但这让我们认识到:日志能精确重建模型当时看到的请求,在agent圈里并不是默认选型。

而DSH把Temporal那种工作流引擎级别的严格,搬进了agent runtime:它不光存事件流、投影请求,还在每一次请求发出前 ,花额外的开销把投影出来的实际要发的拉过来做全量比对,对不上立刻崩。这个较真的程度,在同类agent项目真的少见。

三、这么较真,到底值不值?

每次请求都自检一遍,开销不小,真的值吗?

  • 它加在最热的路径上: deriveMessages()要把整条事件流折叠一遍,历史越长越贵;JSON.stringify一个几十K token的上下文再比字符串,也不是免费的。上下文越长(越该省成本的时候),这道校验越贵。
  • 它防的是自己人的bug: 什么情况下投影出来的 会不等于发出去的?只有DSH自己(或某个插件)的代码有bug时。 那把逻辑写对、拿单测和类型系统覆盖不就行了,凭啥让线上每次请求都为你的bug付性能税?
  • 实现也谈不上优雅:JSON.stringify全量比对,对key顺序敏感,真报了desync也只告诉你不一致,不告诉你哪不一致,排查还得自己来。

这些质疑都成立。而且生产环境该不该开这种运行时断言,业界早有定论,答案挺一边倒的:大多数情况下不该开。 主流项目的默认姿势是断言只在开发测试期开、生产构建关掉。最典型的实证是2025年底Percona的事故:他们误把带断言的PostgreSQL构建发进了生产源,官方专门发公告让大家赶紧升级,理由是断言会带来20%--50%的性能下降、还会因为fail-fast直接杀进程导致线上停机。生产环境每个关键操作都跑运行时断言,在主流实践里是少数派,甚至被当成事故。

那么哪些领域在生产里坚持开运行时检查?是数据库、编译器这种正确性比性能更金贵 的领域。它们有个共同点:内部状态错了还带病继续跑,后果比直接崩掉严重得多。

所以再回头看DSH就明白了:它主动把自己归进了"数据库、编译器"那一类,而不是"普通在线服务"那一类。合不合理,实际上要看它是不是真属于这一类。

我个人觉得它算。因为它本质是个agent runtime,瓶颈是模型推理那几秒到几十秒,多花几毫秒做投影校验基本是噪声,用户根本感知不到。不像高并发服务,20%开销要人命。更关键的是,它整个立身之本就是可复现、可审计、可回放 。对这种系统,日志和现场不一致是致命bug,一旦发生又没被发现,你所有的回放、调试、审计全是假的。再加上它"everything is a plugin"的核心理念,第三方插件能替换模型适配器、会话存储,别人写的插件太容易引入不一致,这道校验防的就不只是自己人,是整个开放生态里的任何人。

所以结论是:合理不合理,取决于你站在什么角度看设计。 如果是高并发交易系统的角度,它是过度设计;如果是数据库、编译器这种"正确性敏感系统"的角度,它就是良好设计。

老A点评:我最欣赏的其实不是这段代码本身,是它背后那个价值排序------宁可每次请求都付一遍自检的开销,也不允许「不可信」有任何发生的机会。

还有一个细节,是这个断言注册时用的{ global: true, prepend: true },注释原文写着:

// Prepend prevents a short-circuiting replay listener from silencing the check.

Cordis的事件是waterfall语义,中间件不调next(),后面的监听器就被短路了。要是有人注册了一个会短路的replay监听器、还排在这条校验前面,这道检查就会被悄没声地跳过,你都不知道它没跑。所以他们把校验prepend到最前、还加global,等于把未来某个插件可能无意中绕过校验这条路也提前堵死了

四、投影这一份,凭什么就是对的?

看到这会儿大家可能会有个疑问:deriveMessages()投影出来的这份,凭啥就是对的?万一它自己算错了呢?

请看packages/core/session/src/surface.ts的注释,我初次读到就觉得这个措辞挺讲究:

This is THE per-node projection rule: Session.deriveMessages folds it over the live surface, external reconstructors and pure projections fold the same function over a log prefix's surface to rebuild the exact messages any request was built from.

它在强调一件事:投影规则只有这一条,不许存在第二套。这是整套设计的核心,也是它跟回放不保证一致的那类框架真正拉开差距的地方。

前面说过,LangGraph那种回放会重新调LLM,为啥它做不到重建出当时一模一样的请求 ?根儿就在这里:一旦在线组装请求和离线重建请求走的不是同一段代码,哪怕两边一开始写得都对,只要有一边迭代时改了个边界条件、另一边没跟上,就会慢慢漂移。

DSH从源头上不给漂移留空间:只有一个投影函数,在线离线都fold它。同一个函数、两条路径,在线发出的和离线重建的在结构上就不可能是两个东西。所以第一节那道断言验证的是一个本来就该恒等的关系,而这个本来就该恒等 ,靠的是投影规则只有一条撑起来的。

这个函数还顺手干了件事:明确区分了哪些日志给模型看,哪些日志给人看。注释里写得很直白:

turn/step boundaries, chunks, usage, and errors are trace/replay data.

日志里的东西分两类:

一类是给模型看的------比如拼好的对话内容,这是模型真正需要的东西。

另一类是给人看的------比如流式输出的chunk、token用量、错误记录、步骤边界。这些对模型来说全是噪声,但对人排查问题、回放现场、做审计来说,是宝贝。

所以设计上干脆分两条路:

日志负责全都记 ------管它有用没用,先存下来,保证以后查什么都有。

投影函数负责挑着喂 ------每次要喂给模型时,只从日志里捞出模型真正需要的那部分,剩下的全部滤掉。

关键是这个投影函数全项目只有一个 ,不存在在线跑一套、离线重建走另一套的情况。所以一份日志就够用了:既记得全,又喂得准,两个目标互不打架。

五、最难的地方:日志不能改,历史又必须变短

到这儿所有设计都建立在一个前提上:日志只增不减(append-only)。可上下文窗口是有限的,历史迟早得压缩。压缩就是删,删就破坏了append-only的设计。这是整套设计里最难自洽的地方。

我特意去翻了它的compaction是怎么处理这个问题的,在packages/compaction/compaction/src/checkpoint.ts

ts 复制代码
const COMPACT_CHECKPOINT_MARKER = Object.freeze({ kind: 'plugin', plugin: 'compact' } as const)

export function compactCheckpointSource(
  compactionId: CompactionId,
  sourceCommandId?: CommandId,
): CompactionCheckpointSource {
  return Object.freeze({
    ...COMPACT_CHECKPOINT_MARKER,
    compactionId,
    ...sourceCommandId === undefined ? {} : { sourceCommandId },
  })
}

思路很简单:往append-only日志里追加一条带来源标记的替换消息,用COMPACT_CHECKPOINT_MARKER标明我是compact插件产生的 ,再带上唯一的compactionId,可选还能带上是哪条命令触发的sourceCommandId。原始历史,一条都不删。

这么做的巧妙之处在于

可识别 :这条消息带着marker,不会伪装成普通模型消息混进历史里;

可追溯compactionId加上sourceCommandId,让这次压缩是谁、因为啥触发的 可查;

不破坏基础:原始事件全部保留,模型看到的是投影之后的短历史,人和工具依然能拿到完整的长历史。

这个设计的本质,有点像会计做账时的红字冲销:账做错了不能拿橡皮擦掉,只能再记一笔带明确标注的冲销分录。账本永远只增不减,但余额是对的。模型的上下文变短了,系统的记忆没变短,这两件事解耦了。

而且它跟上一节能严丝合缝地接上:因为投影规则是唯一的,压缩之后的历史怎么被模型利用,同样是deriveEventMessage说了算。压缩没有绕过投影,它本身就是投影的输入之一,不会出现在线压缩走一套逻辑、离线重建走另一套的分叉。投影规则依然只有一条。

六、真正让我改观的,是它否决了什么

前面这些,说到底还是它构建了什么。构建是可以包装的。真正让我对这个项目刮目相看的,是.agents/notes/这个目录里的东西。

DSH把它的决策过程也做成了一套带生命周期的系统。

四个状态目录:proposed(提案)、implemented(已实现)、rejected(已否决)、archived(归档)。我数了下,implemented底下1600多个文件,rejected里30多个,archived 400多个,proposed还有70多个。implemented下面还细分architecture、bug-fix、feature、simplification、testing。

大多数团队的架构决策记录(ADR),只记我们决定这么做。通过的方案写进文档,被否掉的方案最多在备选方案里提一句,更常见的是直接散落在聊天记录里,没人系统性地留档。

老A点评:这跟上一节是同一个逻辑,代码里日志不删历史,团队里决策也不删历史。一个项目的技术理念和它的协作方式一致,这通常说明一件事:这是团队共同遵守和相信的理念,不是做给外人看的。

我不是要硬夸这个目录的设计,rejected/里也有些提案纯粹是因为优先级或者范围被砍的,跟「可信 」没关系。但rejected/simplification/下面有两份被驳回的优化提议,两次都是在更简洁和更可信之间,选了可信。

第一份,drop-durable-step-boundaries.md。有人提议:删掉step/start/step/end这些步骤边界事件吧,反正每个事件本身都带了{turn, step}编号,边界事件不产生任何模型可见内容,纯冗余,删了日志更小、代码更清爽。

听起来挺有道理。但这个提案被否了,否决理由的原话是:

step/end is concrete information: a reader can tell whether a model request finished, crashed, or is being repaired without deriving that state from the next event.

翻译过来:step/end是具体信息。没有它,你在日志里看到的就是一个请求开始了,然后没有然后了,你根本分不清这是正常结束、是崩了、还是正在被修复重试。这三种状态天差地别,但从日志表面看一模一样。

那份note最后还有一段叫"What we give up"(我们放弃了什么):

That loss is not acceptable while the session log is the durable replay and audit surface.

只要日志还是回放和审计的依据,这种信息损失就不可接受。提案想删的恰恰是对模型没用 的那部分,而否决的理由恰恰对人有用

老A点评:这个优化提议其实特别有诱惑力,删掉不产生模型输入的事件,日志更小、代码更简、逻辑更纯,可能很多团队会通过这个提议。而他们否掉它的理由只有一句:读日志的人得能分清它是结束了、崩了、还是正在被修。判断一个优化该不该做,看的不是它带来的好处是什么,而是它带来的坏处是什么。

冗余和稳健的区别,往往只在出事那一刻才显现,而那时候,你已经没得选了(选错的同学去抄写一千遍稳字经)。

第二份更典型,assembled-assistant-messages-only.md。提议:只存组装完成的assistant消息,把中间那些流式chunk丢掉吧,省存储。显然,组装后的完整消息就是所有chunk拼起来的结果,两份都存确实冗余。

也被否了。理由是:丢了chunk,就再也没法精确重建旧turn的token流;一次中途失败的流,它的部分输出会永久丢失;而高保真回放、快照测试,全都依赖持久化的chunk

实际上,冗余的不是数据,是能力。一次中途失败的流,它压根没有组装后的完整消息这个东西,它只有半截chunk。删掉chunk,等于把所有失败现场一起删了,而失败现场是最需要被复现的那部分。这条又跟第一节呼应上了:那道断言要求字节级同一,这种精度只能建立在原始chunk之上。砍掉chunk,第一节的基础就无了。

当然,这两份note不是为了证明DSH每个决策都对,它现在还是0.1.1-rc.1,官方自己都写了会有破坏性变更。它们只证明一件事:在简洁和可信撞车的时候,这个团队的默认选择是什么。

七、写在最后

绕回开头那个问题:177K star的DeepSeek Harness,是噱头还是真有东西?

营销层面,标题党和夸大其词肯定是有的。《干掉Claude Code》这种标题,图一乐就行。但扒完源码我真觉得DSH的设计是有真东西的。

它值钱的地方,不是"everything is a plugin"这句slogan,也不是那些跑分。而是:一道每次请求前都要跑的断言、一个不许有第二套的投影函数、一笔像红字冲销一样的压缩、一个存着否决案的目录

LLM的不确定性没法消除,DSH也没打算消除它。它做的是把不确定性圈在一个点上:模型的输出可以不确定,但模型看到了什么必须百分百确定、可重建、可比对。不确定性被隔离在一个点,系统其余部分就还是确定的。

这套思路其实不挑场景,任何事后需要还原现场 的系统都用得上:关键契约用运行时断言;在线和回放复用同一个投影函数;还有那个提优化提议的习惯,提这个能不能优化或者删除之前,先仔细想想:优化了以后,出事那天我拿什么看?

绕回开头那个问题:DSH是噱头吗?我觉得营销上是的。但它真正的价值不在那些营销号里,而在:一道每次请求前都要跑的断言、一个不许有第二套的投影函数、一笔像红字冲销一样的压缩、一个存着否决案的目录。就冲这些,那177K star里,我认自己这一票。

相关推荐
别看我只是一直狼1 小时前
补充:证书自动续约检查、故障诊断与重新签发
后端
Lear1 小时前
Java 实现多步骤异步任务编排
后端
半个落月1 小时前
在浏览器里运行 DeepSeek-R1:React 对话界面与安全渲染(三)
前端·人工智能·react.js
深圳市益普科技有限公司1 小时前
先进封装爆发,封测厂的数字化准备好了吗?
人工智能
晴天161 小时前
LLM 与推理模型:从“快思考“到“慢思考“的范式跃迁-Day27
人工智能·深度学习
charles_he1 小时前
Agent风险等于权限乘以自动化倍率
人工智能
汉堡大王95271 小时前
用 Trae Work 自动化任务,6 分钟生成一份前端生态周报
前端·javascript·人工智能
小强19881 小时前
告别 "if-else 地狱":PHP 中的策略模式、管道模式与责任链模式实战
后端