When AI coding agents land a long pull request before lunch, the build still goes green. The unit tests pass. CI is happy. And somewhere inside the codebase a module boundary that used to be clean is now quietly crossed, with no error message and no review comment to mark the change. Emmanuel Akita’s piece in The New Stack names the failure mode Comprehension Debt, and that label fits because what slips through is not broken code. It is code the team can no longer fully explain.
The trickiest part of this failure mode is that nothing fails. A normal bad change from a junior engineer usually announces itself: the build breaks, staging goes red, a reviewer catches it on Monday. A bad change from an AI agent often announces nothing at all. The invoice calculation starts including shipping cost. The presentation layer starts importing a database session. Everything works, and the pull request lands at 4 PM because nobody spotted the wiring in a 500-line diff.
Why behavioral tests do not catch this kind of drift
Behavior tests encode what the system does. Architecture encodes what the system knows about. Those two things are different, and the gap between them is exactly where Comprehension Debt accumulates. A test that checks the invoice total will happily pass even when the invoice code starts reading from the shipping module. The behavior is unchanged. The architecture is not.
This is why a green build is not the same signal it used to be. The tests pass because the tests were never written to enforce the module boundary. They were written to enforce the user-visible behavior, which is a smaller contract than the team usually thinks. AI agents are fast enough that the difference between those two contracts shows up in production before it shows up in code review.
The honest summary is that humans are not the safety net for this kind of drift. A 500-line diff is too long to read line by line, and the architectural smells are too subtle to catch in a skim. The reviewer approves the pull request because the build is green and the diff looks reasonable, and the architecture quietly slides while nobody is watching.
Why documentation is not enough
The instinct here is to write a README and tell the AI what the boundaries are. That instinct has been tested enough times to know it does not hold. An AI agent is pragmatic about finishing the task it was given. If the shortest path crosses a module boundary that the README mentions in passing, the agent takes the short path, especially when the README is buried on page four of the wiki.
Documentation is a soft constraint. AI agents follow hard constraints and ignore soft ones. The fix is to make the constraint hard by encoding it as a test that fails the build when the boundary gets crossed. Anything less than a hard constraint, and the AI will quietly find a path around it within a sprint or two.
The same answer applies to any business rule that lives only in a wiki page. If the rule matters, it deserves a test. Architecture is just one example of that broader principle. Review culture alone is not the safety net, and documentation alone is not the safety net. The build is the safety net.
pytest-archon as a practical starting point
The Python answer worth reaching for first is pytest-archon. It lets a team write architectural rules as pytest functions and run them in CI alongside the rest of the test suite. The install is short:
pip install pytest-archon
The rules live in a tests/test_architecture.py file and read like normal pytest functions. Two rules cover most of what a team wants to protect:
- Billing code should never reach into shipping code.
- Domain models should never import infrastructure modules.
When the AI agent just wired billing to shipping, the test fails with a message that names the offending import and the rule it broke. That message can be piped back into the agent’s context, which means the agent can correct its own architectural mistakes without a human in the loop. The test is the broken record. The agent is the one that listens.
The reason this works is that pytest-archon is just pytest. There is no new runner to install, no new dashboard to watch, no new ceremony. The architectural rule runs in the same command as the rest of the test suite, and CI stays simple.
Three habits that pair with hard boundaries
Hard rules are the spine. Three habits turn the spine into something a team can actually run on.
Make the boundaries loud. Soft constraints get ignored. Hard constraints get followed. Folder-based architecture that lives by convention is not enough on its own. Pair it with import linters or pytest-archon so the build fails the moment a forbidden import shows up. Make the path of least resistance the architecturally correct path, and the agent will take it.
Cap the complexity an AI can leave behind. Architectural tests stop bad wiring. They do not stop a 600-line function full of race conditions. Pair the architectural test suite with a complexity gate like Ruff, Radon, or SonarQube, and put a hard ceiling on cyclomatic complexity. Force the AI to break huge functions into smaller ones before they merge, because the architecture rule does not catch code that respects the boundaries while being a mess inside them.
Review the shape, not the lines. The most expensive thing in code review is the reviewer’s attention. Stop reading every line of an AI-generated diff. Look at the imports, the new endpoints, the schema changes. If those are clean, the implementation details inside them are usually fine, and the mental model survives the pull request.
Trade-offs
Hard architectural rules are not free, and the trade-offs are worth naming up front.
- pytest-archon only catches the rules the team writes down. A partial rule set gives a false sense of safety, because the gaps are invisible until the AI finds them.
- Import-graph scans add time to the build. On large monorepos the overhead can be tens of seconds, which is a real cost the team has to absorb.
- The hard boundaries are only as accurate as the team’s mental model. If the rule file encodes the wrong architecture, the agent will obediently enforce the wrong thing.
- Architectural rules do not catch bad code that respects the boundaries. Complexity gates and the rules have to work together, or the second-order mess slips through.
The simplest single move is to pick one architectural boundary the team actually cares about. Write one pytest-archon rule for it. Add it to CI. Watch the next AI pull request fail loudly. That is the whole assignment, and the cost of doing it is small compared to the cost of untangling a codebase nobody can explain six months later.
Why this lands differently than older static analysis
Most static analysis tools try to catch every possible mistake and end up catching the wrong ones. pytest-archon takes a different stance: the team picks the boundaries that matter, writes a rule for each, and lets the build enforce them. The signal-to-noise ratio is high because every rule the team writes is one the team actually cares about.
That focus is what makes the rules easier to maintain than a sprawling linter config. When the architecture changes, the team edits the rule file directly. When a boundary stops mattering, the rule gets deleted. The tool stays out of the way between those changes, and the CI pipeline picks up the new contract automatically. That is the part older tools rarely get right, and it is the part that makes the difference between rules that survive a year and rules that get commented out by the second sprint.