What replaces code ownership when AI writes the code
262K lines of Rust, 788 PRs, zero read diffs, and four months of green metrics. How externalized memory, mutation testing, and CI gates replaced reading the code.
As AI writes a growing share of the world's code, the fear in engineering organisations has a name: loss of code ownership. Developers stop knowing what the code does, and when something breaks, nobody can reason about it. There have already been cases where that loss cost a product dearly. This is a story about a product where code ownership never existed in the first place. The product did not fall apart. It ships, it grows, and its quality metrics have stayed green for four months. I want to explain why, with numbers rather than impressions.
In June I started a project I had no business starting. I wanted a terminal AI chat with a memory: notes about you, a knowledge base over your files, and a model of itself that it revisits. I wanted it in Rust, because it had to run on Windows, Linux and a rented GPU pod as one native binary. Behind me were fifteen years of enterprise development on .NET, and no Rust at all: I had read the Rust book once and never written anything complicated in it.
Four months later the thing exists. It is called mindfork, it is open source, it ships installers for three platforms, and it has had 31 releases. Almost every line was written by an AI coding agent. I know the language well enough to read it, but early on I stopped reviewing the changes the models make, and I have not read most of the code since. By the usual definition, nobody owns this code.
The question people ask is whether it works. The question that interests me is why it did not rot. Anyone who has used a coding agent for longer than a weekend knows the failure mode. The code works, then it works a little less, then every new feature breaks two old ones, and the test suite turns into a pile of green lies. That did not happen here. New features do not break old ones. The static analyzer has reported zero issues for weeks, and keeping it that way costs nothing.
I went looking for the reason, with the agent's help, by measuring the repository instead of remembering it. The short answer surprised me. The quality did not come from the model. It came from a process in which every claim the agent makes has to be backed by something I can actually read: a number, a run on a real model, a test count, a dated decision. I was not going to verify the code line by line, so I made the code's quality legible without that.
I used no spec framework and no downloaded AGENTS.md. Every rule below grew out of something that went wrong in this repository. That turns out to matter.
The short version
Six mechanisms, in the order the article takes them. Each gets its own section below, with the numbers from the repository rather than the principle alone.
- The owner decides and verifies; the agent owns the code. Every non-obvious choice comes to me as a list of options with a recommendation, and I pick. Every change to the engine, memory or tools runs against a real model before it merges.
- Memory with a budget. A 30 KB router file that CI keeps small, a journal of 667 entries split by subsystem, a lessons file that counts how often each trap recurred.
- Measure, and let the plan lose. 281 journal entries measure something; 108 record a rejection, whole stages among them.
- Tests that must be able to fail. Half the source is tests, checked by hand-run mutation testing; every CI gate is tested against a planted violation.
- Two gates with different blind spots. Four thousand unit tests that need no model, and 279 ignored tests, nearly all of them smokes that need a real one.
- Incident, lesson, rule, gate. Every guardrail in the repository carries the incident that produced it, and a rule broken twice becomes a CI check.
What the product actually is
mindfork is a terminal chat client for language models with a memory of its own. It runs local Gemma and Qwen models through llama.cpp, or any OpenAI-compatible server, and talks to OpenAI, Anthropic, Gemini, xAI, DeepSeek and OpenRouter in the cloud. One native binary for Windows, Linux and macOS. The code, the engineering journal and every number in this article are public in the GitHub repository.
What makes it a demanding thing to build is not the chat. It is what the assistant is allowed to do with tools:
- Memory and knowledge. It saves notes about you and the work, links them into a graph, merges, revises and supersedes them, and consolidates them over time. A knowledge base over your files is searched when you ask.
- A model of itself, and of you. The assistant keeps a summary of who it is, its goals and its traits, adds observations as it goes, and runs reflection passes that revise that model. It keeps a model of the person it is talking to as well.
- Introspection. It can read and change its own system message and its own sampling parameters in the middle of a conversation, and it knows what time it is.
- Conversation management. It can search and read other chats, read the compacted history of a long one, rewrite its own answer, or send a follow-up message on its own.
- Doing things. Python in a sandbox, web search and page fetching, a project workspace attached to a chat, subagents for a second opinion, directed dialogues between two models, MCP plugins, speech.
Every one of these sits behind a switch the user sets. And every one of them is a place where an agentic feature can quietly break another: a tool that edits the system message changes what every later tool sees. That is the codebase that had to stay coherent.
The numbers, so you know what we are talking about
Everything below was measured on the main branch on 2026-10-20; its newest commit is from 2026-10-19.
- Time: 127 days, from the first commit on 2026-06-14 to the newest on 2026-10-19.
- Commits and pull requests: 2,401 commits, 788 merged PRs, about six a day, every day.
- Code: 351 Rust files, 262K lines under src, about 54% of them test code.
- Tests: 4,397 test functions, 279 of them marked ignore: smokes that need a real model, and a handful that need a real clipboard or a real terminal.
- SonarCloud: 204K lines analysed, 0 bugs, 0 vulnerabilities, 0 code smells, 89% coverage, 0.6% duplication.
- Documentation: 8.1 MB of Markdown beside 10.7 MB of source.
- Commits that touch only documentation: 40%.
- Models named in commit trailers: six, from Opus 4.8 in June to Fable 5.1 and Opus 5.5 in October, plus Cursor on five commits.
Two of those lines explain most of what follows. More than a third of the commits never touched code. And the documentation is three quarters the size of the codebase it describes, a ratio that has held since the third week. That is not a byproduct of the work. That is the mechanism.
What I did instead of reviewing the code
I did four things, and the repository shows them better than my memory does.
I decided the forks. The agent is required to lay out every non-obvious decision as a list of options with a recommendation, and to put the list to me as a question before it writes code. I pick. The engineering journal records 153 such decisions with a date, and later work cites them like case law: "Keep it opt-in, F2(b) of 2026-07-30 stands."
Most of the time the recommendation is the best option and I take it. Sometimes it is not. When the agent proposed to refresh a settings hint that named specific models, I said no: a hint that names a model goes stale, so ask the provider for its catalogue instead. That became a feature. The value of the ritual is not that I argue with the agent. It is that the agent has to enumerate the alternatives before it commits to one, and I get to see the whole design space without reading a line of the diff.
I ran the live stand. A unit test cannot tell you whether Gemma 4 actually calls your tool, or that llama.cpp sends the usage chunk after the finish reason. So every change that touches the engine, the memory or the tools has to be run against a real model, and the result has to be written down in the journal: "Smoke, GO (2026-08-12, gemma-4-31B + bge-m3, and all four clouds with real keys)." The GPU is mine. The Docker containers with Ollama and LM Studio are mine. The rented pods are mine. A full run of the rented gate takes about half an hour of wall clock.
I merged every pull request by its artefacts, not its diff. The PR template demands a test count, the list of live smokes and the stack they ran on, a documentation checklist, and a "Models" section naming which model wrote what. I read those. I read the journal entry. I do not read the diff.
I set policy. Who the product is for, what a message may and may not say to the user, which risks stay off the table. Those are product decisions, and product is the thing I actually know.
So not reviewing the code turned into a division of labour. The agent owns the code. I own the behaviour, the decisions and the facts.
Memory with a budget
An agent has no memory between sessions. Whatever it learned yesterday about this codebase, its conventions and its traps, is gone this morning unless it is written down somewhere it will read. The obvious fix is to write everything into the one file the agent loads at the start of every session. I did exactly that, and here is what it looked like by August.
The file every session loads grew to 1 MB, then became a 30 KB router
CLAUDE.md size in KB, one point per week, June to October 2026
The numbers behind the chart
| Date | Bytes | Commit |
|---|---|---|
| 2026-06-14 | 21,092 | ab48c4b1 |
| 2026-06-21 | 99,007 | 7fc0c227 |
| 2026-06-28 | 210,589 | 22e915c7 |
| 2026-07-05 | 342,286 | 677de7b6 |
| 2026-07-12 | 547,214 | 5916aca9 |
| 2026-07-19 | 730,819 | 7bff6bd3 |
| 2026-07-26 | 614,684 | 893f2424 |
| 2026-08-02 | 856,661 | c2203560 |
| 2026-08-08 | 994,562 | 15a903d1 |
| 2026-08-09 | 11,969 | dbef0b24 |
| 2026-08-16 | 23,758 | 0369d1ac |
| 2026-08-23 | 19,123 | b7060a18 |
| 2026-08-30 | 27,169 | c5cabcce |
| 2026-09-06 | 29,530 | ca21845c |
| 2026-09-13 | 29,435 | 16a1b2c5 |
| 2026-09-20 | 28,947 | 9e245c9e |
| 2026-09-27 | 28,750 | d52c8e91 |
| 2026-10-04 | 29,338 | 9ec31dfb |
| 2026-10-11 | 29,263 | bc0de0c4 |
| 2026-10-18 | 29,994 | 2e553089 |
| 2026-10-19 | 29,541 | 5be3435c |
CLAUDE.md started as an 8 KB guide. By 2026-08-08 it was 994,562 bytes: a short guide followed by a 243-entry engineering journal, loaded in full before any work began. The gate that now guards it records the cost in its own docstring: roughly 237K tokens spent per session before the first line of work.
The fix was not to write less. It was to give the memory a shape and a budget.
- CLAUDE.md became a router: orientation, the standing decisions, and a map that says "if you are working on the engine, read architecture §5 and §6, spec §3 and §6 to §8, and the engine journal." It is capped at 30,000 bytes, and CI fails if it grows past that. Today it sits at 29,541.
- The journal moved into 16 files split by subsystem, 667 entries today. Each one records what was done, what was measured, what was rejected and why, the test count, and the model that gave the GO.
- A lessons file holds the traps that would bite on an unrelated task: 267 rules, each with the incident that produced it and a count of how many times it recurred.
- Research documents hold the reasoning before a decision, with claims tagged [code], [measured] or [unverified]. A finished plan moves to a history folder. The outcome becomes an architecture decision record that gets amended, never rewritten.
- The spec and the architecture document use stable section numbers, and the code cites them: 3,414 references from 316 of the 351 source files. An agent that lands in a file is told which two kilobytes of the spec to read.
Three separate documents repeat the same sentence: "Read the section, not the file." This is the whole trick. A stateless agent needs an externalised memory, and an externalised memory needs a context budget, or it eats the session it was meant to help.
Measure, and let the plan lose
The lessons file has a rule I would put on the wall of any team working with agents: "Assume the measurement will overturn the plan, because it repeatedly has."
A language model's defining weakness is confident, plausible assertion. The plan sounds right. The fix sounds right. The explanation of why the test passes sounds right. The project's answer was to refuse every assertion until it had been measured. The journal has 281 entries that contain the word "measured", 31 that describe a control arm, and 108 that record something rejected.
The first time this happened was day two. The spec had been written around an inference engine called xinfer. A live run of Gemma 4 through it produced incoherent output, and the engine was replaced with llama.cpp the same day. The spec still carries the historical note.
Later the stakes got bigger. The code-workspace track had a fifth stage, a semantic index over the project, built as a probe and measured: 48 turns per arm on a 31B model, control against treatment. The effect changed sign depending on how you defined success, which means it was smaller than the instrument. The stage did not ship. The probe is frozen as a git tag, and the journal entry says: "The rejection is the deliverable."
The rejection is the deliverable.
Smaller examples are everywhere. A roadmap item claimed that per-model prefixes improved embedding search; re-measured on 40 documents and 14 queries, the prefixes changed no ranking at all. A rendering change for raw HTML blocks was measured against a 186,805-word corpus, found to affect 0.047% of it, and rejected, while the census turned up a different bug.
Slop is what you get when nothing is ever measured and nothing is ever rejected. This repository rejects things constantly, and writes each rejection down so the next agent does not propose it again.
Tests that must be able to fail
Half the source tree is test code, but that is the least interesting fact about it. The interesting one is that the project learned not to trust green.
Agents are very good at writing tests that pass. The lessons file is full of the ways a passing test can lie, each learned the hard way:
- "Beware the vacuous assertion: a value equal to the type's default proves nothing." Three such assertions in one change, "all three found by mutation, not by passing."
- "Tests that assert an absence can pass for the wrong reason." An always-empty store satisfies them either way.
- "On a path built to degrade gracefully, is_ok() can never be the assertion."
- "A test that waits on an event must bound the wait, or a regression hangs instead of failing." Recorded five times before it became a shared helper that every stub uses.
- "A gate that passes is indistinguishable from a gate that does nothing." So every CI gate is tested in both directions: a planted violation must turn it red, and a clean tree must keep it green.
The countermeasure is mutation testing, done by hand and done routinely; 126 journal entries describe it. The recipe is written down: every mutant is written into the source at once, each behind a switch read from an environment variable at run time; the project is compiled once, and the test binary is run once per mutant with that variable set. A Rust build of this size takes minutes, so compiling once per mutant would make the check too slow to be routine. A surviving mutant is read twice, because it indicts the fixture as often as the test.
You can see the habit in the test names. There are 158 tests with "refuse" in the name, 114 with "does_not", 72 with "says", 58 with "never". Twenty tests carry a doc comment naming the control arm and exercise the same code with the defect switched off.
These are tests written by someone who expects to be lied to.
Two gates with different blind spots
The unit suite runs without a terminal and without a model. A scripted mock engine plays the agentic loop. A tiny TCP server serves canned server-sent events, so the HTTP clients are tested end to end rather than through a serializer, after a serializer-only test let a swallowed error event survive.
Every screen is drawn into an in-memory terminal buffer and read back as text. One test draws every screen and every popup from a window of 0 by 0 upwards, in both languages: 21 states by 493 sizes by 2 locales, and every frame must be either the placeholder or a complete screen with the cursor inside it. Ten committed screenshot dumps must match what the code renders today, byte for byte, or the build goes red. Every storage-format change ships with a golden fixture from the previous version.
The live suite is the other half: 279 tests marked ignore, nearly all of them runnable only against a real model and a real server, mandatory for anything touching the engine, the memory or the tools. The whole set also runs on rented GPUs as one dispatch: on 2026-10-18, 264 smokes, mostly on Gemma 4 31B, in four parallel jobs, 72 minutes of model time inside 36 minutes of wall clock, zero failures. The journal records live runs as "Smoke, GO" with the date and the stack, 186 entries so far, or as an explicit "no live run needed", 130 entries.
The live runs keep finding what mocks structurally cannot. The client never emitted the usage chunk on llama.cpp, because the server sends it after the finish reason and the stream had stopped reading. A launch with no model file started llama.cpp's router mode, whose health endpoint says OK while every message is a 400. The Linux install script passed 25 local scenarios and five CI images, then failed on the first real GPU pod, because root without CAP_CHOWN cannot restore an archive's owner. One tar flag, found only by a pod.
Two gates, two blind spots, and a rule that you do not get to skip the second one because the first one is green.
The process grew itself
I used no framework for any of this. Looking at the git history, the process has two layers with a clear seam.
The structural layer was there on day one. The first commit contains the brief, a 97 KB spec with sixteen numbered chapters, and a plan for milestones M0 to M9 with tasks, tests and a definition of done for each. It fixes Feature-Sliced Design with dependencies pointing strictly downward, the engine behind a trait, a branch per milestone, and even the rule that every commit names the model that wrote it. None of that was learned here. It came from a first attempt at the same product that I had shelved.
The guardrail layer is entirely reactive. Every file in the tools directory, every byte limit, every CI check appeared after something broke, and the commit messages say so in the same voice each time: "held only by discipline, now closed by the gate."
- Day 1, 2026-06-15. The first live gate. Gemma 4 through xinfer was incoherent; the engine was replaced.
- Day 14, 2026-06-28. The first MVP probe with a go/no-go, on the self-model track.
- Day 28, 2026-07-12. AGENTS.md and the PR template, written to codify practice that already existed.
- Day 31, 2026-07-15. CI, the release pipeline, a changelog, a pinned toolchain. "Before this the gates rested only on the agent's discipline."
- Day 45, 2026-07-29. A link checker, after 28 broken links when plans moved to the history folder.
- Day 52, 2026-08-05. SonarCloud, blocking the same day, while preparing for an open-source release.
- Day 55, 2026-08-08. The 30 KB router and the journal split, after a 995 KB CLAUDE.md.
- Day 71, 2026-08-24. A gate for list scroll state, after one scroll bug was copied into eight lists.
- Day 95, 2026-09-17. A release guard, pinned actions, a static C runtime, after a missing VCRUNTIME140.dll on a clean Windows machine.
- Day 117, 2026-10-09. A layer gate. See the last section.
The lessons file counts recurrences: seven rules recorded twice, one three times, three four times, one five times. One entry admits that a recurrence was "written by an agent with this very entry in its context." That admission is why the trajectory always runs from convention to gate. A rule in the agent's context is a suggestion. A rule in CI is a fact.
A rule in the agent's context is a suggestion. A rule in CI is a fact.
This is also why an imported AGENTS.md would not have worked. Every rule in this repository carries its own incident. A rule copied from someone else's repository carries theirs.
Why the analyzer stays green without trying
People look at the SonarCloud badge and assume there is a campaign behind it. There is not. The green gate is a symptom of the process, not a goal anyone pursues.
- Stricter gates run upstream. Every pull request runs rustfmt, clippy with warnings as errors on a pinned toolchain, and the full test suite on Linux, Windows and macOS before Sonar sees it. Sonar's Rust rules overlap heavily with clippy, so there is little left to find.
- The gate judges new code only, and the backlog was paid down once. On 2026-08-06 the agent triaged the initial 114 findings and I decided the classes: 49 wildcard imports accepted because they are the documented convention of the file-splitting playbook, seven complexity findings accepted with a written reason, everything else fixed in four pull requests by area, each verified by extraction rather than rewrite.
- The remainder is swept weekly. Twenty-three of the 40 entries in the quality journal are a "SonarQube follow-up": a handful of findings read off the main branch, fixed by extracting functions along natural seams, never by clicking Accept, and then mutation-tested.
- Coverage is high because tests are a definition-of-done item, not a separate effort. The project is honest about the accounting: inline test modules count toward the measured lines, the tools directory is excluded, and live-smoke files are named so the coverage tool leaves them out.
- The job waits for the server's verdict. The first run ever uploaded a report the server later rejected while CI stayed green. Now the job fails if the gate fails.
The Sonar configuration file is worth reading on its own. It is written as an essay: every exclusion has a date, a reason, and a paragraph on what it costs. "What it costs, stated so it is not forgotten." A future agent cannot undo a decision without reading why it was made.
What is not so clean
An article like this is only worth reading if it says where the method is weak.
Start with the obvious objection: is the code actually good, or are the metrics merely green? The claim here is narrower than "good code". The goal was never that a human could read every line; it was that the quality of the code is legible through artefacts: numbers, live runs, decisions, behaviour. A newcomer who opens this repository meets eight megabytes of documentation and a wall of gates, not Rust that a colleague can explain over coffee. That is a boundary of the method, not a footnote.
The method also scales to exactly one owner. It works as long as there is a person who owns the behaviour and the decisions, if not the lines, and has the time to look at the artefacts every day. For a team that is a different and more expensive arrangement, and I have not tried it.
The evidence is self-graded. The agent that wrote the code also writes the GO, decides whether a failure was a flake, and runs the mutation checks. None of that is a CI gate. The loop is closed by me reading, and by the live stand.
The documents drift. The router says the spec is 496 KB; it is 575. It counts 618 journal entries; the indexes sum to 667. When the coverage condition was switched back on in the Sonar server, nothing in the repository recorded it for two months. No gate checks that a cited section number still exists.
The lessons are not always read. "A fifth time, with this entry unread," says one of them. At 203 KB the lessons file is becoming what the router was built to prevent.
The early journal asserts without evidence. Eleven percent of the entries carry no test count, no GO and no measurement; three in four of them sit in the first half of their journal file, the early ones. The discipline was acquired, not original.
Gates rot when nobody runs them. For several weeks the live smokes ran only on my own GPU and the rented gate sat idle. When I started it again, it did not come back clean. Putting it right took a week: the suite was reworked and split into four parallel jobs, and the nine real defects it surfaced went out as a patch release. The project's own lessons file had already said it: a gate nobody has seen fail is a gate nobody knows works.
And it is expensive in discipline and attention. Eight megabytes of documentation for nearly eleven of code. Forty percent of commits spent on documents. A GPU, rented pods, and a person who merges six pull requests a day and runs the live stand. In money, though, it is cheap: $100 a month for the Claude subscription, and the rented GPU runs came to $5.23 in July, $8.94 in August, $0.99 in September and $98.96 in October, the month the gate was reworked and run many times over, some of them on gpt-oss-120b. Across four months the rented GPUs came to $114, $99 of it in October alone. Three months of a live gate for less than the price of one developer hour is the strongest argument I know for measuring on a real model instead of guessing. The method is reproducible. What it costs is not money but an owner who knows the product cold and has the time to verify behaviour every day.
The day the process ran on itself
While preparing this article I asked the agent to measure the repository, and one of the findings was embarrassing. The architecture document calls one rule "the key FSD invariant in the code": screens never import from the application layer. The survey found eleven event types imported from exactly there, the first on 2026-08-23, every later one written by an agent that had the rule in its context, copying the neighbour that had it. The other half of the same sentence, the arrow between the domain types and the shared layer, had been wrong in the other direction since the second milestone.
The same day, in a separate session, the types moved down a layer in a pure refactoring branch, the document was corrected to say what the code actually does, a new gate went into CI with a self-test that plants a leak in each direction and expects red, and a new lesson went into the file: "A documented never with no gate has drifted by the time anyone reads it."
That is the whole method in one afternoon. Measure. Find the gap between what the documents claim and what the code does. Fix the code mechanically, in its own pull request. Fix the document. Turn the rule into a gate. Test the gate. Write down what happened so it is not repeated.
What to copy
If you are starting a project with an agent and you do not intend to review every change it makes:
- Write the spec before the code, number its sections, and never renumber them. The code will cite them for months.
- Give the agent's memory a shape and a budget: one small router file that CI keeps small, a journal split by subsystem, and a lessons file that is read before implementing.
- Make the agent enumerate the alternatives and put them to you as a question. Decide, with a date. Write the decision where the next agent will find it.
- Require a run on the real thing for anything the mocks cannot see, and require the result in writing: model, stack, date, GO or NO-GO.
- Mutation-test the tests. A test that cannot fail is worse than no test, because it reports confidence it does not have.
- When a rule is broken twice, stop writing rules and write a gate. Then test the gate against a planted violation.
- Keep mechanical refactors and behaviour changes in separate pull requests, and use the test count as the invariant of a pure move.
None of this requires reading the diffs. All of it requires reading numbers, decisions and behaviour, and being willing to let a measurement throw your plan away. That is what stood in for code ownership here.
Do not take my word for any of it. The repository is public at github.com/vshylov/mindfork-rs. Start with docs/journal for what was done and measured, docs/lessons.md for what went wrong more than once, and tools for the gates, including the script that produced the repository's numbers in this article and the one that drew its chart from git. If you find a figure here that the repository does not back, open an issue: that would be the process running on itself one more time. The product itself is on the home page, and the install guide has every way to get it.