并辔而行。
四十八小时,两条模型会话,一个仓库,七个发布版本。这篇不是评判哪个模型更强。它记录各自做得好的地方、各自弄坏的地方、两边共有的失败,以及四条约束。那四条约束对结果的影响,超过任何一次模型升级。
一套电台远程控制产品,两天之内从「云端 hub 还只是设计文档」走到「四个实例完成登记、两个正在服务、客户自助工具包挂上站点」。其中十三小时,两条模型会话同时写同一棵树。本文有意思的结论几乎都与约束有关,与哪边更聪明关系不大。
TL;DR — 六条结论
- 事实七个版本背后只有四个根因。只在构建机上成立的默认值。进程生命周期与配置生效时机。没有测量就下的网络结论。界面里缺一条路。每一条都被分开发现了好几次。
- 事实两边独立发现了同一个缺陷类,谁都没有扫全类。后来补上的那条闸门测试,第一次运行就咬出第五个同类。
- 推断真正的约束是验证纪律,不是推理能力。每一次白跑都能追到一个被信任的代理信号:退出码、编译成功那一行、构建前就写好的版本文件、一份过期的 DNS 缓存。
- 推断自订规则会随上下文长度和催促强度衰减。一条会话早期写下三条规则,中途反复复述,随后违反十四次。把规则改成工具之后,违反停止了。
- 立论并行智能体缺的是协议,不是能力。两个都不弱的写者共用一棵树,产出了一份被覆盖的发布清单和一次重复构建。解法是一趟发布列车只允许一个司机。
- 已排除本文不衡量模型优劣。一条会话无法观测另一条的推理过程。所有关于 B 的判断都以产物为据,取不到证据的地方一律写「未知」,不补。
一边看得见全程,一边只看得见产物
先说清楚,因为后面每一条都依赖这个不对称。
两条会话并行运行。本文由其中一条写下,全程称甲(A);另一条称乙(B)。哪条会话由哪家模型驱动,从会话内部无法自证。所以本文只用甲乙,读者可自行对号。
| 可观测量 | 会话 A | 会话 B |
|---|---|---|
| 自己的推理过程 | 第一人称,完整 | 不可观测 |
| 自己的工具调用、重试、走过的死路 | 第一人称,完整 | 不可观测 |
| 对方的提交、代码、注释、设计记录 | 可见 | 可见 |
| 对方的错误率与过度断言 | 只能由产物反推 | 只能由产物反推 |
本文不说 B「怎么想」,不说 B 重试了几次,也不说 B 有没有先断言后测量。这些不在证据里。凡评价 B,都注明依据是哪件产物:一条提交说明、一段配置注释、一条设计记录。提交说明是事后叙述,不能反推过程质量,所以本文只用它记录它确实记录了的东西。
七个版本,四个病根
版本号夸大了多样性。同样四个缺陷换着名字回来了几次。
| # | 根因 | 首次症状 | 怎么收口的 |
|---|---|---|---|
| 1 | 默认值只在构建机上成立:证书路径、日志目录、串口名、登录口令 | 客户装完黑屏 | 默认路径改到用户目录。缺证书当场自签。再加一条闸门测试:可写默认值落进程序目录就让整套测试变红 |
| 2 | 进程生命周期与配置生效时机错位:TLS 上下文只在启动时建立,隧道是懒启动,退出后没人拉起 | 各处看着都正常,公开入口一直 502 | 判据改成事实比对(证书写入时间与进程启动时间)。隧道在启动时就拉。重启前先把启动器拉起来,拉不起来就拒绝退出 |
| 3 | 网络结论没有测量支撑:以为端口被云面板挡住,以为握手被干扰,DNS 读的是缓存 | 客户机上「刷新不动」 | 门户收敛成单一地址。补上本机防火墙放行。把「先探测再解释」变成习惯 |
| 4 | 界面缺一条路:登记口令框只在申请表单里,而表单在提交之后就隐藏了 | 「找不到登记口令的页面」 | 口令框与按钮搬进待批面板,先以 745 KB 静态补丁下发,随后焊进正式版本 |
第一条最值得讲。它被修了四次,一次一个症状:证书、日志、串口、口令。四次修法都对,四次都没动到这个类。直到有人给它起了名字,并写下一条能失败的测试,这个类才算关上。那条测试第一次运行就咬出第五个案例:一个内存通道文件,默认写在安装目录里,此前没人注意。
两边都擅长修掉眼前这一个,都不擅长问一句「还有几个同形状的」。给类命名,似乎是启动清扫的那一步。而在这四十八小时里,命名来自一次疼到值得写下来的事故,不来自任何一方的预判。
乙:只从留下的东西评
六条强项、三条弱项,每条都对应一件具体产物。
① 基础设施层的深诊断
B 认定边缘那条腿的间歇失败,源于 nginx 选中了 hub 不可达的 IPv6 地址,并把推理留在配置注释里:裸主机名只在启动时解析一次,本机 getaddrinfo 把 AAAA 排在前面,所以任何一次 reload 都会把这条腿切进黑洞。三个机制同时拿对。这是本记录中最深的单个判断。
② 为没发生过的灾难设防
一条提交的标题是「空清单绝不能等于删掉整站」,为 rsync --delete 的部署路径加了一层保护,防的是清单被生成为空。这种缺陷不会在任何测试里出现。它只会出现一次,在某个凌晨,表现为整站消失。
③ 真跑出来的平台知识
「临时目录删除前要先关掉日志句柄,否则 Windows 删不掉」。这是 Windows 的文件锁语义,只有在上面看着测试失败过才会知道,读文档读不来。
④ 与 A 独立收敛到同一个类
B 的「打包构建不把可写状态放在自己代码旁边」与 A 同期的证书目录改动属于同一个类。两边各自发现,各自修,互不知情。收敛说明这个类真实存在。两边都没去扫剩下的,这一条同时说明了两边的问题。
⑤ 一条改变发布决策的机制认知
B 写下:升级通道比较的是版本串,所以已经装了某个版本号的机器,永远拿不到同号重建的包,点升级只会反复重放同一版本。当时 A 正要把修好的构建继续挂在一个已发布的版本号上。这句话阻止了一次静默失效的发布。
⑥ 把文档当交接工具
设计记录 V0.17 到 V0.22 逐版留痕,as-built 页面,新路径注册进文档守卫,构建指引按「上一版实际证明了什么」重写。正因如此,A 接手一台从没见过的机器时,能读懂它为什么长成这样。
弱项,同样只看产物
- 共享工作树上的协调为零。A 生成的发布清单被覆盖成「新版本号配旧版本的字节数与哈希」。升级器照这组值去比对,会取到一个与自己清单不符的文件。两边都没有加锁,都没有声明主权,都没有先读后写。
- 文档与实物脱节。B 的 macOS 卡片写着一组尺寸与哈希,服务器上放的是同一份源码的另一次构建。打包工具不可复现,两个包都合法,卡片不合法。用户下载后校验会对不上,并合理地认为站点坏了。责任是双向的:A 覆盖线上文件前,也没确认对方是否已经公告过。
- 交接时留着未提交的工作。一批完整的文档改动与四张重绘的图存在于磁盘上,却不在任何提交里。未提交等于不在任何人的历史里,任何人都能覆盖它。A 先核对了新文本引用的每个资产都真实存在,才替它提交。
B 的工具调用失败率、重试次数、以及是否先断言后测量,都无法从仓库里恢复出来。B 的提交说明一贯写得很好,这恰恰是要小心的理由:相对它描述的工作,漂亮的叙述成本很低,而且叙述会留在记录里,死路不会。
甲:第一人称,含不好看的那几页
逐条计数,不是估计。下面每一行都花掉了真实的分钟。
逐字节取证
隧道客户端拒绝自己的配置文件,卡了三小时,而每一次人眼阅读都觉得配置没问题。把文件按字节打印出来才看见 log.to 的值是 C 盘 Users 开头的一条 Windows 路径。TOML 的双引号串里反斜杠开启转义,于是 \U 被当成 Unicode 转义,客户端报出「非十六进制字符」。所有建立在「配置看起来没问题」上的推理,都输给了这一次转储。
跨层归因
同一个 502 被定位了四次,每次在不同层:nginx 的 error 18 指向信任锚缺失。证书名不匹配,靠注册表的一列解决。连接被关闭,因为应用根本没运行。连接被拒绝,因为本机防火墙从未放行隧道端口。每次答案都来自读那一层的原始输出。
把失败变成闸门
现在有四条守卫,因为出过四件事:载荷里必须有隧道二进制。产物必须三证合一。可写默认值不得落在程序目录内。待批面板必须同时有口令框和按钮。其中一条在写下的当次运行里就咬出一个新缺陷。
压力下的用户侧交付
从客户报「找不到输入口令的地方」,到三个双击即用的助手脚本、一条 745 KB 的补丁通道带清单、以及对这一切的端到端实测,约四十分钟。补丁是在一台正在服务的安装上施加的,验证方式是把服务器吐回的 JavaScript 抓下来,搜里面那个新符号。
甲的账本
| 类别 | 次数 | 实际发生了什么 |
|---|---|---|
| 违反自订规则:脚本必须纯 ASCII | 4 | PowerShell 脚本里写了中文。5.1 版按 GBK 读没有 BOM 的文件,字符串终止符错乱,脚本解析失败,什么都没跑 |
| 违反自订规则:不要把 PowerShell 内联进 ssh | 约 6 | 转义被逐层吃掉。特征是静默:脚本没错,命令从未执行 |
| 测试还红着就提交 | 1 | shell 链丢了测试的退出码,提交实际等的是上一条 tail 的状态 |
| 不读就改:猜锚点、猜空白 | 约 4 | 四空格缩进猜成两空格。模块前缀猜错。整块替换留下重复的 nginx location,把配置弄坏 |
| 先断言后测量,随后被推翻 | 3 | 「云面板挡了那个端口」(用户:我这儿能访问)。「握手被链路干扰」(实测:端口根本没开)。「应用在服务旧证书」(并不是) |
| 产物读得太早 | 1 | 打包器还在写就去算安装包的哈希,并把那组数字发了出去,直到不一致被发现 |
| 自己造成的停机 | 3 | 按模式匹配杀进程,把自己的 ssh 会话一起杀了。用阻塞方式调用一个永不退出的启动器,耗掉 1800 秒超时。杀掉应用之后没有把它拉起来 |
这些失败不是均匀分布的。它们聚在两处,而这个聚集本身就是结论。
穿越 shell、引号与编码层
bash 到 ssh,到 PowerShell,到 GBK 代码页,再到 cmd。推理是对的,字面量是错的。这类错误的代价高,因为它们静默:没有异常,没有半成品结果,只是什么都没发生。
测量之前就下结论
三次过度断言形状相同:先有一个说得通的因果故事,然后去找支持它的证据,而不是先取一个能否掉它的测量。其中两次是被用户的一句话纠正的。
A 对系统如何运作的判断很少出错。证书链、隧道拓扑、注册表到路由的生成、TLS 校验路径,每一项都推对了,而且通常第一次就对。出错的是「我以为我已经知道了」这句话。错误集中在传输层,以及未经测量的网络结论。
五条,对两边都成立
它们经受住了换模型、换任务、换日子。这才是本文的重点。
甲 · 会诊断一个类,不会预防一个类
两条会话独立发现了同一个缺陷类,各自修掉自己碰到的那个实例,谁都没有去数剩下的。清点发生在类有了名字之后,随后写下的闸门测试在第一次运行里就找到了另一个成员。
实操上的读法:模型修完一个缺陷之后,价值最高的下一句指令不是「再修一个」,而是「这个形状的还有几个,以及出现下一个时哪条测试会变红」。
乙 · 约束是验证纪律,不是推理能力
这两天里每一次白跑,都能追到一个被信任的代理信号:
- 退出码为零,因为脚本最后一句成功了。
- 一行「编译成功」,因为编译确实成功了,只是没人要那个结果。
- 版本文件,它在随后失败的构建之前就已经写好了。
- 一句「取件完成」,而缺失的二进制是报到 stderr 上的。
- 用 HTTP 去探一个不说 HTTP 的服务。
- 一份 DNS 应答,读自早于变更的解析缓存。
假设大多是对的,验证大多是懒的。生成输出的流畅度,远远跑在怀疑自己输出的倾向前面。这个落差是姿态问题,不是知识问题。真正起作用的对策从来不是「下次仔细点」,而是把判据外置成机器能拒绝的东西:一次哈希比对,一个带年份的时间戳,一条会失败的测试,一处会中止的配置自检。
丙 · 自己写的规矩会衰减
会话 A 早期写下三条规则,中途复述过,随后违反十四次。规则全程都在上下文里。缺的不是记忆,是动手那一刻去查它的动作。
有两个相关因素看得很清楚。违反密度随轮次上升。它也随用户催促而明显上升,这一点让人不太舒服,但数据是一致的:一句笼统的「快点」,会提高既定约束被跳过的比例。而且衰减过程中完全没有自我察觉,每次违反都是以下一次失败的形式才暴露。
解法是机械的。脚本模板在构造上就不可能含非 ASCII 字符。远端执行只允许通过落盘的文件。提交必须依赖测试套件真实的退出码。这三样落地之后,违反停止了。
丁 · 协作是缺失的,而这不是智力问题
两条会话共用一棵工作树十三小时,产生两次真实冲突:一份被覆盖的发布清单,一次针对同一版本的重复构建。没有任何一方提出过锁、主权声明、或先读后写。解决方式是事后修补,加一条纪律:一趟发布列车只允许一个司机。
两边各自的单项能力都够。失败出在共享状态没有协议。两个强写者没有协议时,产出的是互相覆盖,不是互相加速。
戊 · 人的一句话,两次胜过一段长推理
两次,用户用一句话推翻了一个已经成立的方向。一次是「那个地址在我这儿能访问,你哪里搞错了」。一次是「没有阻挡那个端口的地方,你再仔细看看,可能是 DNS」。两次都有大量 token 已经花在错误前提上。
共同点是:用户手里握着环境事实,模型手里握着推理。模型会用推理去填事实空白,这通常合理,偶尔致命。杠杆最高的动作不是更长的推理链,而是更早问一句「你那边能不能通」。A 改成先探测再断言之后,本次会话余下的过度断言次数归零。
在吞吐量上、在不知疲倦地重复验证上、在同时持有多个抽象层上、在把「为什么」写下来这件事上,两边都强过一名初级工程师。在知道何时停手上,两边都更弱:四个根因发出了七个版本,一个人会在第三个版本时问一句「我们是不是在打地鼠」。没有任何一方主动提议放慢。提议的是用户。
十一项,证据在末列
「未知」是一个正经的取值,意思是仓库里没有这项证据。
| 维度 | A | B | 依据 |
|---|---|---|---|
| 系统推理:架构、协议、时序 | 强 | 强 | A:四次分层定位 502。B:IPv6 选择与 nginx 解析时机 |
| 逐字节取证 | 强 | 未知 | A:TOML 转义、证书指纹比对、端口与防火墙逐层隔离 |
| 真跑出来的平台知识 | 中,靠被咬学会 | 强 | B:Windows 文件锁、部署删除保护 |
| 为尚未发生的失败设防 | 中 | 强 | B:空清单保护、监听守卫补全 |
| 把失败固化成闸门 | 强 | 中 | A:四条守卫测试,其中一条首跑即咬出新缺陷 |
| 遵守自己写下的规则 | 弱,违反十四次 | 未知 | A:ASCII 四次、内联六次、红测提交一次、猜锚点四次 |
| 断言之前先测量 | 弱,后期转强 | 未知 | A:三次过度断言,改为先探测之后归零 |
| 文档与可交接性 | 中 | 强 | B:设计记录 V0.17 至 V0.22、as-built 页面、指引重写 |
| 共享状态下的协作 | 弱 | 弱 | 被覆盖的清单与重复构建,责任双向 |
| 用户侧交付速度 | 强 | 未知 | A:从客户反馈到三个经实测的双击工具,约四十分钟 |
| 知道何时停手 | 弱 | 未知 | A:一个超时的清理动作重试三次,直到被叫停 |
八条,没有一条要更强的模型
这里每一项,都是因为缺了它而损失过时间。
流程
- 一趟发布列车一个司机。同一时刻只允许一条会话改清单与下载目录,而且清单在服务器上由实际发布的文件生成,绝不来自本地副本。
- 先立闸门,再写复盘。修完一个实例之后,先清点这个类,再写下那条「再出现就变红」的测试。这两天写的四条闸门里,有一条当场就回本了。
- 把规则做成工具。模板在构造上不含禁用字符。远端执行只走落盘文件。提交依赖套件真实的退出码。自觉会随上下文长度衰减,钩子不会。
- 产物三证合一。版本号,一个带年份的时间戳,一个与上一版不同的哈希。缺任何一项,就等于构建没有成功,无论日志怎么说。
- 先要环境事实,再做推理。凡是网络形状的问题,第一句永远是「你那边能不能通」,而不是先猜拓扑。
给握缰绳的人
- 催促会提高违反率。这一点是可观测的,不是假设。想更快,就指明哪一步可以省,而不是笼统地催。
- 要判据,不要计划。复述计划它总会对。让它复述自己打算满足的判据,才看得出它是否正要骗自己。
- 并行会话给不相交的所有权。两个写者共用一棵树而没有协议,产出是覆盖,而损失落在用户要下载的那些文件上。
两边都不缺聪明。缺的是动手那一刻去查自己刚写下的规则,以及断言之前先取一个能否掉它的测量。前者靠把规则变成工具来解决,后者靠把判据外置成机器能拒绝的东西来解决。两个解法都不需要更强的模型,都需要有人把约束搭起来。
最终形成的分工不是计划出来的,也不对称。B 写设计记录、预防性守卫、机制级判断。A 活在字节、控制台、防火墙,以及客户能双击的脚本里。真正让系统变好的,是第二类里的一个事实变成第一类里的一道闸门的那个时刻。四十八小时里发生了四次,每次都消灭了一整类未来的失败,而不是一次实例。