Enforce Architecture Automatically: How to Stop AI From Breaking Your System Design
AI code generators ship fast, but they can silently violate architectural boundaries. Executable Architecture tools like pytest-archon catch these violations in CI/CD before they merge.
Rapid code generation from AI tools accelerates delivery, yet introduces a subtle risk: Comprehension Debt. When an AI system produces working code that crosses domain boundaries, developers lose their grasp of how the system actually fits together. The solution lies not in better documentation, but in shifting to Executable Architecture—using automated testing tools within CI/CD pipelines to enforce design rules that AI agents must obey.
The paradox of AI-generated code is that its greatest strength becomes its greatest danger. A junior engineer's poor code breaks immediately, triggering review and discussion. An AI agent generating 500 lines of functionally correct, bug-free code that subtly breaches architectural boundaries slips through undetected. Gradually, the system drifts: billing logic connects to authentication, the presentation layer gains database access, dependencies wire in ways that work but violate the design assumptions of human maintainers.
The most dangerous thing an AI coding agent can do is generate code that works.
Technical debt has been overtaken by something more urgent—Comprehension Debt, the expanding chasm between how quickly code is written and how thoroughly the team understands its architecture. The issue isn't tangled business logic; it's a fractured mental model. Teams stop knowing why their codebase exists. If AI becomes merely a faster keyboard, architectural integrity collapses. Survival in the age of AI-driven development demands a shift: architecture enforcement must move from passive documentation to Executable Architecture.
The documentation trap
Conventional wisdom suggests: write clearer documentation so AI understands the rules. This approach fails. Documentation ages quickly. An AI agent discovering a shortcut to its objective by bypassing a service layer will take it. Human reviewers, overwhelmed by thousands of AI-generated pull requests, miss these detours. Architectural drift happens invisibly.
You cannot depend on human beings to detect architectural drift. You have to trust the CI/CD pipeline.
When architectural boundaries matter, validate them as rigorously as any business requirement. Fitness functions must fail the build whenever an AI agent violates a boundary condition.
Building executable architecture in Python
The Java world has long used tools like ArchUnit to enforce architectural rules. Python developers now have pytest-archon for the same purpose. Consider a modular monolith for e-commerce with strict boundaries: the Billing domain must never import from Shipping, and domain models must never import from infrastructure (AWS SDK, SQLAlchemy, etc.). An AI tasked with adding shipping cost calculations based on billing tier might directly import the Shipping Calculator into the billing service. The test passes. The application runs. The architecture breaks. pytest-archon stops this from happening.
Step 1: Install the dependency
pip install pytest-archon
Step 2: Define architectural rules as tests
Rather than burying rules in documentation, express them as pytest functions in a test_architecture.py file:
from pytest_archon import archrule
def test_billing_is_isolated_from_shipping():
"""
Ensure the billing module never imports shipping logic.
This prevents the AI from creating tight coupling between distinct domains.
"""
(
archrule("billing_isolation", comment="Billing must not know about shipping")
.match("ecommerce.billing*")
.should_not_import("ecommerce.shipping*")
.check("ecommerce")
)
def test_domain_models_are_pure():
"""
Ensure domain models only depend on standard libraries or pydantic.
Prevents the AI from leaking infrastructure (DBs, APIs) into the core logic.
"""
(
archrule("pure_domain", comment="Domain models must not import infrastructure")
.match("ecommerce.*.models")
.should_not_import("sqlalchemy*")
.should_not_import("boto3*")
.check("ecommerce")
)
Step 3: Close the feedback loop
When the AI agent submits a pull request, pytest runs automatically within the CI workflow. No matter how well the AI calculates the shipping fee, the build fails immediately:
FAILED tests/test_architecture.py::test_billing_is_isolated_from_shipping - AssertionError: Rule 'billing_isolation' violated: ecommerce.billing.invoice imports ecommerce.shipping.calculator
A human reviewer need not manually trace the entire import tree. The best engineering teams never rely on humans for this task. Feed the failing pytest output directly back into the AI agent's context window using tools like Aider or custom CI/CD scripts, allowing the AI to fix architectural violations autonomously.
Defending against Comprehension Debt
Architectural tests alone are insufficient. A comprehensive strategy requires multiple layers:
1. Hard boundaries vs. soft conventions
AI agents respect hard constraints but ignore soft suggestions. Replace loose folder-based architecture with explicit module boundaries. Deploy tools like import-linter or pytest-archon to physically block forbidden imports. The easiest path must be the most architecturally sound path.
2. Limit automated complexity
Clear APIs and boundaries help, but cannot excuse messy, convoluted implementations. If AI generates tangled code in your billing module that causes race condition failures at 3 AM, a human engineer must still maintain and comprehend that code. Pair architectural tests with complexity gatekeepers—Ruff, Radon, or SonarQube—in your CI pipeline. Set firm limits on complexity to force AI to break large functions into smaller, understandable pieces.
3. Examine the interfaces, not just the implementation
When reviewing AI-generated pull requests, preserve the developer's mental energy. Stop scrutinizing each line for loops and variable assignments. Instead, focus on what changes the system from the outside: Are there new dependencies? Did the PR introduce new API endpoints? Did it alter the data schema? If the answer is no, your mental model stays intact.
The path forward
AI coding systems excel at their assigned tasks but have a fundamental limitation: they care only about task completion, not code maintainability. Relying solely on human judgment to control system design leads inevitably to drowning in Comprehension Debt. The answer is not to slow down AI implementation—it is to make your environment more resilient.
AI coders are very strong, but they have one big flaw. They care only about completing the task you assign them.
You do not need to examine every line of AI-generated code. You need to build a cage for it—one made of automated tests, CI/CD enforcement, and Executable Architecture.
Source: The New Stack