这门专栏一直说「你评的从来不是模型,是模型 + harness」,本篇第一次带你看清那个 harness 内部长什么样。两个藏书没有的新机制:「模型可见即已记录」的运行时不变式(trace 完整性不靠自觉埋点,靠架构断言强制)和 fork 分叉评测(从同一历史前缀分叉,做反事实对比和精确同起点的 pass^k)。读源码读出评测手法,是本书最「工程」的一篇。
tools/post-execute、tool/result(无损 JSON 快照)是组件级评分器的天然插入点,turn/end 评轨迹级,agent/pre-step 挂运行时护栏。dsh --dump-config 一条命令导出整棵配置树快照,「这次评测用的是哪套 harness」不再靠人肉记录;patch 机制换掉单个条目即得理想对照实验。先修:a19(实战 02 harness 概念)与 a20(实战 03 loop engineering)——本篇是「harness/loop 区分」的源码级落地;配 a25(实战 21 可观测性)读 trace 的工程实现、配 a24(实战 16)读 fork 如何给 pass^k 提供精确同起点。正在自研或选型 Agent 框架的团队,用第二至六节做「这个 harness 对评测友不友好」的检查清单;不写代码的 PM 读「诚实的边界」之前即可,重点理解「好 harness 处处给评测留门」这个判断标准。
先合上原文在心里作答,再点开对照。全对 = 本篇真正通关。
埋点靠自觉,漏了就缺轨迹,出了事定位不到;不变式靠架构强制——凡抵达模型请求的输入都必须能从日志重建,并由运行时断言保证,想给模型加新可见输入就必须同时新增会话事件。对评测意味着:采集完整、强一致、可复现、可审计的 trace 这件事被架构层钉死了,评测的第一块基石白给。
组件级挂 tools/post-execute 或 tool/result;轨迹级在 turn/end 之后拿整条 turn 的事件序列评。tool/result 特别适合代码型评分器,因为 dsh 会对工具结果做无损 JSON 快照再交给观察者——不可变、可无损序列化的结构化事实,正是代码型评分器梦寐以求的干净输入,不用去解析杂乱的日志。
从头各跑一遍,两条轨迹的历史前缀也可能发散,你隔离不出单一变量;fork 从同一条轨迹的「已完成轮次」处分叉,前缀被完全固定——换 prompt/模型/工具集后,差异只能来自分叉点之后,这就是干净的反事实对比。同理,从同一前缀重复分叉多次,pass^k 想要的「相同起点」有了精确保证。
上方为本站原创导读,下方为原文完整收录,便于对照阅读。
这一讲我们干件不太一样的事——读源码。读的是 DeepSeek 刚开源的 deepseek-harness(简称 dsh,撰写时是 0.1.0-rc.5 技术预览)。它是一个「用于构建 Agent Harness 的插件化 SDK」。
先把丑话说前头:它不是评测工具——你在它的子系统里找不到 eval、grader、scorer、metric 这些东西。所以这一讲不是给你介绍一个新评测框架。那为什么还要花一整篇读它?
因为它是一个设计精良、大厂出品的生产级 Harness。而这门专栏从第 2 讲就在说:你评的从来不是模型,是「模型 + harness」;第 20 讲又强调:评测要尽量复用生产同款 harness。那么问题来了——你要评的那个 harness,内部到底长什么样?一个好的 harness,会不会天然就给评测留好了位置?dsh 正好是一份可以逐行读的、活的答案。读它,就是读「你要评的那个东西」本身,并看清一个好架构会在哪些地方为评测留下「接缝」。
dsh 底层是一个叫 Cordis 的插件框架:产品的每一部分都是插件——模型适配器、工具注册表、会话日志,甚至 agent loop 本身——都能从配置替换。没有需要打补丁的特权内核,扩展它的方式就是把插件挂到别的插件旁边。
对评测工程师来说,这个「一切皆插件、皆可替换」的设计有个直接推论:这个 harness 的每一块,理论上你都能在评测时换成一个受控版本。这句话的份量,读到第四节你会更有体会。
它没有内建评测,但它的架构里,藏着五处对评测极其友好的「接缝」。我们一个个看。
这是最惊艳的一处。dsh 的会话(packages/core/session)用一条仅追加(append-only)的 SessionEvent 日志作为唯一真相源:turn/*、step/*、user/message、assistant/*、tool/* 这些都是持久事件,按序追加进去。模型看到的历史,由 deriveMessages() 从这条日志投影出来;fork、回放、transcript、遥测、持久化,全部派生自同一条事件流。
它还立了一条运行时不变式,我把原话摘给你:「模型可见即已记录」——抵达模型请求的一切,都必须能从日志重建,并由一项运行时断言强制保证。想给模型加一项新的可见输入?那就必须同时新增一个会话事件。
停下来体会一下这对评测意味着什么。第 4 讲我们讲「轨迹(transcript)要完整,否则出了事你定位不到」;第 21 讲我们花一整讲讲怎么给 Agent 接 tracing、记 span。而 dsh 把这件事从架构层就钉死了——它不是「你记得埋点才有 trace」,而是「凡模型看过的,日志里一定有,否则断言就挂」。这等于给了你一套强一致、可复现、可审计的 trace,白给。你要做评测,第一件事——采集完整轨迹——它已经替你保证了。
这也顺带给了合规一个大礼(呼应治理与合规那篇加餐):审计问「这次决策凭什么这么做」,你从这条日志就能完整重建,天然满足可溯源要求。
dsh 把一次运行拆成清晰的层级:一个步骤(step)是「一次模型请求 + 它调用的工具」;一个轮次(turn)包含零或多个步骤。它的生命周期是一串具名事件:
turn/start → step/start → agent/request → llm/stream → tool/call → tools/pre-execute → tools/execute → tools/post-execute → tool/result → step/end → turn/end
其中 agent/pre-step、tools/pre-execute、tools/execute、tools/post-execute 是瀑布事件(waterfall)——监听器必须调用 next() 才会把控制权交下去。翻译成评测语言:这些就是天然的评分器插入点。
tools/post-execute 或 tool/result 上——尤其后者,dsh 会对工具结果做无损 JSON 快照再交给它观察。一个不可变、可无损序列化的结果,正是代码型评分器(第 12 讲)梦寐以求的干净输入:你不用去解析乱七八糟的日志,拿到的就是结构化事实。turn/end 之后,拿整条 turn 的事件序列来评「它这一整轮走得对不对」。agent/pre-step 能改写甚至拒绝模型将要看到的消息——这就是「运行时护栏」可以挂的地方。一句话:dsh 的事件流,等于把「该在哪评」这个问题的答案,直接标在了架构上。你不用魔改它的循环,顺着事件挂监听器就行。
dsh 有个核心概念叫 seam(能力接缝):一项可替换能力由三个角色组成——声明接口的 Service Definition、实现它的 Service Provider、使用它的 Consumer(通常是面向模型的工具)。替换一个提供方,就能改变整个产品的行为。
文档里有个例子特别戳评测的痛点:文件系统和进程提供方共享同一个执行世界,所以你把它们指向一个远程沙箱,就等于把 Bash、PTY、LSP 一整套执行能力都搬了过去,不需要给每个工具单独改造。
这不就是第 20 讲那条铁律的架构级实现吗——评测时要在贴近生产、但受控隔离的环境里跑 Agent。在 dsh 里,「把生产 harness 换成评测隔离环境」不是一件要你到处改代码的苦差,而是换一个 provider 的事。第 10 讲讲的可控性、第 20 讲讲的「每次从干净环境开始」,在这种设计下都变得廉价可行。这就是「一切皆插件、皆可替换」那句话,落到评测上的真金白银。
读源码最大的收获,是发现了一个我们前面没专门讲过的评测手法。dsh 的会话支持 fork:
ctx.sessions.fork(source, boundary?, childSessionId?):选取截至 boundary 事件序号的前缀(要求该前缀结束时没有开放轮次),创建一个带谱系元数据的子会话。
翻译过来:你能在一条轨迹的任意「已完成轮次」处,把它分叉出去。这给了评测两个很有意思的能力:
一个为产品功能设计的 fork,顺手就成了评测的利器——这也再次说明第 3 讲那个观点:好的 harness/loop 设计,和好评测,本来就是一体两面。
最后一处接缝,回扣第 2 讲那句最容易被忽视的话:评测报告必须记清 harness 信息,否则不可复现、不可比较。
dsh 把这件事做到了极致——运行中的 dsh 是一棵插件树,你可以用 dsh --profile web --dump-config 把实际启动的整棵配置树打印出来。也就是说,「这次评测用的到底是哪套 harness」不再靠人肉记录、靠记忆,而是一条命令导出的、精确到每个插件配置的快照。想做 A/B?用它的 patch 机制换掉某一个条目(比如只换工具描述、只换模型适配器),其余完全不变——这正是第 2 讲那个「固定模型、只改 harness」对照实验的理想载体。
可记录、可复现、可 A/B——评测对 harness 的三个核心要求,dsh 用「一切皆插件 + 配置可导出」一次满足了。
前面五处是重点,但读源码时你还会顺手发现几处对评测有用的地方,一并点一下:
ctx.approval、permission-presets、ctx.sandbox):dsh 把「哪些操作能直接做、哪些要人审批、进程能不能越界」做成了独立可配的策略层。这正是第 2 讲说的 harness Permission 面,也是第 25 讲安全评测要盯的对象——它把「越权」这件事的判定点,明确摆在了 tools/pre-execute 前的守卫里。你评安全时,知道该往哪看了。ctx 上的 token-meter、compaction):把成本和「上下文被压缩后会不会丢信息」这两件事显式化了。第 27 讲的成本 / 延迟评测、记忆那篇加餐讲的 context rot,在这里都有现成的观测点。你看,从权限到成本到多智能体,这个 harness 几乎把这门专栏每一讲要评的东西,都摆到了一个有名有姓的位置上。这不是巧合——一个想得清楚的架构,本来就会把「该在哪管、该在哪看」分得干干净净,而「该在哪看」,恰恰就是评测要下手的地方。
读到这儿别上头,几句实话:
tools/post-execute、tool/result 的无损结果)= 天然的评分器插入点,组件级 / 轨迹级各就各位(第 5、12、17、18 讲)。