Mistral 实际发布了什么
文章开头的一句话关乎人,而非工具:
“遗留的科学代码库是几十年来积累起来的,当最初的作者离开后,嵌入在代码中的知识就变得难以恢复。”
正如文章所指出的,Fortran 77 “正如其名,是在 1977 年标准化的,用它编写的代码直接反映了这些限制:没有模块,没有命名空间,没有结构化类型。状态存在于 COMMON 块中 —— 跨整个程序共享的全局内存。” 变量名上限为 6 个字符。拼写错误的名称会静默创建一个新变量,而不是抛出错误。
Mistral 在开始前提出了三个问题:“如何证明迁移后的代码库在数值上与遗留代码库相匹配”、“如何将迁移拆分为可管理的分块”,以及“如何最好地利用自主 Agent 来加速这一过程”。请注意顺序。等效性证明排在第一位,Agent 策略排在第三位。
证明机制是一个对等测试框架(parity harness)。团队添加了“允许导出 Fortran 代码库状态的子程序”、“一个将检查点加载到 C++ 中的测试框架”,以及“用于引导 Agent 正确使用它们的 Skill.md 文件”。他们的评估是:“首先构建这部分测试框架对项目来说是一笔净正收益的投资:它让长时间运行的 Agent 更加安全,而且数值对等是一个易于验证且令人信服的论据,可以证明一段代码已被成功迁移。”
然后是文档整理阶段。该项目现有的文档“散落在旧的 PDF 和埋在 Fortran 本身的注释中”。Mistral “通过使用自定义解析器解析代码库”生成了调用者-被调用者树(caller-callee tree),然后“使用 Vibe CLI 启动了 100 多个 Agent 来对其进行文档化”,从树的叶子节点向上工作,每个节点都会发起一个 pull request。他们这样描述结果:
“整个工作最大的附带收获之一是整理了这些文档,并将其移到了代码旁边。”
迁移本身尝试了三次。第一次:“我们给予了 Agent 充分的自主权:每个 Fortran 子程序分配一个 Agent,每个 Agent 在一周的时间里独立将其函数翻译为 C++。结果是可运行的,但不能称之为代码现代化。COMMON 块变成了一对一的全局结构体。由 GOTO 驱动的控制流保持原样,而不是被重构为循环或提前返回。它看起来像是用 C++ 语法重新输入的 Fortran,而不是现代化的代码。”
第二次尝试给 Agent 赋予了结构 —— “一个规划器(planner)、一个编码器(coder)、一个测试器(tester)和一个代码质量审查器(reviewer)在每个模块上协同工作。” 质量有所提高,但是:“源代码的复杂性最终还是让 Agent 难以招架。它们会遇到 Bug,尝试几次修复,然后陷入停滞,而没有人能够介入。”
最终交付的是折中方案:“由人类操作一个由编码器、测试器和审查器 Agent 组成的工作流,逐个模块地迁移代码库。”
这三个教训逐字记录如下:首先构建对等测试框架,因为“数值一致是证明模块完成最便宜、最令人信服的证据”;“在依赖 Agent 之前先整理好文档,因为你无法迁移没人能读懂的代码”;以及“在这种规模下,带有肉眼审查关卡的结构化工作流击败了完全自主和纯手动操作。”
这改变了什么,又没有改变什么
这并不是说 Agent 无法迁移遗留代码。它们确实做到了,而且 Mistral 自己的定位是语法翻译“在很大程度上是一个已解决的任务”。难点在于架构:从过程式代码转向面向对象的 C++ 意味着“没有行对行的对应关系可供检查,这也是验证迁移困难的原因。”
这也无法推广到每一个遗留系统。Mistral 直接表示:“Fortran 代码库是自包含且可运行的,这是一个有利的初始条件。依赖于外部系统、缺乏可运行基线或编码了无处可查的物理原理的迁移,将会带来本文未讨论的额外挑战。”
慢慢读最后那句话。无处可查的物理原理。 Mistral 将其自身方法的边界定义为知识从未被记录下来的情况。不是记录得不好。不是过时。而是根本不存在。
这篇文章真正改变的是优先级。这个项目的瓶颈约束不是 Agent 的能力。而是代码背后的原因是否以某种任何工具都能读取的形式存在 —— 以及这些原因一旦被恢复,最终会保存在哪里。
人们会从中得出什么结论,以及不应该得出什么结论
目前已经流传着三种解读,而每一种都遗漏了关键部分。
“在你的遗留代码库上启动 100 个 Agent。” 这 100 个 Agent 之所以能工作,是因为自定义解析器已经生成了一个调用者-被调用者树来分配它们,并且因为 Mistral 可以利用过程式代码的一个特性:“整个程序可以绘制为单个调用者-被调用者树。” 如果没有这种结构,就无法进行分发。Agent 是最后一步,而不是第一步。
“文档是一个不错的副产品。” Mistral 称其为“附带收获”,但他们自己的教训列表将其列为先决条件 —— 在依赖 Agent 之前先整理好它。一个你必须首先产生的副产品,其实是一个伪装得更好的先决条件。
“Agent 可以重建丢失的知识。” 它们是从仍然存在的 PDF 和代码注释中重建了文档。在什么都不存在的地方,Mistral 将其标记为超出范围。这也是另外两家厂商用自己的话划定的相同边界。Cline 将其 Memory Bank 描述为“一种文档方法论,将 Cline 从无状态助手转变为持久的开发合作伙伴” —— 一种方法论,意味着这是人要做的事情,而不是工具推导出来的东西。OpenHands 明确地将两者分开:“保留 AGENTS.md 用于针对在仓库中工作的任何 Agent 的指令 —— 记忆(memory)则是为了 Agent 自身学到的东西。” 三个独立的厂商,用三种方式说明了书面意图和推导出的知识是不同的材料。
这 spin 并不是对它们中任何一个的批评。这就是问题的本质。
如果您想了解我们已经涵盖的邻近案例:关于“人走知识留不住”的通用版本,请参阅当有人离职时,AI 上下文会发生什么;而将现有文档转换为 Agent 实际会参考的内容的机制,请参阅将项目文档转化为 AI 记忆。本文比这两者都要窄:它关于迁移所创建的记录,以及该记录在迁移后是否还能存活。
解决方案:将决策记录在下一个项目可以读取的地方
Mistral 的文档整理恢复了代码 and PDF 中仍然包含的内容。它无法恢复的类别 —— 并且被命名为其自身的限制 —— 是值得提前规划的类别。以下是在开始前如何区分这两者的方法。
步骤 1:根据解析器是否可以重新生成来对知识进行分类
拿出一个模块,列出新工程师需要的一切。然后标记每个条目,说明仅读取代码库的工具是否可以生成它。
调用图、类型签名、哪个函数调用了哪个函数、子程序在机械上做了什么:这些是可重新生成的。Mistral 在大规模上证明了这一点 —— 自定义解析器加上 Agent 从一个没有集中文档的代码库中准确地重建了这类知识。
现在看另一栏。为什么选择这个求解器而不是显而易见的替代方案。油藏工程师说这两个循环中的哪一个必须保持位一致(bit-identical)。为什么之前的移植尝试在 2019 年被放弃。由“客户的油藏工程师标记的中间点”中,哪些是重要的,哪些是次要的 —— Mistral 提到这些检查点来自工程师,而不是来自代码。代码库中没有任何内容说明这些。
第二栏才是你真正的资产清单。第一栏中的所有内容都只是构建产物。
步骤 2:在原因被说出口的瞬间将其捕获
第二栏中的原因是在工作过程中浮现的,而不是在工作之前。它们出现在审查评论中、与领域专家的通话中,或者在 Agent 停滞时某人说的话中。Mistral 的第二次尝试最能说明这一点:Agent “会遇到 Bug,尝试几次修复,然后陷入停滞,而没有人能够介入。” 解除停滞的介入恰恰是第二栏的事实 —— 它是被说出来的,而不是被提交(commit)的。
因此,要在那个时刻进行捕获。当人类解除 Agent 的阻塞时,写下一行:Agent 得出了什么结论,实际情况是什么,以及原因。当领域专家否决某项设计时,将否决及其原因记录在设计旁边,而不是记录在会在一周内关闭的 pull request 线程中。
这与我们在当厂商自己的解释变得不那么可靠时,你应该写下什么中描述的原则相同:持久的产物是决策及其合理性证明,而不是产生它的对话记录。
步骤 3:给记录一个不属于该项目的归宿
Mistral 的文档被放在了“代码旁边”,这是正确的第一步 —— 将 pull request 提交到原始代码库中,并由 Agent 按照定时任务(cron schedule)进行审查。代码库文件是持久的、有版本的,并且可以随代码移动。
但迁移会产生两种寿命不同的书面记录。模块文档描述了代码,因此它属于代码,如果代码再次被替换,它也会随之消亡。而决策 —— 为什么采用这种架构、哪些约束是不可妥协的、领域专家排除了什么 —— 比 Fortran 和 C++ 活得都长。它们是下一个项目将会提出的问题的答案。
这些决策需要一个不属于单个代码库中某个目录的归宿。技能文件是流程的良好载体,这就是为什么 Mistral 编写了“Skill.md 文件来引导 Agent 正确使用它们” —— 但流程并不是原因,我们在为什么 Agent 技能不是记忆中详细阐述了这一点。而且指令文件有其自身的范围:正如我们在编程 Agent 实际上在读什么中发现的那样,Agent 读取的是它们的指令文件,而不是你的文档树。
在 MemoryLake 中进行设置
共享决策层是不属于任何单一代码库的部分。MemoryLake 保存了第二栏的事实 —— 裁决、约束、被放弃的方法及其原因 —— 这样每个参与迁移的 Agent 都能读取相同的内容,并且在项目结束时,这些内容依然存在。设置只需三个步骤:在此开始。
步骤 1:创建 API 密钥
为迁移创建一个工作区并生成一个 API 密钥。将一个工作区的范围限定在项目程序上,而不是单个代码库 —— 源树和目标树是两个代码库,而决策适用于这两者。

