Keeping AI-Generated Software Understandable
AI can turn a short request into working code very quickly. That does not mean the resulting system is simple, understandable, or safe to operate.
For a beginner, the distinction is:
- working code produces the expected result in the cases that were tried
- understandable software has responsibilities, dependencies, decisions, and failure behavior that people can explain
- maintainable software can be changed without repeatedly breaking unrelated behavior
Agent-generated code is not automatically worse than human-written code. The risk comes from speed and scale. An agent can produce more changes than a person can meaningfully inspect, and a sequence of individually reasonable changes can create an unreasonable architecture.
The goal is therefore not to avoid AI-generated code. It is to use AI for implementation while preserving human understanding of the system.
The Bottleneck Has Moved
Section titled “The Bottleneck Has Moved”Software development has never been only about translating an idea into programming-language syntax.
The phrase software crisis emerged around the 1968 NATO Software Engineering Conference, when software projects were becoming harder to deliver and control as computers and ambitions grew. In his 1972 Turing Award lecture, Edsger Dijkstra argued that increasingly powerful machines had created a new difficulty: programmers now had to master systems whose complexity could exceed their intellectual control.
The historical analogy is useful, but it is not a prediction that every AI-assisted project will fail. Coding agents create another large increase in production capacity. If a team can generate software faster than it can understand and simplify it, complexity can once again outrun the practices used to manage it.
In No Silver Bullet, Fred Brooks separated software difficulty into two broad categories:
- essential difficulty comes from understanding and representing the problem itself
- accidental difficulty comes from the tools and mechanisms used to implement that solution
Better languages, libraries, infrastructure, and coding agents can remove substantial accidental work. They do not automatically decide:
- what the system should do
- which behavior is truly required
- where responsibilities should live
- which tradeoffs are acceptable
- how the system should fail
- what must remain compatible
AI makes implementation easier, but the conceptual work remains. In some projects, implementation becomes fast enough that understanding, review, and architecture become the limiting resources.
flowchart LR
I["Intent and domain knowledge"] --> D["Design decisions"]
D --> C["Code generation"]
C --> V["Verification"]
V --> O["Operable software"]
A["Coding agent"] -. "Accelerates" .-> C
H["Human owner"] -. "Applies judgment" .-> D
E["Tests and runtime evidence"] -. "Ground" .-> V
Easy Is Not The Same As Simple
Section titled “Easy Is Not The Same As Simple”Rich Hickey’s Simple Made Easy distinguishes two ideas that are often treated as synonyms.
| Idea | Practical meaning | Example |
|---|---|---|
| Easy | Close at hand, familiar, or requiring little immediate effort | Ask an agent to add one more condition to the existing function |
| Simple | Not interleaved or entangled with unrelated concerns | Keep authorization, business rules, persistence, and presentation behind clear boundaries |
An easy action can make a system less simple.
Suppose an application has one authentication file. A sequence of prompts asks an agent to:
- add password login
- add OAuth
- add session refresh
- add enterprise SSO
- repair a conflict between the previous changes
Each response may be locally reasonable. If every request modifies the nearest working path, however, authentication, session storage, user provisioning, and authorization can become intertwined.
flowchart TD
A["Prompt 1<br/>Add authentication"] --> B["Small local solution"]
B --> C["Prompt 2<br/>Also add OAuth"]
C --> D["Extend the existing path"]
D --> E["Prompt 3<br/>Sessions now conflict"]
E --> F["Add another exception"]
F --> G["More coupling and hidden assumptions"]
G --> H["System works, but its structure<br/>is difficult to explain"]
This is a conversation spiral: the architecture begins to reflect the order and wording of the conversation instead of a deliberate model of the problem.
Essential And Accidental Complexity
Section titled “Essential And Accidental Complexity”The distinction is useful when an agent reads an existing codebase.
| Complexity | What it represents | Example |
|---|---|---|
| Essential | Rules that exist because of the real problem | Only an account owner may approve a transfer |
| Accidental | Difficulty created by implementation history | The same ownership check is copied into controllers, jobs, and database helpers |
An agent sees code, tests, documentation, history, and tool output. It does not automatically know which patterns express current intent and which are old workarounds.
For example, repeated permission checks may mean:
- every layer genuinely needs an independent security boundary
- the application lacks a central authorization service
- a previous migration stopped halfway
- an old compatibility rule is still required for one customer
- obsolete code was copied because it appeared nearby
Similarity alone cannot resolve that question. The agent needs evidence from domain owners, architecture decisions, production behavior, history, and tests.
This is why “follow existing patterns” is necessary but insufficient. Existing patterns are evidence, not proof that the design should be repeated.
Why Passing Tests Does Not Guarantee Understanding
Section titled “Why Passing Tests Does Not Guarantee Understanding”Tests are essential, but they answer bounded questions.
A passing suite can show that:
- specified examples still work
- known invariants were preserved
- interfaces remain compatible
- a regression was not reproduced
It may not show that:
- responsibilities are in the correct component
- two concepts have become unnecessarily coupled
- a copied workaround is now spreading
- an undocumented behavior should have existed at all
- the design will remain comprehensible after the next five changes
Verification and architectural review therefore solve different problems. Use both.
Signs That Understanding Is Falling Behind
Section titled “Signs That Understanding Is Falling Behind”Slow down when several of these appear:
- reviewers can describe the diff but not the resulting execution path
- the agent repeatedly edits the same central file for unrelated features
- each new request adds another exception to the previous solution
- generated abstractions duplicate an existing responsibility
- a change passes tests but nobody can explain why the affected boundaries are correct
- the plan names files without explaining their roles
- the agent cannot distinguish required behavior from compatibility scaffolding
- generated documentation describes an architecture that the code does not actually implement
- rollback depends on reconstructing decisions from a long chat transcript
The signal is not merely a large diff. A mechanical large diff can be easy to understand. A small change that mixes two previously separate concepts can be much riskier.
Keep A Human-Owned Model Of The System
Section titled “Keep A Human-Owned Model Of The System”Before substantial implementation, a responsible person should be able to explain:
- Outcome: What problem are we solving?
- Essential behavior: Which domain rules must remain true?
- Boundaries: Which component owns each decision and side effect?
- Dependencies: What calls, stores, or trusts what?
- Failure behavior: What happens when a dependency is slow, unavailable, or inconsistent?
- Compatibility: Which current consumers and data must continue to work?
- Evidence: What will prove that the change is correct?
- Recovery: How can the change be rolled back or repaired?
The person does not need to memorize every line. They need a coherent model that lets them notice when generated code contradicts the intended design.
Current evidence supports this division of work. Anthropic’s June 2026 analysis of approximately 400,000 Claude Code sessions found that people made most planning decisions while the agent made most execution decisions. It also found that task-specific domain expertise was associated with higher success and better recovery from problems. The study is observational and uses model-based classification, so it should not be treated as universal proof, but it reinforces a practical rule: agents amplify the quality of the understanding supplied to them.
Use Research, Planning, And Implementation As Separate Phases
Section titled “Use Research, Planning, And Implementation As Separate Phases”For consequential work, do not begin with an editing prompt.
flowchart TD
G["Goal and constraints"] --> R["Research<br/>map current behavior and dependencies"]
R --> RH{"Human checkpoint<br/>Is the system map accurate?"}
RH -->|"Revise"| R
RH -->|"Approve"| P["Planning<br/>design the change in detail"]
P --> PH{"Human checkpoint<br/>Are boundaries and tradeoffs sound?"}
PH -->|"Revise"| P
PH -->|"Approve"| I["Implementation<br/>execute bounded steps"]
I --> V["Verification and review"]
V --> U{"Can a responsible person<br/>explain the resulting system?"}
U -->|"No"| R
U -->|"Yes"| M["Merge or release"]
1. Research The Current System
Section titled “1. Research The Current System”The research phase should reconstruct reality before proposing changes.
Useful evidence includes:
- relevant entry points and execution paths
- interfaces, schemas, and type definitions
- architecture and design documents
- tests and production runbooks
- recent incidents and known failure modes
- git history for confusing decisions
- migration notes and compatibility requirements
- questions that require domain or architectural judgment
The output should be a bounded research document, not a dump of every file the agent read. It should separate:
- confirmed facts
- reasonable inferences
- unresolved questions
- suspected accidental complexity
This is context compression: turn a large, noisy codebase into a small, reviewable model of the part that matters.
2. Plan The Change
Section titled “2. Plan The Change”Once the current system is understood, define:
- the target behavior
- component and ownership boundaries
- exact interfaces that change
- invariants that must be preserved
- files and symbols likely to be modified
- rollout and compatibility stages
- tests, operational checks, and rollback
- explicit non-goals
The plan should be precise enough that implementation is mostly execution rather than continuing architectural discovery.
Use Spec-Driven Development with Coding Agents when requirements, design, tasks, and evidence need durable traceability. Use Plan Before You Code for a lighter checkpoint.
3. Implement In Bounded Steps
Section titled “3. Implement In Bounded Steps”Give the agent one reviewable step at a time or a dependency-aware set of independent steps.
For each step, specify:
- allowed scope
- required behavior
- constraints and non-goals
- verification commands
- conditions that require stopping
If implementation reveals that the research or design is wrong, return to the earlier artifact. Do not hide the discovery under another local patch.
Create One Reference Change Before Automating A Migration
Section titled “Create One Reference Change Before Automating A Migration”For a repetitive refactor or migration, manually completing one representative change can be more valuable than immediately dispatching many agents.
The reference change can reveal:
- undocumented invariants
- misleading abstractions
- special compatibility cases
- the actual review standard
- commands and tests that prove correctness
- differences between the intended architecture and the current code
The accepted pull request, its review discussion, and its verification evidence then become a concrete example for later agents.
This does not mean every migration must begin manually. Use a reference change when hidden knowledge or architectural judgment is more likely to dominate than mechanical editing. The complete procedure is covered in Parallel Coding Agents for Large Refactors.
Ask For A Teach-Back Before Acceptance
Section titled “Ask For A Teach-Back Before Acceptance”Do not ask only, “What files did you change?”
Ask the agent to explain:
Describe the implemented execution path from input to side effect and response.
For each affected component, state:- its responsibility- why the change belongs there- the invariants it preserves- dependencies it calls- expected failure behavior- evidence that supports these claims
Identify any behavior copied from the existing code that may be compatibilityscaffolding or technical debt rather than intentional design.Then compare the explanation with the code and runtime evidence.
A fluent explanation is not proof. Its value is that discrepancies become visible. If the explanation names a boundary that the code violates, or claims an invariant without a supporting test or source, the review has found useful work.
Scale The Process To The Risk
Section titled “Scale The Process To The Risk”Not every change requires a research report and architecture review.
| Change | Appropriate process |
|---|---|
| Typo or isolated documentation correction | Direct edit and rendered check |
| Small behavior change in a familiar component | Short plan, focused tests, normal review |
| Cross-component feature | System map, reviewed plan, integration tests, teach-back |
| Security, billing, identity, or data change | Explicit invariants, threat and failure analysis, human approval, rollback evidence |
| Repository-wide migration | Reference change, dependency map, staged execution, layered verification |
The cost of the process should follow the cost of being wrong and the difficulty of recovering understanding later.
A Practical Responsibility Split
Section titled “A Practical Responsibility Split”Responsibilities vary by team and risk, but this is a useful starting point.
| Work | Human responsibility | Agent contribution |
|---|---|---|
| Problem definition | Own outcome, priorities, and domain truth | Clarify ambiguity and summarize evidence |
| Research | Validate important facts and answer judgment questions | Search, trace paths, and produce a system map |
| Architecture | Own boundaries, tradeoffs, and risk acceptance | Compare options and test assumptions |
| Implementation | Set scope and stopping conditions | Produce code and mechanical transformations |
| Verification | Decide what evidence is sufficient | Run checks and collect reproducible results |
| Review | Accept accountability for the resulting system | Explain the change and perform independent checks |
Avoid turning this table into a rigid rule. Agents can propose architecture and people can implement code. The important point is that consequential decisions have an identifiable owner who understands their effects.
Common Failure Modes
Section titled “Common Failure Modes”The research document is plausible but imaginary
Section titled “The research document is plausible but imaginary”Require file paths, symbols, test names, command output, or approved documentation for important claims.
The plan preserves accidental complexity
Section titled “The plan preserves accidental complexity”Ask which existing behaviors are essential requirements and which are only implementation history. Escalate uncertain cases to domain owners.
The human checkpoint becomes ceremonial
Section titled “The human checkpoint becomes ceremonial”An approval button is not review. Require the reviewer to resolve open questions and confirm boundaries, invariants, and verification.
The agent rewrites the plan while implementing
Section titled “The agent rewrites the plan while implementing”Allow discoveries, but make them visible. Pause, update the reviewed artifact, and obtain approval when scope or architecture changes materially.
The reference change is not representative
Section titled “The reference change is not representative”Choose a slice that exercises real interfaces and failure paths. If later batches differ materially, create another reference class instead of forcing one pattern everywhere.
Understanding exists only in one person’s head
Section titled “Understanding exists only in one person’s head”Record the accepted system map, decisions, and operational evidence in repository documentation, design records, tests, or runbooks.
Checklist
Section titled “Checklist”Before substantial generation:
- The outcome and non-goals are explicit.
- Essential behavior is separated from suspected implementation debt.
- Current execution paths and dependencies are mapped.
- Important claims point to evidence.
- Human judgment questions are resolved or recorded.
- The proposed boundaries and tradeoffs were reviewed.
- Verification and recovery are defined.
Before accepting the result:
- The complete diff matches the approved scope.
- A reviewer can trace the changed execution paths.
- The code follows the intended component boundaries.
- Tests cover required behavior and important invariants.
- Known accidental complexity was not copied without justification.
- Operational failure and rollback behavior remain understood.
- The resulting system can be explained without reconstructing the chat.
Key Takeaways
Section titled “Key Takeaways”- Faster code generation moves pressure toward understanding, design, and review.
- Easy changes can produce a structurally complicated system.
- Existing code contains both intentional design and historical accidents.
- Tests verify bounded behavior; they do not replace architectural understanding.
- Use research and planning artifacts to compress evidence before implementation.
- Preserve human ownership of outcomes, boundaries, tradeoffs, and risk.
- For uncertain migrations, solve one representative case before scaling execution.
- Accept generated code only when the resulting system remains explainable and operable.
Resources
Section titled “Resources”- Source talk: The Infinite Software Crisis
- Jake Nations: The Infinite Software Crisis
- AI Engineer Code Summit session notes and slides
- Edsger Dijkstra: The Humble Programmer
- Fred Brooks: No Silver Bullet
- Rich Hickey: Simple Made Easy
- Anthropic: Agentic coding and persistent returns to expertise
- Spec-Driven Development with Coding Agents
- Context Engineering for Long-Running Agents
- Reviewing Agent-Generated Code