Until it reached the fan
I got a B2B SaaS project in the stage that any tech lead dreams of: "Almost ready. Release in two weeks." Two out of three API calls returned HTTP 500.
Several generations of agent fixes had accumulated in the code. Whenever a new error was found, the agent was asked to figure it out and fix the bug. The agent would insert a patch in the form of another condition. The state of the object was determined by indirect signs. In 21 places, the handler recognized its type through negation: not A and not B, so it must be mine. The agents saw these approaches ahead of them and continued to wrap the code around this ball in the same pattern.
Claude dove headfirst into this substance. And I provided the keys from the outside. When Claude surfaced, he looked like a cat after valerian. I had never seen him generate so many hallucinations on such a small context before. He couldn't answer half the questions clearly.
After two days of this shit-diving, I decided that fixing symptoms was meaningless. We completely rewrote the module, restoring the authors' intent from the few documents and the existing code. The environment where the agent writes new code without seeing the old one, we called "clean outline." Three days were spent on the transfer contract: algorithms and copies of input and output. Another two days on the new implementation. The algorithms were described in prose, stating what needed to be done, not how to do it.
Transfer mechanism
I divided the work between two agents. The analyst studied the old code, ran the system, took snapshots of inputs, outputs, and changes in the database. He recorded the results in scenario cards, one per operation. From these cards, the transfer contract was built. In each card:
input
expected result
artifacts and states
codes
what is captured in the image
what is confirmed
Each row has a mark: [measurement], [code], or [hole].
The executor wrote new code based on the cards. He didn't see the old code: restriction in the task, separate session, two restrictions in the linter (new code does not trigger the old, old does not trigger new) and a code borrowing monitor that compares the trees of the old and new code and outputs only the matching areas.
Defects in working functions were transferred as is and fixed in a clean environment. Unused functions were not transferred.
The entire user scenario was taken: from input request to result. After successful verification, the calling code was switched to the new implementation. The old path was removed after acceptance.
In a similar way, in 1984 Phoenix Technologies created a BIOS compatible with IBM PC BIOS. Some engineers studied the original and created specifications, while others wrote the implementation without seeing IBM's code. This method is called clean room design.

A few days later, I was given a second project: Rust backend and five Python services in separate repositories. A small feature required coordinated changes in several places, but contracts in two languages diverged during green tests of all six components. In three days I converted the scenarios into a single Python monolith and switched the routing to it. Six repositories were archived. Calls between services that were previously HTTP with JSON became function calls, and contract mismatches are now caught by mypy.
The Clean Code Rule
Even in new code, the agent will still have to use workarounds. Therefore, the executor has a rule consisting of 15 principles. Some of them an experienced developer will consider elementary, while others will be controversial.
№ | Rule |
|---|---|
1 | State is stored in a field. Values and allowed transitions are declared once. The state restriction is built from the same list. The state is not computed from neighboring fields or logs. |
2 | The state is changed by one function, atomically with the transition data. It verifies the transition against the declared table. |
3 | External response or background task has a state for failure and exit on timeout. |
4 | Allowed and disallowed transitions are checked by tests. |
5 | The code of the entity's lifecycle is in one package. Writing its state from outside is prohibited. |
6 | The code of the failure is taken from the general list. The code does not make decisions based on the error message text. |
7 | Unknown value, record failure, or loss of lock result in a failure. Silent success is not allowed. |
8 | Result parts are taken separately. The obligation of a part defines the contract. |
9 | The task type is declared by data. The handler does not compute its type as "not A and not B". |
10 | There are no calls between the old and new code except for explicitly named temporary exceptions. They are removed along with the old path. |
11 | The new test or scenario must fail at least once on the corresponding defect. The visible result is checked. |
12 | The object type is stored explicitly. It is not guessed from the signature, prefix of the identifier, or string format. |
13 | The screen receives the entity's state from the server and displays it. |
14 | A priority rule is declared for conflicting responses and user actions. The order of responses does not affect the outcome. |
15 | The server checks the action's validity. The browser receives available actions and reasons for failure from its response. |
Antigoal rule
If one thing couldn't be fixed in three attempts, stop and re-examine its design. Don't make the fourth patch.
Name - answer to the anthropic /goal that burns tokens like Martens' furnace, trying to achieve the goal.
Intersection with ub-mode
At Uncle Bob's, the skill ub-mode was recently released. Two rules match the clean loop charter:
Attack the Premise: if two fixes with a common premise failed one test, dismantle the premise before proceeding. This is an antigoal rule.
TDD: see how the test fails for the expected reason. This is rule 11.
ub-mode is a skill, and it is specifically called to perform a specific task. The clean loop charter is always in the repository and read in each session.
Weak model does not always follow the charter
Rules can be placed in AGENTS.md, and the model reads them itself. I insert rules into the prompt along with the task for a weak model.
I compared on DeepSeek V4 Flash, 36 runs, Claude Sonnet counted violations blindly.
What did the model get | Notes |
|---|---|
Nothing | 36 |
Example | 31 |
Example and rules in AGENTS.md | 25 |
Example, AGENTS.md and requirements in the prompt | 18 |
First example. When the rule "a value is declared once" was in AGENTS.md, DeepSeek recorded the event type as a string in three out of three runs. When the task explicitly mentioned this trap, zero out of three.
Second example. In the old code, the order did not have a status field. There was a string field cancel_reason, the reason for cancellation. Whether the order was canceled or not, the code decided based on whether this field was filled. This was checked in four places.
I gave DeepSeek the task: you cannot issue new assembly tasks for a canceled order. Without additional rules, the model solved it with the same guess, fifth one:
- if order is None: + if order is None or order.cancel_reason is not None: return None With four rules in the prompt, but without an example, the result is the same.Then I gave the same task with the rules from AGENTS.md in the prompt and an example of a state machine. I explicitly wrote: "Repeat the mechanism, not the names." DeepSeek declared OrderStatus with four values (new, paid, shipped, cancelled), a transition table, and one validation function. Five scattered checks on cancel_reason turned into one status reference.
Result
Without additional rules
With rules
Transition table
No
No
Yes
Completion check
Repeated in five places
Repeated in five places
Bundled into one function
State field
No
No
Yes


