Engineering field log · github.com/mparrett/fmpl

The Revival, Revived, in four tiers

Revitalizing an agent-written codebase: 24 commits from a repo that wouldn’t build on a fresh clone to a published fork with CI, executable claims, and an honest ledger of its own gaps.

2 days, 2026-07-20–21 24 commits (PR #1) 4 tiers ~30k lines sidelined, 0 deleted build broken → CI green

This log is the prequel to Closing the Loop and the making-of behind the engineering tour. FMPL itself is Norman Nunley’s Rust-built descendant of a 1992 MUD language; this is the log of reviving his revival.

§1

What the machine left behind

The repo we inherited was written almost entirely by an autonomous agent loop — “ralph,” set up and steered by the project’s creator, Norman Nunley, iterating against a roadmap for months. What it produced is genuinely impressive: a working language with a bytecode VM, a PEG grammar engine, an object system, and a parser that generates itself. What it also produced is the sediment of its own process: a 107-file .agent/ memory system, 41 files of “superpowers” process documentation, ralph driver scripts, run logs, a bespoke .git-issue/ tracker, and prompt files — roughly 30,000 lines of machinery about building the thing, interleaved with the thing.

And the repo did not build. Not subtly: cargo build failed at manifest resolution, before compiling a line, because one workspace member had a path dependency on execution_tape — a “verified bytecode VM” crate that existed on exactly one machine, and this wasn’t it. Cargo resolves every member’s manifest before building any of them, so the missing path failed everything, including cargo build -p fmpl-cli. The agent never noticed because the agent never had to do a fresh clone.

The first day was recon before surgery: a static read plus live runs of the REPL, TUI, and web app, written up as findings before any code changed. That ordering was deliberate. With an inherited codebase — especially an agent-inherited one — the temptation is to start fixing what you see; the discipline is to first record what is actually true, because you will not get a second chance to observe the artifact undisturbed.

§2

Keep, sideline, delete

The central judgment call of the whole rehabilitation: what to do with the agent’s machinery. Deleting it would have been clean and irreversible — and wrong twice over. The harness is part of the project’s story (the tour says plainly that the implementation is agent-written), and it may be useful again if an autonomous loop resumes. Keeping it in place was also wrong: a public repo where the first thing a visitor meets is the scaffolding, not the building.

The answer was a third category. Everything the agent needed and a human doesn’t was moved — intact, in one commit — to an archive/agent-harness branch. Nothing was deleted; the main branch simply stopped carrying it. The same commit unified the agent’s CLAUDE.md into a single human-and-agent AGENTS.md, later split again into lean workflow rules plus a DEV.md inventory. The test of the triage: in the days since, nobody has needed the process machinery back from the archive — though agent-era feature work off the same lineage (the ITER-0005 persistence arc — durable TupleSpace, content-addressed recovery, bytecode persistence — on branches this fork predated) was since grafted onto main and now underpins active design. The scaffolding was disposable; the code beneath it was not. Keeping the archive costs nothing, and it makes the history honest.

Field note · nothing is deleted

“Sideline, don’t delete” became the arc’s standing rule, and it echoes forward: the pre-rehab branch freezes the untouched inherited tip as PR #1’s base, the advisory-PR convention pins every later phase boundary the same way, and the dormant cross_compile feature keeps the execution_tape integration described in comments rather than erased. A revitalization you can diff against its “before” is one you can trust.

§3

Four tiers, one stack

The work was planned as tiers with a deliberate order — each one meaningless without the one below it — and built as a stack of branches, each on the previous, so the whole thing could be reviewed bottom-up and landed as one fast-forward:

Tier 0/1
Build hygiene: a fresh clone must build Drop the phantom execution_tape dependency (the cross_compile feature goes dormant, documented in Cargo.toml comments), untrack build artifacts, add README, LICENSE, a justfile, and a CI workflow. Everything else assumes this floor exists.
Tier 2
Sideline the harness The ~30k-line move to archive/agent-harness (§2). Done early so every later diff is about the language, not the scaffolding.
Tier 3
Ergonomics: fix what bites in the first five minutes Accept if as a guard alias for when (the docs’ own idiom didn’t parse), universal type predicates on every value, a targeted hint when @{…} misroutes, and a DEMO.md sweep against a live REPL.
Tier 4
Test quality: make the suite tell the truth Checked integer arithmetic (overflow is an error, not a process abort), and a reason on every one of ~185 ignored tests — the beginning of the gap ledger (§5).

The ordering encodes a belief about inherited code: credibility is restored outside-in. A stranger’s first five minutes are clone, build, run an example from the README, hit an edge. Ergonomic polish on a repo that doesn’t build is decoration; deep test work before the harness is sidelined gets tangled in machinery that’s about to move.

§4

First contact with CI

The repo had never had CI — the agent ran its own checks, on its own machine, in its own environment. The very first CI run failed twice, and both failures were the same lesson wearing different clothes: properties nobody asserts are properties nobody has.

The first failure was the determinism check: regenerate the parser twice, byte-compare. The outputs differed every run — grammar rules travel through a HashMap, so each generator process emitted the parser’s functions in a different random order. Rust doesn’t care about definition order, so ~1,390 tests had been green for months over a nondeterministic build. The fix was one sort; the point is that the assertion had existed the whole time as an ignored test nobody ran.

The second was humbler: CI installs the latest stable toolchain, and its clippy was newer than the local one, with new lints. A locally-clean build is only evidence about the local toolchain. That became a standing rule in AGENTS.md — keep local Rust current, because a clean local clippy is not proof of a green CI.

A third repair completed the set: the checked-arithmetic work from Tier 4 had moved a panic rather than removing it — the generated parser’s digit-fold actions were emitted with .unwrap() on the newly-fallible ops. Threading Result through generated grammar actions (and rejecting the tempting catch_unwind shortcut, which would have poisoned VM mutexes) is the change that started the parser-generator epoch history at 6 — the same counter the next arc drove to 9.

§5

The ledger is born

The suite we inherited had ~185 ignored tests with no stated reasons — indistinguishable from neglect, and unusable as a map. The Tier 4 pass read every one, grouped them by root cause, wrote a machine-readable reason into each #[ignore], and distilled the result into docs/known-gaps.md: metacircular parser (~120), pattern matching (~50), pending design decisions, odds and ends.

This was the quiet keystone of the whole revitalization. An agent-written test suite encodes months of intent — tests written ahead of features, aspirational behavior pinned before it exists — but intent you can’t read is indistinguishable from cruft. Turning the ignore pile into a ledger did two things: it made the repo honest to visitors (the count reads as roadmap, not rot), and it made progress measurable. The payoff came fast: the next arc picked the ~120 bucket off the top of the ledger, burned it to zero in two days, and could prove it — because the ledger defined what zero meant.

§6

Going public

Publishing had its own fork points. The upstream repo wasn’t pushable (403 — no access), which settled the topology: a fork carries the work, upstream remains the lineage. The rehabilitated history needed to be reviewable without being mergeable — the fork’s main already contained everything — which produced the advisory-PR convention: freeze a pre-rehab branch at the inherited tip, open a PR from main against it, mark it review-only, close it when read. The frozen base is the “before” snapshot; merging would destroy it. That convention outlived the rehab — every phase since gets an advisory PR based on the previous phase’s frozen tip.

The last mile was the engineering tour: a single page that says what the thing is, what provably works, and what honestly doesn’t — every claim in it verified against a fresh REPL, like the TUTORIAL sweep before it. GitHub Pages hosting came with one classic paper cut: Jekyll choked on the design docs, so .nojekyll ships in docs/. (Pages later moved to Actions-based deploys in the wasm arc; the file remains as a fossil of this one.)

FORK-1Sideline the harness, or delete it
Sidelined to an archive branch, zero lines deleted. The harness is provenance, not garbage — and reversibility was the theme of the whole arc.
FORK-2Vendor the phantom crate, or go dormant
execution_tape could have been vendored to keep cross_compile alive. Dormancy won: a fresh clone must build with zero external state, and the feature’s seam is documented for whenever the crate returns.
FORK-3Fix the suite, or make it legible
Tier 4 deliberately did not try to make ignored tests pass. It made them mean something — the fixing came two arcs later, guided by exactly that legibility.
FORK-4Review PR, or merge PR
A merge PR was impossible (main already had the work) and an upstream PR premature. The review-only PR against a frozen base got the visibility without the fiction — and became the house convention.
§7

What carried forward

Revitalizing an agent-written codebase turned out to differ from ordinary legacy work in one repeated way: the artifact is strong where humans are weak, and weak where humans are strong. The agent never got tired, so the language core is deep and the test suite vast. The agent never changed machines, so the build was broken for everyone else on earth. Its review and roadmap lived inside its own process layer — PAR adversarial audits, an iteration log, a behavior-scenario corpus — so to an outside reader who never opened that layer, nothing on the surface distinguished roadmap from rot. Every tier addressed some version of that asymmetry — and the fixes were mostly social artifacts, not code: a README, a ledger, a review convention, an archive, CI as an outside observer.

What the later arcs inherited from this one: a build a stranger can run, a ledger that made “burn down the bucket” a measurable goal, an advisory-PR convention still in use at #5 and #6, the epoch discipline for generated-parser changes, and the habit — docs verified against a live REPL — that grew into the doctest harness in the very next phase.

broken → green
fresh-clone build; CI added and passing from the second run onward
~30,000 → 0 lines
agent machinery on main — moved to archive/agent-harness, none deleted
185 unexplained → 185 cataloged
ignored tests, each with a reason, grouped by root cause in known-gaps.md
0 → 3
published surfaces: repo, engineering tour, review PR — the browser REPL followed one arc later