Skip to main content

Software Design for AI Agents

A common narrative in AI-assisted development suggests that humans only need to write specifications, leaving code generation entirely to large language models. Under this "Spec-to-Code" view, code is considered cheap and disposable: if something fails, the developer simply adjusts the prompt or specification and regenerates the implementation.

In non-trivial codebases, this approach breaks down quickly. When developers stop stewarding internal structure, software entropy (a concept Pocock borrows from The Pragmatic Programmer) accumulates rapidly: local patches duplicate logic, boundaries erode, and the codebase degrades into unmaintainable sprawl. AI coding agents do not make poor codebases cheap; rather, messy codebases make AI agents ineffective and expensive to run.

AI coding magnifies the value of sound software engineering. In his AI Engineer Europe talk, Matt Pocock identified five recurring failure modes in AI-assisted programming and paired each with classic software design principles.

Intent Misalignment: Building Design Concepts via Adversarial Dialog

Failure mode

A developer holds a mental model of the feature, but the model interprets the initial prompt or rough specification in an unexpected direction, generating large volumes of unhelpful code.

Theoretical basis cited in talk

Pocock draws on Frederick Brooks' The Design of Design to argue that in collaborative design, the essential artifact is not static documentation, but the design concept: the shared, intangible understanding of the system's internal mechanism and boundaries held between collaborators.

Engineering practice

Before generating implementation code, establish design alignment with grill-me or grill-with-docs from the mattpocock/skills repository:

  • The shared grilling procedure maps a decision tree and asks the currently unblocked questions in rounds, with a recommended answer for each;
  • grill-with-docs combines that interview with domain modeling so resolved terminology and hard-to-reverse decisions can be captured in CONTEXT.md and ADRs;
  • After alignment, the separate to-spec and to-tickets commands can synthesize the current conversation into a specification or tracer-bullet tickets.

Vocabulary Mismatch: Grounding Context with Ubiquitous Language

Failure mode

The agent generates verbose prose and misaligned terminology, introducing function and class names that drift away from actual business concepts.

Theoretical basis cited in talk

Pocock cites Eric Evans' Domain-Driven Design to emphasize the Ubiquitous Language: domain experts, developers, and code must share a single, strictly defined vocabulary to eliminate translation friction.

Engineering practice

  • Extract a structured domain glossary from the codebase and specifications (such as a CONTEXT.md defining entities, operations, and lifecycle states);
  • Provide this glossary in the active agent context during planning, generation, and refactoring;
  • Consistent terminology keeps the agent's internal reasoning concise and tightly bound to domain models.

Outrunning Headlights: Enforcing Limits with TDD and Static Typing

Failure mode

The model emits a large batch of code in a single turn. The code looks convincing but fails at runtime or drifts into unverified assumptions.

Theoretical basis cited in talk

Pocock borrows the principle from Andrew Hunt and David Thomas's The Pragmatic Programmer: don't outrun your headlights. Safe driving speed at night is bounded by how far your headlights reach. In software engineering, the speed of feedback is the maximum safe speed of development.

Engineering practice

  • Static types and runtime environments: Use TypeScript or strict typing, alongside direct terminal or browser execution tools;
  • Test-Driven Development (TDD):
    1. Write a focused test defining expected input and output;
    2. Run the test to confirm a failing baseline (red);
    3. Prompt the agent to produce the minimal implementation that passes (green);
    4. Refactor and refine module design.
  • TDD constrains the model to small, verifiable steps, which helps limit runaway generations.

Shallow Sprawl: Deep Modules That Hide Complexity

Failure mode

The codebase consists of numerous fragmented helper functions and shallow files. The agent exhausts its context window trying to navigate sprawling dependency graphs.

Theoretical basis cited in talk

Pocock refers to John Ousterhout's A Philosophy of Software Design to distinguish two module designs:

  • Shallow Modules: Complex interfaces with thin internal logic, imposing high cognitive and navigation overhead;
  • Deep Modules: Simple, stable interfaces encapsulating substantial internal logic and state mechanics.

Engineering practice

  • Refactor fragmented utilities into cohesive deep modules with improve-codebase-architecture, which scans for deepening opportunities and applies the deletion test;
  • Enforce minimal, stable interface contracts;
  • Place testing boundaries at module interfaces, hiding internal implementation details so the agent can safely modify internals without needing full-repo context.

Cognitive Overload: Gray-Box Delegation and System Design

Failure mode

As generation speed accelerates, human developers attempting to review every generated line experience cognitive fatigue and review bottlenecks.

Theoretical basis cited in talk

Pocock connects this to Kent Beck's emphasis on daily investment in system design: the enduring craft of software engineering lies in defining system boundaries, data flows, and component responsibilities rather than typing syntax.

Engineering practice

  • Gray-box delegation:
    • Treat deep modules as gray boxes in non-critical paths;
    • The developer defines interface contracts and verifies external integration tests, while delegating internal implementation mechanics to the agent;
    • Maintain strict white-box line-by-line review for security boundaries, authentication, financial math, and core data stores.
  • Role separation: The human operates as a Strategic Architect (defining specifications, interfaces, and test suites); the AI operates as a Tactical Programmer (implementing logic within established contracts).

Principle and Practice Mapping

DimensionClassical PrincipleTheorist Cited in TalkAI Failure ModeAgent Practice
IntentDesign ConceptFrederick Brooks (The Design of Design)Spec drift, misaligned codeSocratic grilling (grill-me) before writing assets
VocabularyUbiquitous LanguageEric Evans (Domain-Driven Design)Verbose drift, mismatched namingStructured domain glossary (CONTEXT.md) in context
PacingDon't outrun headlightsHunt & Thomas (The Pragmatic Programmer)Broken runtime, unchecked generationTDD step-by-step loops, static typing
StructureDeep ModulesJohn Ousterhout (A Philosophy of Software Design)Context degradation in shallow sprawlSimple interfaces, encapsulated complexity
WorkflowSystem Design InvestmentKent BeckReview fatigue, cognitive bottleneckGray-box delegation, interface-level testing

Implementation Boundaries

  1. Automated test coverage is a prerequisite for gray-box delegation: Without automated interface assertions, gray-box delegation degrades into unmonitored code rot.
  2. High-risk subsystems require white-box inspection: Authentication, cryptographic logic, financial transactions, and database migrations require complete human line-by-line review.
  3. Restructure before delegating: Consolidating shallow sprawl into deep modules before running batch agent tasks can improve navigability.

Sources and Tool Definitions

The five failure-mode/principle pairings above are attributed to Pocock's talk; the tool behavior is grounded in the current repository definitions: