Dropping Loop Engineering onto an existing product piles up technical debt at high speed — rebuild the verification substrate with refactoring first
The previous post argued that Loop Engineering works best for solopreneur-style new products. When you spun up the repository yourself and hold the whole design, specification authority, verification substrate, and decision closure all line up, and the design of Loop Engineering pays off directly.
This post is about the flip side. What happens when you drop Loop Engineering onto an existing product that is already in production? The short version: technical debt piles up at high speed underneath gates that keep passing. And because the gates pass, the pile is invisible.
This is not the agent’s fault or a defect in the design of Loop Engineering. The verification substrate simply is not up to the standard Loop Engineering demands. So the fix is equally direct: before turning Loop Engineering on, rebuild the substrate. That means refactoring, and it has to come first.
This post walks through why an existing product accelerates debt accumulation under Loop Engineering, what kind of refactoring counts as “building the verification substrate,” and how to decide when a region of the codebase is finally ready to hand to the loop.
Why debt accumulates fast on an existing product
Loop Engineering uses gate-passage as the single indicator of “clean.” Clearing all five gates (earlier post) makes the PR review-ready and passes it to the human for the merge call. That design assumes what the gates can judge lines up with the quality bar of the repository.
On an existing product, that assumption breaks first. And the shape of the break is not “gates too strict, nothing passes” but “gates too loose, everything passes.” It shows up in the following ways.
Tests do not fail, so it passes
A codebase that has been alive for ten years typically has a large fraction of tests that “exist but do not actually catch regressions.” Coverage numbers look respectable, yet you can seriously break the implementation and CI stays green.
Loop Engineering uses green tests as proof-of-no-regression. Any regression the tests cannot judge slips right through. Where a human would intuitively say “I should manually verify this area for a change like this,” the agent does not. Any region the tests do not cover is treated as if the region does not exist.
The problem is that this slip-through now happens at agent speed. A place that used to ship three human PRs a day can ship fifteen with Loop Engineering on. Even if the debt per PR is unchanged, it accumulates five times faster.
Types are loose, so it passes
TypeScript any, Python Any or untyped functions, Ruby’s duck-typed seams: any existing product has regions where type-based guarantees are effectively zero.
Loop Engineering uses type-checking as one of the substrate signals, but a loose region is not being judged. In an unjudged region, the agent interprets the space as “free to modify.” Where a human would self-restrict (“this is dangerous; keep changes minimal here”), the agent does not.
And a change in a loose region clears the same five gates as a change in a strict region. From the gate side, they are indistinguishable.
Boundaries are implicit, so untouchable code gets touched and it still passes
This is the worst form of debt.
An existing product has regions that “must not be touched, but the code does not say so.” Legacy compatibility layers, modules whose contracts with external systems are implicit, and callers that depend on undocumented side effects.
Loop Engineering treats the issue as the sole authoritative scope. Unless the issue explicitly says “do not touch module XYZ,” the agent will touch it. When “code that should break if you touch it” is not guarded by tests, touching it leaves everything green.
Over time, you accumulate a codebase where implicit contracts have been broken while things still appear to work. It looks fine, so nobody notices until the contract is actually needed (a migration to another environment, a spec change from an integration partner, crossing a performance threshold). By then, the person who ran the loop that broke it usually does not remember.
The essence: five times faster
What these three share is that the mechanism itself was already present in the existing product. Loop Engineering did not invent a new source of debt.
What Loop Engineering did do is raise the throughput. Higher throughput, with the same quality bar, means debt accumulates in proportion. What used to accumulate slowly enough for humans to catch it now accumulates too fast to catch.
Turning Loop Engineering on before the substrate is real is like replacing the engine with a five-times-larger one while leaving the brakes untouched. The faster you can drive, the larger the crash.
What to refactor
The fix is to raise the substrate to Loop Engineering’s bar. The order matters. Do it top-down.
1. First, put in tests that actually catch regressions
Tests come first. Judge them by “does this actually fail when I break the intent,” not by coverage percentages.
Concretely:
- Measure what fraction of current coverage is “actually catches regressions” by breaking the implementation on purpose and seeing whether tests fail.
- Add tests around the core business logic, API boundaries, and data-shaping paths that assert on values (not
toBeTruthy(), but the expected value). - Where tests refuse to fail, do not patch the test to be even less strict. Either add tests, or split the code being tested into units that can be verified.
The realization at this stage is usually that “tests exist but do not judge anything” vastly outnumbers “tests do not exist.” Tests that pass thanks to a permissive mock, tests that only record method calls with no value expectations, layers with only E2E and essentially no unit coverage. All of these look nonexistent from Loop Engineering’s viewpoint.
2. Next, thicken the types at boundaries
Once tests are in reasonable shape, expand what the types cover. Tests say “fail when broken”; types say “you cannot write it in a broken form to begin with.” For code you hand to an agent, the second is often more efficient.
- Put strict types on the inputs and outputs of public APIs.
- Peel back
unknownandanyin internal data structures, one layer at a time. - Introduce patterns that let the type system force state exhaustiveness (discriminated unions and similar).
- For core domain models, prefer nominal types over primitives (
string,number).
As the region types can protect grows, two of Loop Engineering’s five gates (Codex /review and Claude Code /code-review) suddenly have more surface to judge. The wider the independently judgeable region, the more the reviews mean as “the same diff seen from different angles.”
3. Resize architecture into units Loop Engineering can touch
Once tests and types are in shape, module boundaries come next. The goal here is to reshape the code so that “what is safe to touch” becomes something you can declare in an issue.
- Split monolithic modules with mixed responsibilities along domain lines.
- Keep call relationships directed; eliminate cycles.
- Push side effects (DB access, external APIs, file I/O) to the edges so core logic tends toward purity.
- Move from “touching this breaks other things” to “the unit that is safe to touch is explicit.”
The reason this refactoring pays off is that the scope declaration in an issue and the actual touched region can now agree with module boundaries. Writing “only touch the Cart module” in an issue becomes something both the agent and reviewers can mechanically enforce. Without boundaries, writing “only touch things around Cart” leaves “around” defined differently by every reader.
Why tests, then types, then architecture in that order? Because each later stage requires a larger set of changes to land safely, and without the earlier stages you cannot tell whether the change stayed intact. Thickening types without tests hides regressions. Resizing architecture without types hides ripple effects.
4. Write the current state into AGENTS.md and CLAUDE.md
Alongside the refactoring, keep AGENTS.md and CLAUDE.md current. What goes in here is the actual result of your refactoring: which parts of the substrate are up to the bar and which still are not.
- Which directories or modules are safe to run Loop Engineering against.
- Which regions are still weak and require human review to be spliced in.
- Files that must not be touched, and the list of modules with implicit contracts.
- Exact invocation of validation commands.
- Which test suites, if they fail, are real problems, and which are being tolerated as flaky.
This matters much more here than on a new product. Existing products always have “contracts kept implicitly,” and running Loop Engineering without writing them down leaves them implicitly broken.
Partial enablement is the realistic answer
Ideally, tests, types, and architecture would all be up to bar across the whole repository before Loop Engineering is turned on. In practice, that is almost never feasible for an existing product in one shot.
The realistic answer is partial: enable Loop Engineering region-by-region, in the order that regions cross the substrate threshold.
- For modules where tests are thick, types are thick, and boundaries are clear, allow Loop Engineering only for issues whose scope is closed inside that module.
- For issues that touch other modules, disable Loop Engineering and stay on the ordinary human-driven flow.
- For issues that cross module boundaries, at minimum have humans review the crossing.
Doing that judgment fresh every time is tedious, so declare the “Loop Engineering-eligible region” explicitly in a file (AGENTS.md, or something CODEOWNERS-shaped). The agent’s preflight can read this file and halt if the scope exceeds the eligible region.
A side benefit of partial enablement is that refactoring gets a concrete motivation. Instead of an abstract “improve tests,” the goal becomes “get this module inside the Loop Engineering region,” and the work to do gets sharp.
When you want to use Loop Engineering on the refactor itself
“Refactoring is exactly what I want the agent to do” is a natural instinct. There is a trap.
Refactoring to build the substrate is done while the substrate is not yet real. That means Loop Engineering’s five gates cannot enforce the quality of the refactor itself. You cannot use Loop Engineering to guarantee the refactor.
A workable compromise looks like this:
- Purely mechanical transformations (replacing
anywith concrete types, unifying naming, splitting files) can be delegated to the agent, but every PR is reviewed in full by a human. - For test-addition tasks, delegate under the strict constraint that the existing implementation may not change. Only after tests grow, hand over implementation refactoring.
- For architectural redesign (responsibility splits, dependency reversal), keep at least the design judgment with humans. What the agent can do is the mechanical relocation to the chosen design.
For this phase alone, the “everything the agent judges” principle of Loop Engineering has to be relaxed. While the substrate is being built, judgment depends on human eyes. Trying to cut this corner embeds a bug in the substrate itself: it fails to notice its own failures.
Checklist for enabling Loop Engineering on an existing product
Finally, here is a short checklist for deciding whether a region of an existing product is ready for Loop Engineering. Partial adoption is possible even if some items are missing, but running the loop where none of these hold is slower in the end than not running it at all.
- Three deliberate breakages of the target module all cause tests to actually fail.
- Inputs and outputs of the target module have strict types with no
anyorunknownleaks. - The boundary between the target module and its neighbors is traceable via imports.
- AGENTS.md or CLAUDE.md lists the safe-to-touch range, off-limits range, and implicit contracts for the target module.
- Issue scope can be written such that it closes inside the target module.
- CI, for that region, is configured so failures actually reach a human.
If all six hold, enable Loop Engineering for that region only. Where they do not hold, spend the effort making them hold. During that effort, use Loop Engineering sparingly.
Wrapping up
Dropping Loop Engineering onto an existing product piles technical debt underneath passing gates at high speed. Regressions the tests do not judge, unwitting changes to under-typed regions, silent incursions into modules with implicit contracts. These debt-accumulation mechanisms were already there in the existing product; Loop Engineering just runs them at five times the speed.
The fix is to raise the substrate to Loop Engineering’s bar before turning it on. The order is: tests that actually catch regressions, then types that protect boundaries, then architecture that makes safe-to-touch units explicit, then AGENTS.md and CLAUDE.md that state the current status in words. Skip an earlier layer and you cannot tell whether the later work landed intact.
In practice, whole-repository readiness is not feasible in one shot, so enable Loop Engineering region by region, in the order regions cross the substrate threshold. The refactoring that builds the substrate itself has to depend on human eyes, not on Loop Engineering. Cutting that corner embeds a bug in the substrate that fails to notice its own failures.
The previous post’s statement, “Loop Engineering works cleanly on a solopreneur-style new product,” implies its counterpart: “the same move on an existing product crashes.” If you want Loop Engineering’s leverage on an existing product, the only way in is through the substrate, and the substrate has to be rebuilt first.
That’s all from explaining why dropping Loop Engineering onto an existing product accumulates debt at high speed, and why the substrate has to be rebuilt in the order of tests, types, and architecture before turning it on, from the Gemba.