博客
体感是真的,归因往往是错的

一个没被量过的数字,只是一种感觉。感觉通常是真的,感觉的解释通常是错的。
「冷启动大概要四十秒。」
这句话是我说的。说的时候我信,而且信得很踏实——那不是我编的,是我实实在在等出来的。
我那天在干什么
八月十三号,DeepSeek 开源了一个叫 Harness 的智能体运行时。我第三天上手,想给它写出第一个插件。
吸引我的是它那句设计口号:一切皆插件。模型、工具、技能、会话、沙箱、存储、调度、界面——甚至连智能体自己跑的那个循环——全都是插件,挂在一个共享的上下文上,可以随意替换、自由重组,不改一行源码。
我一直在做的方向,是让 agent 靠真实业务的评测自我进化。这件事有个很硬的前提经常被跳过:agent 得先有一个可以被改动的自己。 如果能力是焊死在源码里的,所谓进化最多是换换提示词,骨架一动不动。而当每一样东西——包括循环本身——都成了可插拔的部件,「改装自己」才第一次有了结构上的可能:加一个工具、换一条循环、替掉一个评估器,都不需要重新发版本。
这是我愿意在它身上花一整天的原因。
四十秒
写插件的节奏是这样的:改一行代码,重启,看效果,再改一行。一天下来我重启了八次,每次都在那儿干等。
四十秒这个数,就是从这八次等待里长出来的。它是那种「我知道它有多慢」的确信——有画面、有情绪、有具体的烦躁。
我甚至已经拿它当论据了:既然每次重启都要四十秒,那做一个热重载插件就很值得。
然后我停了一下。倒不是我怀疑自己,是我对一切没量过的数字都习惯性地不放心——就算这四十秒真真切切发生过,一次也说明不了什么。它可能是网络抖了一下,可能是磁盘正忙,可能是那一次刚好赶上别的事。要拿它当作「值得做一个新功能」的理由,样本得先够。
于是我量了三次。
第 1 次冷启动: 3.6 秒
第 2 次冷启动: 3.1 秒
第 3 次冷启动: 3.1 秒
三秒。
那三十七秒去哪了
四十秒不是幻觉。我确实等了大约四十秒,只是那段时间里真正属于「启动」的只有三秒,剩下的全是我自己。
等一下、去看眼日志、还没起来、再等两秒、切个窗口、回来再看一眼。这些动作单拎出来每个都很短,可它们叠在一起,构成了我对「慢」的全部体验。我把自己等待的开销,算到了被等待的那个东西头上。
这种错比信口开河危险得多。信口开河一眼能识破,而这个数字背后垫着一段货真价实的经验,我讲得出细节、说得出当时的烦躁——它对别人可信,对我自己更可信。
更麻烦的是,我靠自己发现不了。因为在这件事里,拿秒表的人和被计时的人是同一个。
自己评自己,这个结构本身就不成立