步骤 2:上传您的第一批记忆
从人工介入开始。每次人类解除 Agent 的阻塞时,该交流都包含一个代码未声明的事实。接下来添加领域专家的裁决,然后是检查点列表以及选择每个检查点的原因。PDF、会议记录和审查线程可以直接导入;您不需要先将它们转换为 markdown。

步骤 3:连接您的 AI 和 Agent
连接实际执行工作的 Agent —— 编码器、测试器、审查器。每一个都读取相同的决策集,因此审查器 Agent 可以标记出与编码器从未见过的约束相冲突的更改。这就是结构化工作流通过人工介入手动弥补的差距;而共享层在人类不得不介入之前就弥补了其中的一部分。

这在实践中改变了什么
您的第一阶段冲刺不再产生只存在于三个人脑海中的知识。当一个模块被移交给新的 Agent 工作流时,约束会随之而来。
第二次尝试的失败模式变得成本更低。Agent 会在同一类问题上反复停滞 —— 数值约定、隐式契约、物理假设。第一次停滞需要人工介入。随后在相同事实上的停滞则无需任何成本,因为该事实现在是可读的。
而且记录在项目结束后依然存在。Mistral 的第一阶段冲刺完成了 300,000 行中的 40,000 行,这意味着还有五个冲刺阶段,很可能会有不同的人参与。第六阶段冲刺是从已知原因开始,还是重新发现它们,取决于您现在把它们放在哪里。
保留迁移所获教训的最佳实践
在编写迁移代码之前编写对等测试框架。Mistral 最强有力的主张是,数值一致是“证明模块完成最便宜、最令人信服的证据”。测试框架也是关于“正确”意味着什么的物理记录 —— 这是项目将产生的最具复用价值的文档。
将可重新生成和不可重新生成的知识保存在不同的地方。将它们混在一起意味着每次重新生成都有可能覆盖无法重新生成的部分。这与我们在在大型证明工作中,Agent 如何记录已经真实存在的事实中描述的陷阱相同:持久的价值在于书面记录,而不是产生它的运行过程。
记录否决,而不仅仅是决策。“我们选择了 PetSc”不如“我们在排除了两个替代方案后选择了 PetSc,原因如下”有说服力。被排除的选项正是阻止下一个 Agent 再次提出它们的原因。
为领域事实标注日期。油藏工程师在 9 月份提出的约束可能会在 3 月份被取代。未标注日期的约束要么会被永远遵守,要么会被完全忽略。
将人工审查关卡视为源头,而不仅仅是控制手段。Mistral 的第三个教训是,关卡胜过完全自主。每个关卡也是一个人陈述代码库中不包含的内容的时刻 —— 在那里捕获它。
结论
Mistral 的报告将被解读为一个关于 Agent 迁移遗留代码的故事,在某种程度上确实如此。但他们自己的三个教训将测试框架和文档整理置于 Agent 策略之前,并且他们为自己的方法划定的边界是“编码了无处可查的物理原理”的代码。
Agent 让机械性的工作变得廉价。但它们并没有让未记录的意图变得可恢复,Mistral 也没有声称可以做到这一点。迁移将会结束。C++ 最终也会变成遗留代码。能够传承下去的是有人选择写下来的那部分 —— 以及他们把它放在了哪里。