「请重启以完成卸载」
论文开篇拿 VSCode 当例子,因为它是用的人最多的可扩展编辑器之一。VSCode 的所有扩展跑在同一个共享进程里,叫扩展宿主。装扩展可以随时装,卸扩展却没法单独卸:扩展的代码一旦跑起来,想停用或卸载,就得把整个宿主重启一遍,所有扩展陪着一起重来。
有多常见?论文数了安装量前 100 的扩展:87 个带可执行代码,卸载时都得重启 p.5。剩下 13 个是主题、快捷键这类纯声明的东西,才能随手删。
VSCode 不是没给扩展留「收尾」的口子。它有个 deactivate 钩子,但只在整个宿主关闭时才调用,做不到运行中单独卸载 p.5。还有一个更隐蔽的毛病:装的时候做了什么写在一处,撤的时候该撤什么写在另一处。两份清单靠作者自己对齐,漏一行就是一个清不掉的残留。首页那个「幽灵任务」,就是这么来的。
插件之间,几乎不打交道
另一个数字更有意思:前 100 个扩展里,只有 7 个声明了对别的扩展的依赖 p.5。
不是扩展们不想合作,是平台没给好的通道。扩展主要往宿主固定的几个口子上挂东西:命令、视图、语言功能。真要互相调用,拿到的是一个没有类型的对象,对方改了接口,你这边直到出错才知道。结果就是:插件之间要么不打交道,要么提心吊胆地打交道。
论文说,这两个毛病不是 VSCode 独有的,几乎所有插件系统都有,只是程度不同。
土办法:断电重来
大部分软件对付这两个毛病,用的是现成的粗办法。某个模块出了问题,重启进程;模块之间有依赖,干脆拆成两个服务,交给容器编排去管。
能用,但很贵。每次重启都丢掉进程里攒下的东西:缓存、连接、做到一半的计算,重建一次要几秒到几分钟 p.6。为了重启期间不停服,还得多养几份副本顶着。拆成服务呢,本来一次函数调用就能办的事,变成了一次网络请求。
论文的诊断是一句话:粒度不对。重启管的是「进程」,编排管的是「服务」,可现代软件真正的组装单位,是比它们细得多的「组件」。
到了 agent 这里,问题放大一千倍
上面这些,在人写插件的世界里还能忍:一天卸几次插件,重启就重启吧。可 agent harness 不一样,它可能一边服务用户,一边给自己改零件,而且改的频率高、几乎没人盯着。
论文把后果说得很直白。拆不干净,每次自我修改都要整机重启,攒下的状态全丢;改得一频繁,停机时间越积越多,手头的任务被一遍遍打断。更糟的是,一次改坏的自我修改,可能连负责修复的那个进程也一起弄瘫 p.5。
接不稳呢?每个模块都得自己盯着它依赖的模块:什么时候出现、什么时候消失、什么时候换了一个。用土办法直接替换代码,还可能悄悄弄坏下游,或者绕出一个循环依赖,直到下次重载才炸出来。
两道考题:时间与空间
论文把「边跑边换零件」需要的能力,拆成两个互相独立的维度 p.4:

拆得干净
一个组件被拿走时,它对共享环境做过的每一个改动,都要完整、安全地撤回。
本书用回执来想:做一件事,留一张「怎么撤回」的回执。

接得稳
组件之间谁依赖谁,要能声明、能被找到;依赖一变,谁该上岗、谁该歇着,自动安排好。
本书用插座来想:进门先说清楚,自己要插哪几个插座。
为什么说这两道题新?在老式的静态程序里,它们都早有答案:一段代码执行完,变量自动收走,这是「拆得干净」;文件开头写 import,编译器帮你连好,这是「接得稳」。麻烦在于,组件一旦能在运行中来来去去,这两个老答案就都不够用了。
边跑边换零件的软件,要过两道考题:拆得干净(时间),接得稳(空间)。
请帮我检查这个项目,回答两个问题: 1. 时间:哪些代码在运行中注册了东西(定时器、事件监听、命令、全局状态),却没有对应的撤销?列出位置。 2. 空间:哪些模块依赖别的模块,却没有声明出来,而是直接假设对方已经存在?列出位置。 先只列清单,不要改代码。
