让 AI 改一个项目,它闷头干了二十分钟,中间读文件、跑命令、改代码折腾了几百步,最后交出一个跑不通的结果。
你想问它:你到底是在第几步走岔的?

问不出来。你能看到的只有一段被整理过的对话摘要。中间那几百步——读了哪些文件、看到了什么、为什么突然拐弯——没了。
这不是哪个产品做得差,这是行业默认状态。
8 月 13 日 DeepSeek 开源了 Harness,两天冲到九万星,写这篇时已经十三万。所有报道都在复述同一句话:Everything is a plugin,一切皆插件。
我觉得这句话是整件事里最不重要的。
零件可拆可换这事不新鲜,VSCode 是这样,Webpack 是这样,LangChain 这类框架主打的也是自由组合。要是 DeepSeek 只是又做了个能拼装的框架,它不值这么多星。
值钱的东西在别处。为了找到在哪,我把代码库整个下载下来翻了一遍。下面的数字、文件路径和代码片段全部来自本地,大家可以自己复核。
一、harness 是什么,为什么这次开源不一样
模型本身只会写字。改文件、跑命令、开浏览器,它一样都干不了。
中间得有一层东西:把模型写出来的话翻译成真实操作,把操作结果再翻译回去给它看,决定它什么时候该停,决定哪些信息塞进它的"记忆窗口"、哪些该扔掉。这一层就叫 harness。这词本来指套在牲口身上、让它能拉动车的那套装备,用在这里很贴切:模型提供力气,harness 决定这股力气能用来做什么。
过去两年有个不太被明说的共识:模型本身越来越像,真正拉开差距的是这层 harness。同一个模型换一套 harness,考试成绩能差出十几分。
The Register 提到过一组对比:有的产品给模型的"开场白指令"只有两百 token 左右,Claude Code 曾经写到一万 token 量级,后来砍掉了八成。这不是抠细节,是两种世界观。
所以 harness 才是各家真正藏着的东西。它装着大量写不进论文的手感——什么时候该把聊天记录压缩一下、工具说明书怎么写模型才不会用错、什么时候值得再叫一个 AI 来帮忙分担。
DeepSeek 把这一层整个开源了,用的是最宽松的 MIT 协议,随便你商用。五十多万行 TypeScript(含测试代码),两百多个包。
这才是分量所在。不是"又一个免费的 Claude Code 替代品",是行业第一次能公开检查一线实验室的这套东西到底怎么造的。
顺带一提,它底下垫着的那个零件管理框架叫 Cordis。DeepSeek 没有把它当外部依赖引进来,而是整个复制进了自己的代码库,还改成了自家的名字。这个动作说明的事比架构图多:地基他们要完全捏在手里,不接受别人的更新节奏。
二、三个值得细看的设计
1、那份流水账不是日志,是承重墙
大多数框架里,会话记录是个副产品:跑完了顺手写一份,方便你事后翻。真出了问题你翻到的是一份整理过的摘要,中间过程早没了。
这里反过来了。
DeepSeek 把每次会话记成一本只能往后加、不能回头改的流水账——模型看到的每一句话、每一次工具调用和返回,全都按顺序记进去。然后在 packages/core/agent-loop/src/invariant.ts 里,他们加了这么一道关卡:
const expected = session.deriveMessages()if (JSON.stringify(options.messages) !== JSON.stringify(expected)) {fail(`llm request for session "${String(session.id)}" diverges from the dispatch-time durable derivation (log-reconstruction desync)`)}翻译大白话:每次要发给模型的内容,必须和"照着流水账重新算一遍"的结果一模一样,差一个字就直接报错,不许发出去。
这句话的分量在于——模型看到的东西不是"顺手记了一份留底",而是从流水账里推算出来的。流水账是唯一的真相,发出去的请求只是它的影子。影子和本体对不上,系统当场拒绝干活。
同一道关卡还顺手检查了六项:用的哪个模型、开场白指令、温度参数、最长输出、停止条件、可用工具清单,全都得和流水账里记的对得上。而且请求本身和消息列表都必须是"冻住的"——中途谁想偷偷改一笔,改不了。
最有意思的是这道关卡插在哪儿。源码里那行注释我原样贴出来:
// Prepend prevents a short-circuiting replay listener from silencing the check.ctx.on('llm/stream', (options, next) => { ... }, { global: true, prepend: true })按这行注释的说法,是为了防止自家的回放逻辑抢先一步把这道检查跳过去,所以强制把它排在最前面。
这是那种踩过坑之后才会补上的防御。
这道检查默认是开着的。代价很实在——每次调用都要把完整的聊天记录完整比对一遍,白白多花一次功夫。换来的是什么?"回放一遍""从中间某步分岔重来""搜索历史""断点续跑"这些能力,全都变成了白送的,不需要单独再开发。你可以把一次失败的会话倒回第 37 步,换个说法,重跑后半段。
我认为这是整个项目的天花板。想到不难,敢为它付双倍开销才难。
2、隔离跑不起来就报错,绝不偷偷放行
AI 要跑命令,就得有个笼子关着它,免得它把你硬盘删了。这个笼子行话叫沙箱。
DeepSeek 的笼子分三档:只读、只能改工作目录、完全放开。不同系统用不同的底层机制关——Linux 用 Landlock(仓库里甚至专门写了个原生启动器),macOS 用 seatbelt,Windows 用权限令牌。这些都是操作系统自带的隔离能力。
关键在这儿:如果你要求关笼子,但这台机器上笼子建不起来,它直接报错罢工,而不是"那就先这么跑着吧"。
测试里把这条行为写死了:
expect(() => sandbox.confine(['true'], RO)).toThrow( expect.objectContaining({ name: 'SandboxUnavailableError', code: SANDBOX_UNAVAILABLE }))策略文件里还写明,默认档位是最保守的"只读"。
有些号称带沙箱的工具,在容器里、老系统上、权限不够的自动化环境里,会安静地退化成没有隔离。使用者往往并不知情,因为它"跑成功了"。
这不只是技术选择,是个立场:宁可让你的任务失败,也不让你在自以为安全的情况下,把删文件的权限交给一个靠概率说话的模型。
3、拿真实会话当测试样本
给 AI 系统写测试是公认的难题。模型每次输出都不一样,你没法写"结果必须等于 X"。
行业于是流行起一种做法:让另一个 AI 当裁判,评判这个 AI 干得对不对。这种方式的稳定性一直存在争议。
DeepSeek 的做法是:把真实会话的流水账录下来存成文件,然后拿这份录像造一个假模型——喂什么吐什么,完全可预测。同一个文件既是"回放的素材",又是"预期的答案"。
packages/test-support/acp-snapshot/src/suite.ts 里三种模式并存:
mode: 'replay' | 'record' | 'refresh'回放、录制、重新录一遍当新标准。结果是:没有任何账号密钥也能跑完整套测试,而且每次结果都一样。
这套机制撑起来的测试量值得单说一句。我数了一下:
真正干活的代码: 228,618 行测试代码: 287,495 行(940 个文件)测试比正片还多。
一个八月才发布、版本号还挂着"预览版"的项目,测试写得比业务多。这个比例我盯着看了一会儿——它比任何"多少万行"的规模数字都更能说明这个团队在防范什么。
这三个设计其实是一件事的三个面,全都立在"流水账是承重结构"这个前提上。没有第一条那道关卡,第三条的回放测试根本不可能可靠。架构能自洽,比某个地方特别聪明值钱得多。
三、说回"一切皆插件":为什么连"会话"都能拔下来换
开头我说那句口号是整件事里最不重要的。这个判断我依然保留。但有一个地方它确实兑现了,而且兑现得很巧妙——正好也是最反直觉的那个:会话本身可以被换掉。
模型、工具、界面能换插件,这好理解,无非是接口对上就行。可"会话"是什么?是这次对话从头到尾的全部内容。这玩意儿听着像是系统的地基,怎么可能拔下来换?
答案藏在一个区分里:记下来的账,和模型看见的内容,是两回事。
流水账只能往后加,一个字都不能改——这是第一节说的那条铁律。但模型每次看到的并不是整本账,而是账本投影出来的一个"当前视图"。DeepSeek 管这个视图叫 surface(表层)。每条记进账的内容都带一个标记,说明它怎么落到这个视图上:
export type SurfaceOp = | 'append' | { op: 'replace'; start: number; end: number }append 是正常情况,接在后面。replace 才是关键:这条新记录遮住从第 start 条到第 end 条的旧内容。
注意是"遮住",不是"删掉"。旧的那些一个字没少,还老老实实躺在账本里。只是从此以后,模型再看这个视图时,那段位置上显示的是新的这条。而且规矩很死——新记录必须逐条列出自己遮了哪些,一条都不能漏。
想改历史,你不能去改历史,你只能追加一条声明"从这儿到这儿,以后看我的"。
这一个设计,把"会话"从一块死地基变成了可以插东西的插槽。
最直接的用处就是聊天记录压缩。
但这里有个坎,不迈过去整件事是说不通的:既然只能追加不能删,压缩之后上下文凭什么变短?
要讲清楚,得先把中间那层拆出来。整条链子其实是三段,不是两段:
账本(存在硬盘上,只增不减)→ 清单(算出来的,可长可短)→ 消息(拼出来的,发给模型)
大家默认只有账本和消息两段,卡住就卡在漏了中间的清单。
先说编号从哪来。每条记录进账本的时候,系统顺手给它盖一个编号,这个编号就是它进去时账本的长度:
const event = deepFreeze({ type, seq: this.log.length, // 编号 = 它进账本时,账本已有多少行 ...})第 0 条编号 0,第 1 条编号 1,一直往下排,连续、不跳号、不重用。相当于账本的行号。
再说清单是什么。清单是一串编号,记着"这一轮该让模型看账本里的哪几行"。注意它存的是行号,不是内容本身。
模型看到的消息,就是照着清单一个个去账本里取出来拼的。
清单不是提前生成好放在那儿的,是每进一条记录顺手更新的。全部逻辑就这几行:
if (plan?.kind === 'append') { state.nodes.push(plan.seq) // 普通消息} else if (plan?.kind === 'replace') { state.nodes.splice(plan.startIdx, plan.endIdx - plan.startIdx + 1, plan.seq) // 摘要}普通的一句话进来,就把它的行号追加到清单末尾,清单长一格。摘要进来,就把清单里第 3 到第 47 号那一段整个抠掉,塞进摘要这一条的行号——清单从 45 项变成 1 项。
所以变短的是清单,不是账本。
下一次拼消息照着新清单走,那 45 行压根不会被取出来,上下文实打实地少了,钱也实打实地省了。而账本里那 45 行原文一个字没动,你随时能翻回去看,也能从其中任何一行重新分岔。
常规做法是直接把那 45 条删掉换成摘要,省是省下了,历史也就永远回不去了。差别就在这儿。
还有一个容易被忽略、但其实是整套设计地基的点:清单是算出来的,不是被当作权威存下来的。
它是账本的纯函数——把整本账从头到尾过一遍,该追加的追加、该抠的抠,清单自己就出来了。
仓库里确实有一个 session-projection-cache,会把这类派生状态做成检查点写到磁盘上,省掉冷启动时的重放。但它的 README 把自己的地位写得毫不含糊:
a fold shortcut, never an authority(一条重算的捷径,永远不是权威)
配套的几条约束也很硬:缓存可能过时,但 seq 会明确告诉你过时到第几条;版本对不上就直接丢弃重算,绝不做迁移;日志先落盘,缓存才跟上,所以崩溃只会让缓存落后于账本,不会跑到前面去。
换句话说,缓存只是加速,账本才说了算。
这也正是前面那条测试断言为什么必然成立:拿原始账本从零重建一个会话,它重新算一遍,结果不可能不同。
账本是唯一的权威,其余都是算出来的。
出厂就带了三个吃这个机制的插件,各干各的:一个是聊天太长时自动叫模型总结;一个不用模型,纯靠规则把那些巨大的工具返回值裁短;还有一个是你手动敲命令让它压缩。三个走的是同一个插槽,互不干扰,你也可以自己写第四个。
存储也是同理。管会话的那个服务只管内存里这一份,源码注释里写得很直白——持久化故意不在这儿实现,谁想存,自己去监听"有新记录了"这个信号,爱存哪儿存哪儿,硬盘、数据库、云端随你。
那么问题来了:允许插件随便遮蔽历史,前面那条"发出去的内容必须和账本重算的结果一模一样"的铁律不就破了吗?
没破。两个压缩插件的测试里都写死了同一条断言:
const replay = Session.create(session.id, [...session.events])expect(replay.deriveMessages()).toEqual(session.deriveMessages())意思是:压缩完之后,拿原始账本从零重新算一遍,得出的内容必须和现在这份完全相同。
这就闭环了。正因为改历史的唯一合法方式是"追加一条遮蔽声明",所以不管插件怎么折腾,整个过程永远可以从账本重放出来。
反过来说也成立:如果没有第一节那条铁律,"会话可插拔"就是个灾难——每个插件都能偷偷改上下文,出了问题谁也说不清是哪一环动的手。
所以这两件事是一件事。可插拔不是靠接口设计得多灵活换来的,是靠"一切都必须能从账本重算出来"这条底线换来的。
一切皆插件这句口号本身没什么信息量。但它在会话这一层的兑现方式,是我在这个项目里看到的第二漂亮的设计。
四、顺手挖到的一个细节
它出厂带了四套配置,躺在 apps/cli/config/agent-presets/ 底下:标准版、写程序版、极简版,还有一个叫 cordis 的。
中文报道里普遍把第四个写成 Creator,实际叫 cordis。它的本事是让 AI 去改造自己所在的这套系统——让 AI 写 AI。它自己的配置注释里有句提醒:开这套配置,等于把电脑的命令行权限交出去了,请照此对待。
真正让我停下来的是极简版。它给模型的完整开场白指令,就是这个:
- id: personaname:'@deepseek-ai/dsh-persona'config:text:You are a helpful software engineer assistant.complete:trueincludeRuntimeContext:false"你是一个乐于助人的软件工程师助手。"
一句话。底下那行 complete: true 的意思是这就是全部,后面任何环节都不许再往里加字。环境信息也不喂了。工具只留一个命令行和一个文本替换编辑器,连聊天记录压缩都没有。
一个把"提示词工程"讨论了两年的行业,官方文档指向的基准环境,用的就是这套一句话加两个工具的配置。
至于跑分本身——BENCHMARK.md 只有三行。乍看像是占位,翻开发现那一句写得挺具体:用哪个变体、每个任务用独立的工作目录和会话编号,路子是清楚的。缺的是分数。
不过缺分数这点还是值得一提:一个主打"让 AI 帮你写代码"的产品,发布时没有附上评测成绩,而极简版看起来正是为基准测试准备的。官方没有说明原因。
五、写程序版值得单说
平常 AI 干活是这么转的:调一个工具 → 结果回来 → 模型看一眼 → 决定下一步 → 再调一个。每一步的中间结果都要占掉它本就有限的"记忆"。
写程序版换了个路子。配置注释里说得很直白:
不是一个动作一次调用,而是让模型直接写一小段程序,一次执行完,本来要五个来回的活变成一个。
中间那些零碎结果留在执行环境里,只有最后的结论回到模型眼前。扫五十个文件夹的依赖、从一万行日志里挑错误——这类活在平常模式下是记忆杀手,在这儿就是一个循环。
代价也实在:程序写错了排查更难;需要人频繁拍板的活不适合;工具返回的格式得足够稳定。
有一点值得留意:按 code 预设配置里的说明,这个预设只改变工具的呈现方式,沙箱与审批那一套仍归宿主层所有,预设无权替换。也就是说,程序里的每次操作并没有绕开原来的关卡。
六、几点保留意见
Python 接口目前还很初步。版本号写着 0.0.0.dev0,实现上是对底层 Node 程序的一层封装,而非独立的 Python 实现。现阶段不建议当作正式依赖来规划。
对外接口是单向的。它能去用别人提供的工具服务,但自己不对外提供。它内置了把 Claude Code 和 Codex 作为协作方接入的通道。这个方向目前是单向的。
用量成本需要自己盯。官方讨论区 #1052 有用户询问用量增长较快的原因。社区诊断下来是几件事叠加:聊天记录越滚越长、外接工具吐回来的东西(网页结构快照、截图那种)赖在记忆里不走、写程序模式保留了中间结果。给出的解法基本是开新会话、手动压缩、装第三方插件把工具输出截短。
形成对照的是:这个框架把"过程可查"做得很彻底,"用量可控"目前更多依赖社区插件补足。
官方文档明确提示会有不兼容改动,版本停在预览阶段。这是预览阶段的正常状态,但配上目前的热度,值得提醒一句:它还不适合直接用于生产环境。
七、开源与定价:同一周里的两件事
8 月 13 日 Harness 开源。同一时期,新版模型上线接口,定价有所调整,并引入了高峰与低谷两套价格。据财新报道,部分档位的调整幅度较大;各家二手渠道给出的具体单价互有出入,这里不引用精确数字,感兴趣可以查官方定价页。
把这两件事放在一起看,有个值得琢磨的结构。
开源出去的是 harness——一层软件,多一万人用也不增加多少成本。它降低了开发者接入的门槛,让更多真实工作能跑到这套系统上。
它确实没有绑死自家模型,但方式和很多报道说的不太一样。仓库里 packages/llm/ 下只有三个包:llm-deepseek、llm-pi-ai、llm-retry——并没有各家厂商的专用适配器。接入其他模型走的是 llm-pi-ai 这个通用适配器(底层是第三方库 @earendil-works/pi-ai):OpenAI 兼容网关、自建服务、pi-ai 目录里已有的 provider,都通过配置一个路由接进来,按它 README 的说法是"配置而非改代码"。
不过默认值很清楚:唯一自带的专用适配器是 llm-deepseek,出厂声明的是自家两个模型,默认上下文 100 万 token、单次输出上限 25.6 万 token,连叫来帮忙的"分身"默认模型也是 deepseek-v4-flash。默认路径通向哪里,通常是产品设计的选择,而不是技术能力的边界。
而真实工作产生的每一步执行轨迹,恰好被那本只能往后加的流水账完整记录了下来。
至于这两者之间是否存在设计上的关联,官方没有说明,我也无从证实。但"开源工具层、在模型服务上获得回报"是一种成熟且常见的商业结构,很多公司都这么做,本身谈不上什么可指摘的地方。
开源与商业考量从来都不冲突,看清后者,也不减损前者的技术价值。
The Register 那篇提到 Armin Ronacher 说被这套设计哲学"quite inspired"。我认同这个评价。
同一篇报道还提到一个对比:出于对模型能力被复制的顾虑,部分厂商在逐步收紧可见的技术细节。DeepSeek 这次选择了相反的方向,把整层 harness 连同实现细节公开。
两种取舍,各有各的道理,短期内大概也看不出高下。
八、所以
如果你自己在做这类系统:去把代码下下来读,别只装来玩两下。那道"照流水账重算一遍"的关卡、那个"关不上笼子就罢工"的原则、那套"拿真实录像当测试"的机制,这三样值得抄进你自己的项目,跟你用不用 DeepSeek 的模型一点关系都没有。
如果你只是想找个顺手的工具,现在可能还早。界面还比较初步,用量需要自己盯着,预览版本会有不兼容改动。
写完这篇我印象最深的不是架构多漂亮,是那个测试量超过业务代码的比例,和那行防着自家逻辑抢跑的注释。
说到底,它们和那道流水账关卡在回答同一件事:中间过程不可查,结论就没法信。
这话对 AI 成立,对写 AI 的人也成立。

夜雨聆风