这个坑我在工作里见过,只是那时候它长着另一副样子。
现在很流行让模型给自己的输出打分:生成一段内容,再让它自己判断这段够不够好,够好就通过,不够就重写。听上去很省事,实际很容易翻车,而且翻起来是灾难性的——它会一路自我确认地滑下去,每一步都觉得自己干得不错,直到整件事已经完全偏了,中间没有任何一个环节会喊停。
我一直以为那是模型的毛病。那天量完三次冷启动我才想明白,那是「自己评自己」这个结构的毛病,跟谁来评没关系。 换成人,一模一样。
要让评估有意义,它必须由另一个对象给出。可以是另一个独立的模型,可以是一条写死的规则,可以是一次真实的测量,也可以是另一个人。具体是谁不重要,重要的是它不能是被评估的那一个。
我那天做对的唯一一件事,就是给自己找了这个「另一个对象」:一块秒表。
我判断一件事该不该做的三步
被这三秒提醒之后,我把当天的方案都放回自己惯用的框架里重新过了一遍。这个框架我用了很久,就三步,顺序不能换:
第一步,先说清楚为谁解决什么问题。 问题定义不出来,后面全是白搭。
第二步,用一次干净的对照实验证明它真的有价值。 做优化类的事情时这一步尤其要紧——优化的意思就是「比原来好」,那至少得有个原来,得有条基线,得有一组能对照的数字。没有对照,「更好」这两个字是空的。
第三步,退一步看全局。 我做的这件事,符不符合绝大多数人的利益,符不符合这个项目本身的利益。
除了这三步,我还有两条常设的放弃条件,撞上哪条都停:
一,别人已经做得很好的事,我刚开始做的时候不做。
二,被实验数据证伪的事,不做。
那天我提了两个方案,一条撞一个,全中。
方案一:热重载,撞上第二条
热重载插件就是上面那个想法:改完代码自动生效,不用重启。
我给它准备了三条理由,当时听着都挺硬。一是冷启动四十秒,改一个字符也得全付。二是进程一死,会话上下文就归零了,验证场景得重新搭一遍。三是深度场景下每改一次要重跑五轮对话,每轮烧掉几万 token。
我还提前写死了作废线:如果实测省下的 token 不到三成,就说明我把痛点判断错了,方案作废。
然后三条理由一条一条塌了。
第一条上面说过。
第二条塌得最让我意外——会话根本没归零。每次重启后,侧边栏里那个会话一直都在,点进去接着聊,历史完整,它本来就存在磁盘上。而且这件事整个白天当着我的面发生了五次,我一次都没注意到。我脑子里那句「重启会丢上下文」,是从别的工具带过来的旧经验,我从没在这个工具上核对过。
第三条最需要认真对照。我去翻了会话日志里的用量记录:
turn 1 新增 in=30 out=108 cacheRead=11008
turn 1 新增 in=183 out=71 cacheRead=11008
turn 2 新增 in=122 out=93 cacheRead=11136
界面上显示的「输入 22.1K」,绝大部分是缓存读取,真正的新增输入每轮只有几十到一百多。所谓「每轮烧几万」,也不成立。
最后还剩一个问题:重启会不会把缓存打掉?这个必须做干净的对照,光靠推理不行。我排了五轮,重启和不重启交替进行,其他条件全部保持一致:
| 轮次 | 条件 | 时长 | 缓存读取 |
|---|---|---|---|
| 1 | 首轮 | 3.9s | 11,008 |
| 2 | 不重启 | 5.6s | 11,264 |
| 3 | 重启后 | 3.6s | 11,520 |
| 4 | 不重启 | 3.6s | 11,776 |
| 5 | 重启后 | 3.2s | 11,904 |
缓存读取一路单调递增,没有任何跳变——重启完全影响不到它,因为缓存命中发生在模型服务那一端,杀掉本地进程够不着。时长上两组也没拉开差距,最慢的那一轮反而出现在不重启组。
到这儿结论就清楚了:无论热重载还是重启,我都得让模型再跑一轮才能触发那个工具,而那一轮的开销一模一样。热重载能省下的,只有它前面那三秒。
省下的 token 是零。 不到三成?是零。
被实验数据证伪的事不做。作废线是我自己划的,那就得自己执行。
方案二:重放,撞上第一条
第二个方案我叫它「重放」:既然会话日志里完整存着每一次工具调用的参数,那就直接拿这些参数、对着改完的代码重新跑一遍,全程不惊动模型。
这次我先查证再主张。四项技术核实全部通过:日志里的调用记录可重放、有公开的外部执行入口、走的是同一条链路、还有不经过模型的触发机制。竞品也认真查了,二十二个名字接近的仓库逐个打开读,方向确实是空的。
技术上没有拦路虎,输入输出的约定我都设计完了。
然后我问了自己一个问题:这东西到底给谁用?这个问题是我编出来的,还是真实存在的?
于是我去找了真实插件作者自己写下来的踩坑记录。他们记录的六个主要的坑,全是构建和环境问题:依赖被打包了两份、配置文件三层嵌套、Windows 上符号链接失败、压缩格式不兼容、运行时版本冲突、类型定义路径解析不了。
里面没有一条是「验证麻烦」。
更打脸的是,我们那天开局就撞了其中三个:版本冲突、压缩格式、路径解析。真正让插件作者难受的事,一整天都在我眼前反复出现,我却盯着另一个方向在设计方案。
而且「验证麻烦」这件事已经有更便宜的解法:把决策逻辑抽成纯函数,用毫秒级的单元测试盖住。
那天有个统计代码行数的小工具,算出来 13743 行。界面上看不出任何破绽,模型也一本正经地把这个数转述了出来。我用别的方式独立数了一遍:13721。多出来的 22 行,正好等于文件数量——每个文件都被多算了一行。这种 bug 就长在纯函数里,一个单测就能抓住,用不着重放,用不着模型,也用不着打开浏览器。
重放最后还剩一小块站得住的地方:纯函数测不到「接线」——配置写错了、渲染约定不对、某个字段忘了投影、依赖没声明。这些确实只有真跑起来才暴露。
可这么一收,受众就窄了一个数量级,只剩「写复杂工具、而且输出约定还在反复调整」的那一小撮人。这个生态里绝大多数插件是薄封装,写完就定型,一辈子用不上。
别人已经做得很好的事,我刚开始做的时候不做。第一步没站住,第三步跟着也就塌了。停。
自嗨的东西,GitHub 上已经够多了

