{
  "@context": "https://schema.org",
  "@id": "https://aggr.aymericbeaumet.com/items/mistral-ai/2026-09-09-modernizing-complex-legacy-code-with-ai-agents/#webpage",
  "@type": [
    "WebPage",
    "ArchiveComponent"
  ],
  "articleBody": "Legacy scientific codebases accumulate over decades, and when original authors leave, the knowledge embedded in the code becomes hard to recover. Moreover, using languages with no active developer ecosystem means missing out on the opportunity to build on top of others’ work. Mistral helped a European energy operator migrate 40,000 lines of Fortran 77 to C++, a physics-intensive reservoir simulator with no test suite and no centralized documentation.\n\n## Moving beyond code translation in legacy code modernization.\n\nTranslating syntax from one language to another is a largely solved task. Asking any recent model to translate a snippet from a non-completely-obscure language to another, will likely converge to an acceptable outcome in few iterations. However, migrating a full system from a procedural language to object-oriented C++ creates the need for architectural refactors that make the task non-trivial.\n\nFortran 77 was standardized in 1977, as the name may suggest, and code written in it reflects those constraints directly: no modules, no namespaces, no structured types. State lives in COMMON blocks—global memory shared across the entire program. Variables are implicitly typed by their first letter, so a misspelled name silently creates a new variable instead of raising a compiler error.\n\nFor a simple but illustrative example, take the following implementation of a first-degree Taylor expansion. Inputs and outputs are globals in a COMMON block, and `IC` is an integer only because its name starts with a letter between I and N. Variable names are quite cryptic, as their length is capped to 6 characters.\n\n```python\n   SUBROUTINE GASDEN   INCLUDE 'common.h'   DO 10 IC = 1, NCELL10 RHOG(IC) = ROG(IV) + DROG(IV)*(P(IC)-PTAB(IV))   END\n```\n\nIn C++, one will be able to leverage explicit types, write object-oriented code, and return a value returned instead of a global write:\n\n```javascript\ndouble gasDensity(const GasProperties& gas, double pressure) {    size_t i = lookup(gas.pressure, pressure);    return gas.density[i] + gas.slope[i] * (pressure - gas.pressure[i]);}\n```\n\nThe scattered COMMON arrays become a single `GasProperties` parameter and the grid loop moved out to the caller, so there's no line-for-line correspondence to check, which is what makes verifying the migration hard.\n\nThese structural differences, plus the requirement to integrate modern scientific computing frameworks such as PetSc, raised a few important questions even before starting the migration:\n\n- How to prove the migrated codebase matches the legacy one numerically\n\n- How to split the migration into manageable chunks\n\n- How to best use autonomous agents to speed up the process\n\n## Building a parity harness before migrating legacy code.\n\nBefore letting agents loose, we needed a way to prove the two codebases agreed. Here, \"agreement\" meant numerical equality of the outputs—both the final results and a set of critical intermediate points flagged by the client's reservoir engineers.\n\nWe added:\n\n- subroutines allowing to export the state of the Fortran codebase\n\n- a test framework to load the checkpoints into C++\n\n- [Skill.md](http://Skill.md) files to steer agents into using them correctly\n\n![](https://mistral.ai/_astro/blog-storengy-1_Z2npsCv.webp?dpl=6ad1d7b6946342e26a4d2b4f)\n\nIn the migration workflows, agents successfully instrumented the Fortran codebase to dump state snapshots and used the C++ test framework to verify correctness of migrated modules.\n\nBuilding this part of the harness first was a net-positive investment for the project: it made long agent runs safer, and numerical parity is an easy-to-verify and compelling argument to show a piece of code has been successfully migrated. We believe this should be one of the first steps for any code modernization engagement.\n\nThe example below illustrates this. We first inserted a line into the Fortran code to dump the value of the RHOG variable (42.71834 in this run), and then used that same value as the reference checkpoint when testing the migrated C++ module.\n\n![](https://mistral.ai/_astro/blog-storengy-2_eKcDf.webp?dpl=6ad1d7b6946342e26a4d2b4f)\n\n## Using AI agents to understand and document legacy codebases.\n\nThe project’s documentation was scattered across old PDFs and comments buried in the Fortran itself, and one of the largest side-wins of the whole effort was reconciling this documentation and moving it next to the code.\n\nLuckily, procedural code like Fortran has a convenient property: the whole program can be drawn as a single caller-callee tree. We generated that tree by parsing the codebase with a custom parser, then used [Vibe CLI](https://mistral.ai/products/vibe/) to spawn over a hundred agents to document it. Each agent could pull in the relevant PDFs through document libraries and [Mistral OCR](https://mistral.ai/solutions/document-ai/).\n\nStarting from the leaves of the tree and working upward, each node spawned a subagent to document it and open a PR to the original repository. A reviewer agent running in a loop on a cron schedule looked for newly opened PRs, reviewing them and scheduling fix tasks when necessary.\n\n![](https://mistral.ai/_astro/blog-storengy-3_Z2dgfSV.webp?dpl=6ad1d7b6946342e26a4d2b4f)\n\n## Utilizing the AI agents for code modernization.\n\nOn our first attempt, we gave agents full autonomy: one agent per Fortran subroutine, each translating its function to C++ independently over the course of a week. The result was functional, but it couldn't be called code modernization. COMMON blocks became global structs, one-to-one. GOTO-driven control flow stayed intact instead of being restructured into loops or early returns. It looked like Fortran retyped in C++ syntax rather than modernized code.\n\nIn the second attempt, we addressed this by giving agents structure instead of just autonomy: a planner, a coder, a tester and a code quality reviewer working together on each module. Code quality improved substantially over the first attempt. But the source code's complexity caught up with the agents eventually. They would hit a bug, attempt a few fixes, and stall, with no one available to intervene.\n\nWe landed on a middle ground: a human operating a workflow of coder, tester, and reviewer agents, migrating the codebase module by module. This preserved the code quality of the second attempt while adding a human checkpoint to unblock agents when they got stuck. The next section covers this workflow in detail.\n\n## Running structured AI agent workflows for complex code migration.\n\nWith the codebase documented and the parity harness in place, the remaining fun bit was tuning how much autonomy we could hand the agents while still getting mergeable code out. We tried both extremes, from fully autonomous runs to closely supervised manual sessions. The structured workflow below is where we landed for this use case.\n\nWorking with the client's reservoir engineers, we used the caller-callee tree to identify independent modules—self-contained subtrees of manageable size (empirically, less than ~10’000 lines of Fortran). Each module ran through the same workflow:\n\n1. Generate the target C++ architecture.\n\n2. Review it with a reservoir engineer.\n\n3. On approval, break it into a task queue.\n\n4. Run an implementation sub-workflow per task: plan → implement → test → repeat.\n\n5. A human reviews the resulting PRs and requests changes until they merge.\n\n### Recognizing the limits of AI assisted legacy code modernization.\n\nThe first sprint covered core functionality: 40,000 of 300,000 lines. The Fortran codebase was self-contained and runnable, which is a favorable starting condition. Migrations that depend on external systems, lack a runnable baseline, or encode physics documented nowhere would bring additional challenges not discussed in this post.\n\n## Applying three principles for modernizing complex legacy systems.\n\nThree lessons from this project should carry over to any large legacy migration.\n\n1. Build the parity harness before you write migration code—numerical agreement is the cheapest, most convincing proof that a module is done.\n\n2. Get the documentation in order before you lean on the agents, because you can't migrate code nobody can read.\n\n3. And at this scale, structured workflows with human review gates beat both full autonomy and hand-driven manual sessions.\n\n## We are hiring!\n\nMistral's Applied AI team is a group of engineers building full-stack solutions around Mistral models and Enterprise platform. We ship high-stakes, domain-specific solution to solve some of the world’s hardest problems.\n\nIf this is the kind of work you want to be involved in, [apply to join our team](https://mistral.ai/careers/).\n",
  "authors": [],
  "captured_at": "2026-10-19T01:26:53.998049939+00:00",
  "category": "ai",
  "content_html": "<p>Legacy scientific codebases accumulate over decades, and when original authors leave, the knowledge embedded in the code becomes hard to recover. Moreover, using languages with no active developer ecosystem means missing out on the opportunity to build on top of others’ work. Mistral helped a European energy operator migrate 40,000 lines of Fortran 77 to C++, a physics-intensive reservoir simulator with no test suite and no centralized documentation.</p>\n<h2>Moving beyond code translation in legacy code modernization.</h2>\n<p>Translating syntax from one language to another is a largely solved task. Asking any recent model to translate a snippet from a non-completely-obscure language to another, will likely converge to an acceptable outcome in few iterations. However, migrating a full system from a procedural language to object-oriented C++ creates the need for architectural refactors that make the task non-trivial.</p>\n<p>Fortran 77 was standardized in 1977, as the name may suggest, and code written in it reflects those constraints directly: no modules, no namespaces, no structured types. State lives in COMMON blocks—global memory shared across the entire program. Variables are implicitly typed by their first letter, so a misspelled name silently creates a new variable instead of raising a compiler error.</p>\n<p>For a simple but illustrative example, take the following implementation of a first-degree Taylor expansion. Inputs and outputs are globals in a COMMON block, and <code>IC</code> is an integer only because its name starts with a letter between I and N. Variable names are quite cryptic, as their length is capped to 6 characters.</p>\n<pre><code class=\"language-python\"><span class=\"code-snippet\" data-language=\"Python\"><span class=\"syntax-source syntax-python\">   <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-other syntax-constant syntax-python\">SUBROUTINE</span></span> <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-other syntax-constant syntax-python\">GASDEN</span></span>   <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-other syntax-constant syntax-python\">INCLUDE</span></span> <span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-single syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&#39;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-single syntax-python\">common.h<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&#39;</span></span></span>   <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">DO</span></span> <span class=\"syntax-constant syntax-numeric syntax-integer syntax-decimal syntax-python\">10</span> <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">IC</span></span> <span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span> <span class=\"syntax-constant syntax-numeric syntax-integer syntax-decimal syntax-python\">1</span>, <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">NCELL10</span></span> <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">RHOG</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">IC</span></span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span> <span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span> <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">ROG</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">IV</span></span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span> <span class=\"syntax-keyword syntax-operator syntax-arithmetic syntax-python\">+</span> <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">DROG</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">IV</span></span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span><span class=\"syntax-keyword syntax-operator syntax-arithmetic syntax-python\">*</span><span class=\"syntax-meta syntax-group syntax-python\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">P</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">IC</span></span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span><span class=\"syntax-keyword syntax-operator syntax-arithmetic syntax-python\">-</span><span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">PTAB</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">IV</span></span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-python\">)</span></span>   <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-other syntax-constant syntax-python\">END</span></span>\n</span></span></code></pre>\n<p>In C++, one will be able to leverage explicit types, write object-oriented code, and return a value returned instead of a global write:</p>\n<pre><code class=\"language-javascript\"><span class=\"code-snippet\" data-language=\"JavaScript\"><span class=\"syntax-source syntax-js\"><span class=\"syntax-variable syntax-other syntax-readwrite syntax-js\">double</span> <span class=\"syntax-meta syntax-function-call syntax-js\"><span class=\"syntax-variable syntax-function syntax-js\">gasDensity</span><span class=\"syntax-meta syntax-group syntax-js\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-js\">(</span><span class=\"syntax-variable syntax-other syntax-readwrite syntax-js\">const</span> <span class=\"syntax-variable syntax-other syntax-readwrite syntax-js\">GasProperties</span><span class=\"syntax-keyword syntax-operator syntax-bitwise syntax-js\">&amp;</span> <span class=\"syntax-variable syntax-other syntax-readwrite syntax-js\">gas</span><span class=\"syntax-punctuation syntax-separator syntax-comma syntax-js\">,</span> <span class=\"syntax-variable syntax-other syntax-readwrite syntax-js\">double</span> <span class=\"syntax-variable syntax-other syntax-readwrite syntax-js\">pressure</span></span><span class=\"syntax-meta syntax-group syntax-js\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-js\">)</span></span></span> <span class=\"syntax-meta syntax-block syntax-js\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-js\">{</span>    <span class=\"syntax-variable syntax-other syntax-readwrite syntax-js\">size_t</span> <span class=\"syntax-variable syntax-other syntax-readwrite syntax-js\">i</span> <span class=\"syntax-keyword syntax-operator syntax-assignment syntax-js\">=</span> <span class=\"syntax-meta syntax-function-call syntax-js\"><span class=\"syntax-variable syntax-function syntax-js\">lookup</span><span class=\"syntax-meta syntax-group syntax-js\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-js\">(</span><span class=\"syntax-variable syntax-other syntax-object syntax-js\">gas</span><span class=\"syntax-punctuation syntax-accessor syntax-js\">.</span><span class=\"syntax-meta syntax-property syntax-object syntax-js\">pressure</span><span class=\"syntax-punctuation syntax-separator syntax-comma syntax-js\">,</span> <span class=\"syntax-variable syntax-other syntax-readwrite syntax-js\">pressure</span></span><span class=\"syntax-meta syntax-group syntax-js\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-js\">)</span></span></span><span class=\"syntax-punctuation syntax-terminator syntax-statement syntax-js\">;</span>    <span class=\"syntax-keyword syntax-control syntax-flow syntax-js\">return</span> <span class=\"syntax-variable syntax-other syntax-object syntax-js\">gas</span><span class=\"syntax-punctuation syntax-accessor syntax-js\">.</span><span class=\"syntax-meta syntax-property syntax-object syntax-js\">density</span><span class=\"syntax-meta syntax-brackets syntax-js\"><span class=\"syntax-punctuation syntax-section syntax-brackets syntax-js\">[</span><span class=\"syntax-variable syntax-other syntax-readwrite syntax-js\">i</span><span class=\"syntax-punctuation syntax-section syntax-brackets syntax-js\">]</span></span> <span class=\"syntax-keyword syntax-operator syntax-arithmetic syntax-js\">+</span> <span class=\"syntax-variable syntax-other syntax-object syntax-js\">gas</span><span class=\"syntax-punctuation syntax-accessor syntax-js\">.</span><span class=\"syntax-meta syntax-property syntax-object syntax-js\">slope</span><span class=\"syntax-meta syntax-brackets syntax-js\"><span class=\"syntax-punctuation syntax-section syntax-brackets syntax-js\">[</span><span class=\"syntax-variable syntax-other syntax-readwrite syntax-js\">i</span><span class=\"syntax-punctuation syntax-section syntax-brackets syntax-js\">]</span></span> <span class=\"syntax-keyword syntax-operator syntax-arithmetic syntax-js\">*</span> <span class=\"syntax-meta syntax-group syntax-js\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-js\">(</span><span class=\"syntax-variable syntax-other syntax-readwrite syntax-js\">pressure</span> <span class=\"syntax-keyword syntax-operator syntax-arithmetic syntax-js\">-</span> <span class=\"syntax-variable syntax-other syntax-object syntax-js\">gas</span><span class=\"syntax-punctuation syntax-accessor syntax-js\">.</span><span class=\"syntax-meta syntax-property syntax-object syntax-js\">pressure</span><span class=\"syntax-meta syntax-brackets syntax-js\"><span class=\"syntax-punctuation syntax-section syntax-brackets syntax-js\">[</span><span class=\"syntax-variable syntax-other syntax-readwrite syntax-js\">i</span><span class=\"syntax-punctuation syntax-section syntax-brackets syntax-js\">]</span></span><span class=\"syntax-punctuation syntax-section syntax-group syntax-js\">)</span></span><span class=\"syntax-punctuation syntax-terminator syntax-statement syntax-js\">;</span><span class=\"syntax-punctuation syntax-section syntax-block syntax-js\">}</span></span>\n</span></span></code></pre>\n<p>The scattered COMMON arrays become a single <code>GasProperties</code> parameter and the grid loop moved out to the caller, so there's no line-for-line correspondence to check, which is what makes verifying the migration hard.</p>\n<p>These structural differences, plus the requirement to integrate modern scientific computing frameworks such as PetSc, raised a few important questions even before starting the migration:</p>\n<ul>\n<li>\n<p>How to prove the migrated codebase matches the legacy one numerically</p>\n</li>\n<li>\n<p>How to split the migration into manageable chunks</p>\n</li>\n<li>\n<p>How to best use autonomous agents to speed up the process</p>\n</li>\n</ul>\n<h2>Building a parity harness before migrating legacy code.</h2>\n<p>Before letting agents loose, we needed a way to prove the two codebases agreed. Here, &quot;agreement&quot; meant numerical equality of the outputs—both the final results and a set of critical intermediate points flagged by the client's reservoir engineers.</p>\n<p>We added:</p>\n<ul>\n<li>\n<p>subroutines allowing to export the state of the Fortran codebase</p>\n</li>\n<li>\n<p>a test framework to load the checkpoints into C++</p>\n</li>\n<li>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"http://Skill.md\">Skill.md</a> files to steer agents into using them correctly</p>\n</li>\n</ul>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://mistral.ai/_astro/blog-storengy-1_Z2npsCv.webp?dpl=6ad1d7b6946342e26a4d2b4f\" alt=\"\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>In the migration workflows, agents successfully instrumented the Fortran codebase to dump state snapshots and used the C++ test framework to verify correctness of migrated modules.</p>\n<p>Building this part of the harness first was a net-positive investment for the project: it made long agent runs safer, and numerical parity is an easy-to-verify and compelling argument to show a piece of code has been successfully migrated. We believe this should be one of the first steps for any code modernization engagement.</p>\n<p>The example below illustrates this. We first inserted a line into the Fortran code to dump the value of the RHOG variable (42.71834 in this run), and then used that same value as the reference checkpoint when testing the migrated C++ module.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://mistral.ai/_astro/blog-storengy-2_eKcDf.webp?dpl=6ad1d7b6946342e26a4d2b4f\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<h2>Using AI agents to understand and document legacy codebases.</h2>\n<p>The project’s documentation was scattered across old PDFs and comments buried in the Fortran itself, and one of the largest side-wins of the whole effort was reconciling this documentation and moving it next to the code.</p>\n<p>Luckily, procedural code like Fortran has a convenient property: the whole program can be drawn as a single caller-callee tree. We generated that tree by parsing the codebase with a custom parser, then used <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://mistral.ai/products/vibe/\">Vibe CLI</a> to spawn over a hundred agents to document it. Each agent could pull in the relevant PDFs through document libraries and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://mistral.ai/solutions/document-ai/\">Mistral OCR</a>.</p>\n<p>Starting from the leaves of the tree and working upward, each node spawned a subagent to document it and open a PR to the original repository. A reviewer agent running in a loop on a cron schedule looked for newly opened PRs, reviewing them and scheduling fix tasks when necessary.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://mistral.ai/_astro/blog-storengy-3_Z2dgfSV.webp?dpl=6ad1d7b6946342e26a4d2b4f\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<h2>Utilizing the AI agents for code modernization.</h2>\n<p>On our first attempt, we gave agents full autonomy: one agent per Fortran subroutine, each translating its function to C++ independently over the course of a week. The result was functional, but it couldn't be called code modernization. COMMON blocks became global structs, one-to-one. GOTO-driven control flow stayed intact instead of being restructured into loops or early returns. It looked like Fortran retyped in C++ syntax rather than modernized code.</p>\n<p>In the second attempt, we addressed this by giving agents structure instead of just autonomy: a planner, a coder, a tester and a code quality reviewer working together on each module. Code quality improved substantially over the first attempt. But the source code's complexity caught up with the agents eventually. They would hit a bug, attempt a few fixes, and stall, with no one available to intervene.</p>\n<p>We landed on a middle ground: a human operating a workflow of coder, tester, and reviewer agents, migrating the codebase module by module. This preserved the code quality of the second attempt while adding a human checkpoint to unblock agents when they got stuck. The next section covers this workflow in detail.</p>\n<h2>Running structured AI agent workflows for complex code migration.</h2>\n<p>With the codebase documented and the parity harness in place, the remaining fun bit was tuning how much autonomy we could hand the agents while still getting mergeable code out. We tried both extremes, from fully autonomous runs to closely supervised manual sessions. The structured workflow below is where we landed for this use case.</p>\n<p>Working with the client's reservoir engineers, we used the caller-callee tree to identify independent modules—self-contained subtrees of manageable size (empirically, less than ~10’000 lines of Fortran). Each module ran through the same workflow:</p>\n<ol>\n<li>\n<p>Generate the target C++ architecture.</p>\n</li>\n<li>\n<p>Review it with a reservoir engineer.</p>\n</li>\n<li>\n<p>On approval, break it into a task queue.</p>\n</li>\n<li>\n<p>Run an implementation sub-workflow per task: plan → implement → test → repeat.</p>\n</li>\n<li>\n<p>A human reviews the resulting PRs and requests changes until they merge.</p>\n</li>\n</ol>\n<h3>Recognizing the limits of AI assisted legacy code modernization.</h3>\n<p>The first sprint covered core functionality: 40,000 of 300,000 lines. The Fortran codebase was self-contained and runnable, which is a favorable starting condition. Migrations that depend on external systems, lack a runnable baseline, or encode physics documented nowhere would bring additional challenges not discussed in this post.</p>\n<h2>Applying three principles for modernizing complex legacy systems.</h2>\n<p>Three lessons from this project should carry over to any large legacy migration.</p>\n<ol>\n<li>\n<p>Build the parity harness before you write migration code—numerical agreement is the cheapest, most convincing proof that a module is done.</p>\n</li>\n<li>\n<p>Get the documentation in order before you lean on the agents, because you can't migrate code nobody can read.</p>\n</li>\n<li>\n<p>And at this scale, structured workflows with human review gates beat both full autonomy and hand-driven manual sessions.</p>\n</li>\n</ol>\n<h2>We are hiring!</h2>\n<p>Mistral's Applied AI team is a group of engineers building full-stack solutions around Mistral models and Enterprise platform. We ship high-stakes, domain-specific solution to solve some of the world’s hardest problems.</p>\n<p>If this is the kind of work you want to be involved in, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://mistral.ai/careers/\">apply to join our team</a>.</p>\n",
  "content_markdown": "Legacy scientific codebases accumulate over decades, and when original authors leave, the knowledge embedded in the code becomes hard to recover. Moreover, using languages with no active developer ecosystem means missing out on the opportunity to build on top of others’ work. Mistral helped a European energy operator migrate 40,000 lines of Fortran 77 to C++, a physics-intensive reservoir simulator with no test suite and no centralized documentation.\n\n## Moving beyond code translation in legacy code modernization.\n\nTranslating syntax from one language to another is a largely solved task. Asking any recent model to translate a snippet from a non-completely-obscure language to another, will likely converge to an acceptable outcome in few iterations. However, migrating a full system from a procedural language to object-oriented C++ creates the need for architectural refactors that make the task non-trivial.\n\nFortran 77 was standardized in 1977, as the name may suggest, and code written in it reflects those constraints directly: no modules, no namespaces, no structured types. State lives in COMMON blocks—global memory shared across the entire program. Variables are implicitly typed by their first letter, so a misspelled name silently creates a new variable instead of raising a compiler error.\n\nFor a simple but illustrative example, take the following implementation of a first-degree Taylor expansion. Inputs and outputs are globals in a COMMON block, and `IC` is an integer only because its name starts with a letter between I and N. Variable names are quite cryptic, as their length is capped to 6 characters.\n\n```python\n   SUBROUTINE GASDEN   INCLUDE 'common.h'   DO 10 IC = 1, NCELL10 RHOG(IC) = ROG(IV) + DROG(IV)*(P(IC)-PTAB(IV))   END\n```\n\nIn C++, one will be able to leverage explicit types, write object-oriented code, and return a value returned instead of a global write:\n\n```javascript\ndouble gasDensity(const GasProperties& gas, double pressure) {    size_t i = lookup(gas.pressure, pressure);    return gas.density[i] + gas.slope[i] * (pressure - gas.pressure[i]);}\n```\n\nThe scattered COMMON arrays become a single `GasProperties` parameter and the grid loop moved out to the caller, so there's no line-for-line correspondence to check, which is what makes verifying the migration hard.\n\nThese structural differences, plus the requirement to integrate modern scientific computing frameworks such as PetSc, raised a few important questions even before starting the migration:\n\n- How to prove the migrated codebase matches the legacy one numerically\n\n- How to split the migration into manageable chunks\n\n- How to best use autonomous agents to speed up the process\n\n## Building a parity harness before migrating legacy code.\n\nBefore letting agents loose, we needed a way to prove the two codebases agreed. Here, \"agreement\" meant numerical equality of the outputs—both the final results and a set of critical intermediate points flagged by the client's reservoir engineers.\n\nWe added:\n\n- subroutines allowing to export the state of the Fortran codebase\n\n- a test framework to load the checkpoints into C++\n\n- [Skill.md](http://Skill.md) files to steer agents into using them correctly\n\n![](https://mistral.ai/_astro/blog-storengy-1_Z2npsCv.webp?dpl=6ad1d7b6946342e26a4d2b4f)\n\nIn the migration workflows, agents successfully instrumented the Fortran codebase to dump state snapshots and used the C++ test framework to verify correctness of migrated modules.\n\nBuilding this part of the harness first was a net-positive investment for the project: it made long agent runs safer, and numerical parity is an easy-to-verify and compelling argument to show a piece of code has been successfully migrated. We believe this should be one of the first steps for any code modernization engagement.\n\nThe example below illustrates this. We first inserted a line into the Fortran code to dump the value of the RHOG variable (42.71834 in this run), and then used that same value as the reference checkpoint when testing the migrated C++ module.\n\n![](https://mistral.ai/_astro/blog-storengy-2_eKcDf.webp?dpl=6ad1d7b6946342e26a4d2b4f)\n\n## Using AI agents to understand and document legacy codebases.\n\nThe project’s documentation was scattered across old PDFs and comments buried in the Fortran itself, and one of the largest side-wins of the whole effort was reconciling this documentation and moving it next to the code.\n\nLuckily, procedural code like Fortran has a convenient property: the whole program can be drawn as a single caller-callee tree. We generated that tree by parsing the codebase with a custom parser, then used [Vibe CLI](https://mistral.ai/products/vibe/) to spawn over a hundred agents to document it. Each agent could pull in the relevant PDFs through document libraries and [Mistral OCR](https://mistral.ai/solutions/document-ai/).\n\nStarting from the leaves of the tree and working upward, each node spawned a subagent to document it and open a PR to the original repository. A reviewer agent running in a loop on a cron schedule looked for newly opened PRs, reviewing them and scheduling fix tasks when necessary.\n\n![](https://mistral.ai/_astro/blog-storengy-3_Z2dgfSV.webp?dpl=6ad1d7b6946342e26a4d2b4f)\n\n## Utilizing the AI agents for code modernization.\n\nOn our first attempt, we gave agents full autonomy: one agent per Fortran subroutine, each translating its function to C++ independently over the course of a week. The result was functional, but it couldn't be called code modernization. COMMON blocks became global structs, one-to-one. GOTO-driven control flow stayed intact instead of being restructured into loops or early returns. It looked like Fortran retyped in C++ syntax rather than modernized code.\n\nIn the second attempt, we addressed this by giving agents structure instead of just autonomy: a planner, a coder, a tester and a code quality reviewer working together on each module. Code quality improved substantially over the first attempt. But the source code's complexity caught up with the agents eventually. They would hit a bug, attempt a few fixes, and stall, with no one available to intervene.\n\nWe landed on a middle ground: a human operating a workflow of coder, tester, and reviewer agents, migrating the codebase module by module. This preserved the code quality of the second attempt while adding a human checkpoint to unblock agents when they got stuck. The next section covers this workflow in detail.\n\n## Running structured AI agent workflows for complex code migration.\n\nWith the codebase documented and the parity harness in place, the remaining fun bit was tuning how much autonomy we could hand the agents while still getting mergeable code out. We tried both extremes, from fully autonomous runs to closely supervised manual sessions. The structured workflow below is where we landed for this use case.\n\nWorking with the client's reservoir engineers, we used the caller-callee tree to identify independent modules—self-contained subtrees of manageable size (empirically, less than ~10’000 lines of Fortran). Each module ran through the same workflow:\n\n1. Generate the target C++ architecture.\n\n2. Review it with a reservoir engineer.\n\n3. On approval, break it into a task queue.\n\n4. Run an implementation sub-workflow per task: plan → implement → test → repeat.\n\n5. A human reviews the resulting PRs and requests changes until they merge.\n\n### Recognizing the limits of AI assisted legacy code modernization.\n\nThe first sprint covered core functionality: 40,000 of 300,000 lines. The Fortran codebase was self-contained and runnable, which is a favorable starting condition. Migrations that depend on external systems, lack a runnable baseline, or encode physics documented nowhere would bring additional challenges not discussed in this post.\n\n## Applying three principles for modernizing complex legacy systems.\n\nThree lessons from this project should carry over to any large legacy migration.\n\n1. Build the parity harness before you write migration code—numerical agreement is the cheapest, most convincing proof that a module is done.\n\n2. Get the documentation in order before you lean on the agents, because you can't migrate code nobody can read.\n\n3. And at this scale, structured workflows with human review gates beat both full autonomy and hand-driven manual sessions.\n\n## We are hiring!\n\nMistral's Applied AI team is a group of engineers building full-stack solutions around Mistral models and Enterprise platform. We ship high-stakes, domain-specific solution to solve some of the world’s hardest problems.\n\nIf this is the kind of work you want to be involved in, [apply to join our team](https://mistral.ai/careers/).\n",
  "dateCreated": "2026-10-19T01:26:53.998049939+00:00",
  "date_created": "2026-09-09T12:00:46+00:00",
  "date_modified": "2026-09-09T12:00:46+00:00",
  "date_published": "2026-09-09T12:00:46+00:00",
  "description": "Mistral helped a European energy operator migrate 40,000 lines of Fortran 77 to C++. Learn how it was done, and the lessons to carry forward.",
  "external_url": "https://mistral.ai/news/legacy-code-modernization/",
  "git": {
    "commit": "ebb0941c5c06a2ab2377342d607535262a3e969e",
    "path": "items/mistral-ai/2026/09/2026-09-09-modernizing-complex-legacy-code-with-ai-agents.md",
    "permalink": "https://github.com/aymericbeaumet/aggr-instance/blob/ebb0941c5c06a2ab2377342d607535262a3e969e/items/mistral-ai/2026/09/2026-09-09-modernizing-complex-legacy-code-with-ai-agents.md"
  },
  "id": "urn:aggr:item:1d172a6db09f02d71f6e653f3dd85333138af284",
  "isBasedOn": {
    "@id": "https://mistral.ai/news/legacy-code-modernization/"
  },
  "mainEntity": {
    "@id": "https://mistral.ai/news/legacy-code-modernization/",
    "@type": "CreativeWork",
    "archivedAt": {
      "@id": "https://aggr.aymericbeaumet.com/items/mistral-ai/2026-09-09-modernizing-complex-legacy-code-with-ai-agents/#webpage"
    },
    "dateModified": "2026-09-09T12:00:46+00:00",
    "datePublished": "2026-09-09T12:00:46+00:00",
    "headline": "Modernizing complex legacy code with AI agents.",
    "timeRequired": "PT6M",
    "url": "https://mistral.ai/news/legacy-code-modernization/",
    "wordCount": 1303
  },
  "name": "Archived snapshot: Modernizing complex legacy code with AI agents.",
  "preview": null,
  "reading_minutes": 6,
  "replicated_at": null,
  "source": "mistral-ai",
  "source_name": "Mistral Blog",
  "summary": "Mistral helped a European energy operator migrate 40,000 lines of Fortran 77 to C++. Learn how it was done, and the lessons to carry forward.",
  "tags": [],
  "temporalCoverage": "2026-10-19T01:26:53.998049939+00:00",
  "title": "Modernizing complex legacy code with AI agents.",
  "url": "https://aggr.aymericbeaumet.com/items/mistral-ai/2026-09-09-modernizing-complex-legacy-code-with-ai-agents/",
  "word_count": 1303
}