01

Working code can still be structurally wrong

A generated change can compile, pass unit tests, and still move the system in the wrong architectural direction. It may import persistence from presentation, create a cycle through a shared package, or place a new capability in the easiest folder instead of the owned module.

Prose helps people understand intent, but it is not a reliable enforcement boundary. A fitness function turns one architectural decision into executable evidence that runs after every change.

The problem is not that generated code is uniquely careless. Human teams have always created duplicated logic, giant modules, hidden coupling, and misplaced dependencies. What changes with coding agents is the production rate. More changes reach review, so any control that depends on a person noticing every structural edge becomes less reliable.

02

Behavioral tests do not measure architectural shape

Unit and integration tests answer whether software behaves as expected for the scenarios they exercise. Type checking answers whether values and interfaces satisfy language constraints. Neither one proves that a domain package stayed independent, that feature slices remain acyclic, or that a new adapter entered through the intended port.

A pleasant-looking diff is not enough either. Reviewers see the files that changed, but an architectural effect can travel through existing edges elsewhere in the graph. A locally reasonable import may close a project-wide cycle or increase the coupling of a package the author never opened.

Architecture fitness functions add a separate evidence layer. They do not judge business behavior or design taste. They evaluate the objective structural decisions a team has chosen to encode.

  • Behavioral tests protect observable outcomes.
  • Type systems protect language-level contracts.
  • Linters protect local syntax and import conventions.
  • Architecture tests protect graph-wide structural invariants.
  • Human review handles context, tradeoffs, and intentional change.
03

Close the loop

The useful loop is short: an agent or developer changes code, the ordinary test command evaluates the architecture, and a failure reports the exact dependency path. The next iteration begins with concrete evidence instead of a broad instruction to improve the architecture.

Source-backed feedback changes the quality of the correction. Instead of telling an agent to respect clean architecture, the test can report that src/shop/api/subscriptions.py depends on src/shop/adapters/postgres.py even though API code may only reach application ports. The next action is constrained: introduce or reuse the port, move the dependency, and rerun the check.

The loop should use the same command locally and in CI. If an architecture rule requires a private dashboard or a manual export to evaluate, the result arrives too late for autonomous iteration and is harder for developers to reproduce.

  • Write the rule in the language of the repository.
  • Run it with the test framework already used by the team.
  • Fail on missing selections so refactors cannot silently disable the guardrail.
  • Return source-backed violations that a person or agent can act on.
feedback-loop.txt
architecture decision

executable architecture rule

test and CI feedback

targeted code correction
04

Keep deterministic and fuzzy checks separate

Some quality concerns are deterministic. A forbidden dependency exists or it does not. A cycle has a concrete path. A selector matched zero files. A source unit crossed a size threshold. These checks are strong candidates for merge-blocking automation because the same input produces the same answer and the failure can cite evidence.

Other concerns are interpretive. Two implementations may be semantically duplicated even when their syntax differs. A name may be technically valid but misleading. A module may have too many responsibilities without crossing a measurable threshold. Static graph rules should not pretend to settle those questions.

Use clone detection, language models, and human review to find fuzzy candidates, then use behavioral and architecture tests to verify objective parts of the cleanup. Keeping the boundaries clear prevents a probabilistic suggestion from masquerading as a deterministic policy.

05

Protect the verifier from the optimizer

A coding agent is often optimizing for a green test suite. If it can edit the rule, raise the threshold, broaden an ignore, or remove the CI step, the shortest path to green may be to weaken the verifier instead of improve the production design.

Architecture tests, ignore files, thresholds, and workflow definitions should therefore have explicit ownership. CODEOWNERS, branch protections, or a review policy can require a person who understands the decision to approve those changes. The agent remains free to refactor production code and rerun the suite, but it cannot silently redefine success.

Rule rationales help here. A because clause captures why the boundary exists, which lets a reviewer distinguish a legitimate architectural change from a workaround. If the decision truly changed, update the production code, rule, and rationale together.

CODEOWNERS
# Architecture policy requires explicit review
/tests/test_architecture.py  @architecture-owners
/.archignore                @architecture-owners
/.github/workflows/         @platform-owners
06

Start brownfield work with a ratchet

A legacy system may produce hundreds of findings on its first scan. Making that complete result merge-blocking creates a permanently red suite, and permanently red checks become invisible. Adoption should reduce uncertainty without requiring a rewrite.

Begin with one boundary that is already clean or one painful region the team is ready to repair. Prevent new violations there, fix a neighboring module, and expand the selector. For metrics, set the first ceiling near the current maximum and lower it as cleanup work lands. For cycles, protect a clean component before attempting the whole repository.

Exceptions should be narrow, documented, owned, and time-limited. A broad ignore for legacy code describes a second ungoverned architecture. A named exception with an owner and removal condition describes a migration step.

  • Track new and removed architecture violations, not generated lines of code.
  • Measure cycle count, post-merge rework, review rounds, and the size of exceptions.
  • Expand protected scope only after the current scope produces a trusted signal.
  • Schedule small cleanup changes that preserve behavior and reduce one measured risk.
07

Use one graph for prevention and cleanup

The same dependency model can support two complementary loops. Pull-request checks prevent known boundaries from getting worse. Scheduled analysis can identify cycles, coupling hotspots, metric outliers, or repeated exceptions that deserve a focused cleanup proposal.

A scheduled coding agent can then work from a bounded finding, change a small set of files, run behavioral tests, run the architecture suite, and present the result for review. It should not perform an open-ended rewrite. The graph supplies the target and the tests define the stopping condition.

This is architecture garbage collection in practical terms: prevent objective new debt at the change boundary, then continuously collect existing debt in small, independently verifiable steps.

08

Prefer a few strong invariants

The goal is not to encode every preference. Protect the boundaries whose violation creates real cost: domain independence, allowed package direction, cycle freedom, integration ownership, or a measurable design threshold. A small set of trusted rules is easier to understand and harder to ignore.

As the architecture evolves, change the rule deliberately in the same pull request as the design decision. That keeps the code, the test, and the explanation in one reviewable unit.

The strongest suite is not the one with the most rules. It is the one the team believes, maintains, and uses to make changes safely. Every green result should mean that a valuable claim was actually evaluated over the source that ships.