顺手统计了一下这个生态里的插件仓库,一千八百多个。有一类特别有意思:六十六个仓库加起来只有一百五十来个 star。挨个翻下去——其中大约二十五个在做同一件事:显示账户余额。四个不同作者的仓库连名字都一样。
另一头,涉及权限模型和智能体组合的那一类,一千八百个仓库里只有九个。
所以拥挤和空白说明不了方向好坏。余额插件扎堆是因为它容易,难的那块空着是因为它难。
那二十五个余额插件,每一个大概都是这么来的:我遇到一点小麻烦,我顺手做了个东西解决它,我把它传上去了。中间缺的正是那三步——没人问过除了我还有谁需要,没人做过对照,更没人退一步看看架子上已经摆了二十四个一模一样的。
写这段的时候我心里有点虚。因为按我原来的路子走下去,热重载插件就会变成第二十六个。它甚至更精致一点:有明确的痛点描述,有三条论据,有完整的设计。
它唯一缺的,就是那三次计时。
人还剩下什么位置

那天收工的时候我想清楚一件事:这场协作里我不可替代的贡献是判断。
但我得把话说准。经过这一天,我不太敢说自己的判断更准——四十秒是我说的,会话归零是我记岔的,几万 token 是我想当然的。所谓更高级的判断,指的是另外四件事。
肯为判断付验证成本。 直觉人人都有,区别在于愿不愿意在动手之前花三十秒去量一下。论证那个方案我花了好几轮,推翻它只用了三次计时——验证一直比论证便宜得多,可我们总是先论证。
用同理心找真痛点。 自己觉得痛只能证明一个人痛。要证明别人痛,得去看别人自己写下来的东西。我那天最大的错,就是拿自己半天的体验去推断一整个群体的处境。
定评估方案。 什么算做对了、拿什么去量、对照组怎么排——这些定下来之后,执行完全可以交出去。那五轮交替对照,设计是我的,跑是它跑的。
知道什么时候打断。 这条最难,也最容易被低估。
Agent 有时候会钻牛角尖。它为了把某个跨度很长的指标做得好看,会变得「过度聪明」——绕远路、加中间层、把一件本来很简单的事做得特别精巧。站在它自己的局部目标上看,每一步都合理;站在「我究竟要解决什么问题」上看,它已经跑偏很久了。
我用它是为了解决问题、创造价值,方向永远比精巧重要。而方向这个东西它没有,只有我有。所以不能让它全干——我心里得非常确定自己要什么,否则连它跑偏了我都看不出来。
要判断该不该打断,前提是看得见它的轨迹。看不见轨迹,你只能看到最后那个结论,而结论总是自洽的,总是看起来挺好。
它不会自己停,因为在它的视角里一切都在顺利推进。我也不会自己停,因为在我的视角里论据一条比一条充分。停的信号得从外面来。
那天它来自一块秒表。四十秒的体感是真的,我做的只有一件事:在拿这个体感去论证一个功能之前,先量了三次。
三秒。
然后一整天的方向就变了。