One year of daily AI-assisted development on a real multi-module project. What I built, what I learned, and what I would tell anyone starting today.
In March 2025 I started a pet project the hard way as an experimenting with LLM : copying source files into a chat window and downloading whatever came back. The project is a flight-simulator add-on. It has a native module, an in-sim UI, a desktop editor, a companion app, a cloud backend, and a data pipeline. Six subsystems, one repository, and several languages: C++, C#, JavaScript, TypeScript, Python.
It was terrible, especially for modifying existing files, but that time that was the best new thing.
A month later OpenAI released Codex, its command-line coding agent, and I was glad to stop copy-pasting. Then I tried Claude Code, Anthropic's equivalent, and noticed it could react to a screenshot pasted straight into the terminal. That was the moment I switched, and I have stuck with it since.
Today I run it with Fable, Anthropic's newest model, and I can hand it a change that spans the simulator plugin, the C++ WebAssembly module, the C# companion app, and the JavaScript edge scripts running on Bunny, my cloud provider.
One long run 1-4 hours, several languages, several runtimes, zero errors at the end (just UX nitpicking).
That last sentence is the part people find hard to believe, and I did not believe it either. TBH I did not try it before having access to Fable.
So this article is about how I got there.
The short answer: the model matters, but the setup matters more. And the setup grew out of a series of small, painful lessons.
Lesson one: trust has to be earned, and checked. For months I verified everything by reading pull requests myself. That does not scale. So verification grew in layers: first proper test harnesses, then agents whose only job is to challenge the work.
Today the project has a contrarian agent that argues with a plan before any code is written, a blue and a red security agent that play defense and attack against my own system, and one deliberately annoying reviewer agent I nicknamed Monday (got him from ChatGPT) the cat owner ponytail colleague, which runs its own stubborn, opinionated verification pass and he is also highly YAGNI. Yes, he has a cat... I left it there, I know, token waste.
Test coverage sits at 99.6 percent. The remaining 0.4 percent is code that genuinely cannot be tested via unit testing.
Lesson two: one agent cannot know everything. It became obvious early that I could not keep all project knowledge centralized in one general-purpose assistant. I needed specialists. And here is the surprise: what makes a specialist special is not a cleverly written prompt although yes, it helps (see the contrarian guy). It is their memory. A security specialist and a general agent can run on nearly identical instructions. The difference is the notes the specialist has built up over months of work in its own area.
Lesson three: nothing survives first contact. This whole project has been one continuous proof of concept iteration. Build it, try it, dislike it, learn the lesson, rebuild. Rinse, repeat. That is true for the code, and it is equally true for the AI setup around the code. So yes, I vibed it up, iterating until I saw and felt what I actually wanted, at a speed I could mentally hardly keep up with - and then reviewed it.But only after all the related agents reviewed it.
Those three lessons produced a system.
It has three parts: structure, memory, and friction. The rest of this article walks through each one.
Everything lives in one repository. At the top sits a single instructions file called CLAUDE.md, which Claude Code reads at the start of every session. Think of it as the project's rulebook. It has two halves.
The first half is general good habits, close to what Andrej Karpathy has written about working with coding agents: think before you code, say your reasoning out loud, do the simplest thing that solves the problem, change only what the task actually needs. Turn a vague request into something checkable, so "fix the bug" becomes "write a test that shows the bug, then make it pass."
The second half is project rules learned the hard way: quirks of the environment, naming conventions, and one I am fond of, never trust a commit message about what the code currently does. Go read the code.
The root file stays deliberately short. Anything bigger, such as formatting rules, infrastructure details, or the documentation style guide, lives in its own file and is loaded only when the task actually needs it. If the rulebook tried to explain everything up front, the agent would either ignore most of it or waste attention carrying rules it does not need.
The same idea repeats one level down. Every subsystem has its own local CLAUDE.md, and the agent reads it before working in that part of the codebase: how that part is built, how to test it, what has gone wrong before, what not to touch. It became clear that even this was not fine-grained enough, so today many subdirectories inside a subsystem carry their own CLAUDE.md as well. The goal is always the same: keep the context window clean, so the agent holds only what the current task needs.
Eventually, I noticed that somewhere along the way, the AI stopped being a tool I use and became part of the codebase. All of its memory lives in the repository, as plain files next to the code. Nothing is tied to a machine or a login. In practice that means I can work on my own PC in Ireland, then continue from a cloud desktop (Shadow PC) in Spain or Hungary, and the setup does not skip a beat. Yes, I need a gaming Windows cloud PC with dedicated video card to be able to run the simulator.
That memory comes in a few distinct forms, and keeping them apart is the whole trick.
Decision records write down choices that are hard to undo or that were genuinely argued about, together with what else was considered and why it lost. They are short, numbered, and never change once written. Their job is to stop the same argument from happening again six months later.
Ledgers track the current status of ongoing work: a review, a migration, a list of things found and fixed. A ledger is updated the moment something changes, so it never goes stale. A decision record answers "why did we choose this," and that answer is fixed. A ledger answers "where do things stand right now," and that answer moves constantly.
Because status lists appear everywhere in a project like this, they all use one display format: a small fixed set of colored dots, shown as a checklist with a totals line. A small command renders any status document this way on request. Asking "where do we stand" always looks the same, no matter which document answers it.
To be clear, this ledger is for me - the AI upkeeps its own ledger during the implementation but also uses mine.
Since having Fable I also have a LEDGER_BATCHED, tasks that can be done without my presence in a long horizon run. I wonder how will Opus perform with them, considering that I had 4 hours long runs with Fable. But I have a feeling: with all this scaffolding it will do just fine.
This is the lesson-two payoff. Beyond the general-purpose agent, the project defines a set of specialists, one per area: a particular subsystem, security review, infrastructure, even legal matters. Each keeps its own notes, updated over time: what it has learned, past investigations, open questions. Working with one feels like asking a colleague who owns that part of the system and remembers your past conversations, instead of briefing a stranger from scratch every session.
The specialists learned lessons alongside me, and because each was building its own history, each developed its own style, not just its own expertise. I run multi-agent sessions as what I call a council. When I introduced a new member named Caveman, an agent whose entire personality is blunt two-word phrases, one of the existing members greeted it with: "if you say fire good, rock hard, you must be able to roll it back." A version-control joke, aimed precisely, and nobody told it to make one.
For completeness: I tried off-the-shelf memory plugins before building my own, and none fit the project, so the approach above grew organically instead. The habit that made it work is simple. Every so often, I turn the agents on the workspace itself: review the setup, review the sessions, tell me what to improve. When Fable arrived, one of its first jobs was exactly that, a full audit of the AI scaffolding built by its predecessors.
One honest side note. All the documentation above was originally written for me. These days the AI is its main reader, and it shows: the writing is verbose in the way that suits a human skimming, not an agent that must hold every word. Trimming that is ongoing work. Meanwhile, documentation for actual human consumption lives in a separate local wiki. At one point I needed the local wiki server itself to do something it did not support, so I asked the AI to rewrite the wiki server... this is our new world.
Not every agent exists to make things faster. One exists to slow things down on purpose. Before any code is written, the dodgy guy interrogates the plan, asking one hard question at a time. It will not re-argue a point that is already settled, but it pushes hard on anything that has not been nailed down.
An agent that only ever says "sounds good, let's build it" is not helping much. The most useful thing an agent can do is argue with me before I commit to a direction, not after. It doesn't care about the task either, it is the janitor and guardian of the existing codebase.
Funny enough, eventually Anthropic added this approach to ultracode.
Warning: never run Fable with ultracode! It cost me EUR 90 of my free Anthropic tokens sadly - in 25 minutes.
The setup is easier to see with a concrete trio. Two of my agents are builders. One is scoped to the desktop editor, the other to the in-sim UI and native module. Each one's instructions say, in effect: take the task at face value and build it. Do not second-guess whether the request is the right one. Each also has its own voice. One talks like a methodical tinkerer who takes pride in small structural details. The other is a relentlessly upbeat veteran who insists every bug is an opportunity, while never once softening the actual technical verdict. The personality is not decoration. It keeps each agent's output recognizable from one session to the next.
The third agent's entire job is to not trust any of us. Where the builders take the task at face value, this one is told to distrust the diff, not the intent behind it. It maps the blast radius of a change: what else does this touch, which tests are missing, what will a user have to re-learn, does this quietly break a security assumption. Its personality is professionally paranoid, not pessimistic. It refuses to simply approve things. Its job is to say what has not been proven safe yet, not to sign off.
The asymmetry is the design, a builder that constantly second-guesses its own task slows down for no reason: it was told what to build. A reviewer that trusts the diff at face value is not reviewing anything. The same underlying model wears two opposite postures depending on which hat it has on.
The skeptic also runs in two modes from one source of truth. I can talk to it directly, as a conversation. But it also runs automatically at two fixed moments: right before a commit, and right before a plan is accepted. The automatic version is a stripped-down twin generated from the same definition, with no persona and no back-and-forth. It reads the change, produces a short report, gives one clear verdict, and stops. Because the twin is generated rather than hand-written, it cannot quietly drift out of sync with the conversational version. If the review criteria change, they change in one place.
And the lazy-loading idea from the structure section applies to the agents' own memory too. Every specialist keeps one compact index file: a running summary of its subsystem's architecture, file locations, and known quirks. By default it works from just that index, and pulls in a deeper topic file only when the task touches that topic. The skeptic even carries a small routing table: touch the editor, load the editor specialist's index; touch the in-sim module, load that one instead. It reads only the notes relevant to what actually changed.
Underneath the separate voices and separate jobs, all of them still answer to the same base rules from the root file. The persona is a layer on top of the rulebook, never a replacement for it.
The contrarian earns its keep in a specific kind of session. Some of my sessions are not coding sessions at all. I spend a day, sometimes a week, just talking a problem through, building up a large plan piece by piece before a single line of code changes. Planning was always the easy part. The bottleneck was execution: handing a plan that size to a model and watching it hold together across every file it touches, without drifting or quietly cutting corners halfway through.
Fable is the first model I have used that can carry a plan at that scale from start to finish in one run. The zero-error, multi-language change from the introduction was exactly one of those runs: a week of planning, one afternoon of execution.
The setup is only half of it. The other half is a set of working habits, and none of them came from a plan. Every one came from a real correction, a session where the agent did something I did not want, and I named the moment and stated the rule. These are the ones that stuck.
Big or hard-to-undo actions need a check-in first. Committing, pushing, creating branches, starting an expensive multi-step run: never just because the conversation moved on. Only an explicit "yes, do that" counts. Words like "continue" or "sounds good" after a related question do not.
"Explain it" and "do it" are two different requests. When I ask for an explanation, the answer is the explanation, full stop. Not an explanation followed by quietly starting the fix.
Some sessions are for thinking, not building. A conversation framed as research stays in that lane. It produces notes and documents, not source edits or commits.
Never trust a secondhand summary. Before a specialist's finding is used to make a decision, it is checked against the actual current files. Notes are a starting point, not proof.
Once a direction is picked, the other options are gone. Later replies should not keep bringing rejected paths back for completeness.
Naming has to be exact, everywhere, right away. When a name or term is corrected, the correction applies everywhere it appears, in the same sitting. A half-fixed name is worse than an unfixed one.
Tests and simulations use the real thing. When I ask the agent to test behavior, it runs the actual code paths, not a faster stand-in that skips the parts most likely to break.
Plain language first, jargon explained on first use. When a new topic opens, a plain-English explanation with concrete examples comes before any request to choose between options.
When I experienced any unexpected thing, or some behaviour that I disliked, I immediately made a rule to avoid it in the future. Interestingly, these rules were not always were respected like a year ago, but they are mostly respected by today's models.
Nothing in this article is exotic on its own. What matters is the overall stance: treat an AI coding agent like a new engineer you are onboarding, not a tool you point at a task. Give it structure, so it learns the territory one layer at a time. Give it memory, so its reasoning and its expertise survive between sessions. And give it friction, something whose whole job is to push back before you commit.
The result is not a smarter model, It is the same model, working inside a setup that keeps it honest and lets it hold on to what it has learned. A year ago I was pasting files into a chat window.
The distance between there and here was never the model alone - it was everything we built around it. To see a difference; on Sunday during a 15 hours long marathon Fable built me a new proof of concept plugin in a different simulator based on different SDK and my existing other plugin. I was watching HBO Max at the same time and tested when it asked me. This cost me 30% of my weekly allowance under 20x Max subscription - a result of several 2-4 hours long unsupervised coding sessions with /goal and /loop where Fable wrote the prompts too.
And that leads to the conclusion I did not expect when I started: the instruction files, the decision records, the ledgers, the specialist memories: these are now part of the project as much as any code that gets compiled, built, and deployed. They are versioned, maintained, and refactored like code, and the project does not work without them. Which means something unusual is sitting in git. Clone this repository and you do not just get source code. You get the settled arguments, the learned lessons, the accumulated judgment, everything that normally lives in a senior engineer's head and leaves when they do. Point a capable model at it, and all of that comes back to life.
○ Make a decision, monorepo or not. I personally dislike monorepo but find it good for a solo project.
○ Write a root CLAUDE.md — general working habits plus your first project rules. Keep it short.
○ Split anything bigger into separate CALUDE_xxx.md files, loaded only when the task needs them.
○ Add a local CLAUDE.md to each module. Go deeper into subdirectories when the context window needs it.
○ Start a decision record catalog (ADR). Number them. Never edit them after the fact.
○ Create a ledger per ongoing piece of work - the AI does the same, but you need yours. Make the AI to keep it updated. It also helps handoff.
○ Pick one status format for everything, and build one command to render it. I noticed that the format of the ledger is not consistent, so this is my way to render any ledger to my preferred format.
○ Define your first specialist agent with its own memory file: learned, investigated, open.
○ Add a contrarian. Let it argue with the plan before code is written.
○ Grow verification in layers: tests first, then review agents, then adversarial ones.
○ Keep all of it in the repo, versioned like code. Nothing tied to a machine or a login.
○ Every few weeks, turn the agents on the workspace itself and ask what to improve.