Deterministic Tools for Nondeterministic Agents
As we move to intent-driven development, automated checks can replace routine manual reviews while protecting the behavior and quality we expect.
Intent-driven development needs a different way to decide when the work is done.
In The Idea Layer, I argued that intent is becoming the unit of engineering work. We describe what we want, and agents produce the implementation. But moving code generation to agents only takes us halfway if every change still needs human approval.
The bottleneck we kept
Agents can produce changes faster than people can review them. When that happens, work collects in a review queue instead of reaching users. Routine human review becomes the limit on delivery. To complete the transition, we need to turn expected behavior and quality into checks that software can run. What must be true before we accept a change, and which parts can we check automatically?
Generation is automated; acceptance still waits for a person.
Which parts of quality still matter?
In Beyond the Idea Layer: Rethinking Quality, I argued that clear specifications, system boundaries, small modules, and strong tests become more important than code written for human readers. Readability still helps people investigate failures, but polishing every generated implementation is a poor reason to require manual review.
I was too quick to dismiss duplication. That argument might hold if generation became cheap enough to rebuild the whole codebase from scratch each time, though consistency would still need checking. In agentic loops that change an existing repository, duplication remains relevant. A business rule copied across modules needs coordinated updates; a missed copy creates inconsistent behavior. Keeping that rule in one place keeps each change small and consistent. The goal is not perfect DRY. It is to keep related behavior consistent without forcing unrelated code into a shared abstraction.
Scalability, reliability, and separation of concerns still matter. They affect how the system behaves, not just how its code looks. The next question is how to protect these properties without asking someone to inspect every diff.
Give agents tools, not just instructions
In an interview on How Many CTOs Does It Take?, Robert “Uncle Bob” Martin describes looking at system behavior and tool results rather than reading syntax. Martin points to deterministic tools that measure duplication, module size, dependency direction, complexity, and coverage.
An agent is a nondeterministic process: the same task can produce different implementations and judgments. Its own approval cannot serve as an independent acceptance test. Repeatability alone is not enough. A model can consistently approve code without checking the constraints we care about. For automated acceptance, we need explicit rules that we can inspect, such as “the domain module must not import the database adapter.”
Explicit checks do not replace every useful part of model-based review. A model can flag suspicious changes that existing rules do not cover and suggest new checks. Those findings can guide human investigation, while the agreed checks decide whether routine changes can be accepted.
Make the important expectations executable
An acceptance contract is the set of conditions a change must meet. It connects our quality priorities to concrete checks:
- Clear specifications: acceptance tests check examples of expected behavior; they cannot choose the right outcome for us.
- System boundaries: contract tests check component interactions. Architecture tools enforce dependency rules. Import Linter checks Python import rules; ArchUnit tests Java package dependencies, layers, and cycles. For JavaScript and TypeScript, dependency-cruiser does the same.
- Small modules: tools such as Radon (Python) measure size and complexity. Set project-specific limits rather than optimizing a score. Martin mentions CRAP (Change Risk Anti-Patterns), developed by Alberto Savoia and Bob Evans. It combines complexity and coverage to flag risk, not certify quality.
- Strong tests: regression and invariant tests protect behavior. Mutation-testing tools such as PIT (Java) change code deliberately to check whether tests detect faults. This exposes weak assertions better than coverage alone, but proves neither completeness nor correctness.
- Consistent rules: tools such as jscpd detect copied code blocks. These are candidates for investigation, not proof of duplicated business rules.
Reliability and scalability need failure and load tests too. These depend on timing and environment, so they need more care than a static check to stay repeatable.
We can evolve the contract as we learn rather than specify everything upfront.
A feature can work and still fail acceptance
Consider a hypothetical order service. An agent adds a discount rule, and tests produce the expected totals. But the domain module imports a database adapter, breaking an agreed boundary.
The agent reads the failure, fixes the dependency, and reruns the checks. Acceptance requires both behavior and architecture checks to pass.
The human decision was that the domain should remain independent of storage details. The checker cannot tell us whether that decision was wise; it can enforce it consistently.
People choose the boundary. Software checks each change against it.
Close the loop automatically
The workflow becomes:
Intent and constraints → implementation → validation → repair → validation → acceptance.
When a check fails, it should explain which rule was broken and where. For example, a dependency checker can identify a forbidden import, giving the agent a concrete problem to fix.
Checks must run outside the agent’s control and govern acceptance. They must run on the exact revision being accepted. Keep contract files outside the agent’s implementation write scope. These include lint configuration, architecture rules, and acceptance-test assertions. A trusted runner loads the approved contract; a separate gate detects changes to protected files and routes them to human-controlled review. The agent may propose new tests or contract changes, but cannot approve them or weaken existing assertions to get a pass.
Before the agent starts, agree on which changes it can complete automatically and which need human review. For example, changes to security code, database structure, public APIs, or the acceptance rules should go to a person. Set limits on how much of the system the agent may change in one task. Passing tests does not remove these limits, and the workflow must enforce them independently of the agent.
Bound the retries too. If the agent still cannot pass the checks, it should stop and ask for help. Accepting a change and allowing it to deploy are separate decisions.
Automate within agreed limits; route exceptions to people.
Move the work, not the responsibility
We still own outcomes, constraints, and acceptable risk. The work moves toward improving the checking system and investigating exceptions. Martin acknowledges that experienced developers notice problems tools miss; that is where their attention should go.
Intent-driven development needs intent-driven acceptance. If every generated change must wait for human review, we have only completed half the transition.
Resources
- The Idea Layer and Beyond the Idea Layer: Rethinking Quality: the earlier arguments this post develops.
- Interview with Robert “Uncle Bob” Martin: inspiration for the deterministic-tool discussion.
- This Code is CRAP: Alberto Savoia on the metric’s origins and limitations.
- Import Linter, ArchUnit, and dependency-cruiser: dependency rules.
- jscpd, Radon, and PIT: duplication, metrics, and mutation testing.
Related Posts
Beyond the Idea Layer: Rethinking Quality
Traditional code quality concerns like DRY and readability were built for a world where humans maintain code. When intent is the artifact and code is disposable, what does quality actually mean?
AI-Driven EngineeringFrom Vibe Coding to Spec-Driven Development
Why ad-hoc AI prompting fails at scale and how structured approaches like BMAD and MCP are reshaping software engineering into a human-agent collaboration.
AI-Driven EngineeringThe Idea Layer
We're shifting from code as the unit of work to ideas as the unit of work. Exploring how AI is transforming developers into orchestrators and why legacy migration may be the unexpected killer app.