Article summary

TL;DR

  • AI coding agents reduce implementation cost, but the team still owns architecture, product intent, security, operability, and the decision to release.
  • Use a real red-green-refactor loop: make the new test fail for the expected reason before the agent writes the implementation.
  • Let fast unit tests, integration tests, end-to-end tests, and human product checks protect different risks instead of asking one layer to prove everything.
  • Push agents hard in low-risk projects so you learn where they are reliable before delegating high-impact production work.
  • Codify repeatable engineering practice in repository instructions, agent skills, review loops, documentation, and deterministic CI checks.

01 / PRODUCTION FIRST

AI makes implementation cheap, not production failure.

AI coding agents can build a convincing prototype in an afternoon. They can inspect a repository, edit code across several files, write tests, explain failures, and prepare a pull request. That changes what one developer or a small team can attempt.

It does not change what production software requires.

A feature still has to solve the right problem. The code has to remain understandable when the original conversation is gone. Tests have to protect behavior instead of preserving a mistake. Operators need enough evidence to diagnose failures. The team needs a safe path to deploy and reverse the change.

I explored this production-minded view of AI-assisted development with Shep Alderson, a software developer and site reliability engineer with more than 15 years of experience. The conversation covers TDD, layered tests, agent orchestration, refactoring, documentation, and the difference between fast code generation and reliable engineering.

YouTube video

Playing this video connects to YouTube, which may process device and connection data. Read the provider privacy policy.

The conversation grew out of a Reddit discussion about whether AI-generated tests are becoming good enough. That question led to a much wider one: what process turns fast AI output into software a team can trust?

The advice applies whether you use GitHub Copilot, Cursor, Claude Code, OpenAI Codex, or another repository-aware coding agent. Models and interfaces will improve. The engineering problem will remain: how do you convert a plausible change into evidence that the change is ready to operate?

02 / WHAT AI CHANGES

The biggest shift is what developers can attempt.

AI discussions often reduce productivity to typing speed. That misses the more interesting change. An experienced engineer can give an agent a goal, encode years of lessons in instructions, and attempt work that would previously have been too large or too repetitive for the available time.

A small team can maintain more tests. A backend developer can explore a frontend implementation while still applying known engineering boundaries. A solo developer can ask independent agents to implement and review a change. Documentation that used to be postponed can become part of the same workflow.

This does not make every developer equally capable. In fact, AI can amplify differences in judgment.

A senior engineer recognizes when a task hides a data migration, a security boundary, or an operational risk. They can describe the tradeoff, constrain the agent, and reject code that is locally correct but architecturally wrong. A newer developer may receive a polished implementation without knowing which assumptions need to be challenged.

That is why fundamentals matter more, not less:

  • how data and control move through a system;
  • how modules own responsibilities;
  • how failures cross process and network boundaries;
  • how tests express behavior;
  • how deployments, monitoring, and rollback work;
  • how users actually experience the product.

Learning syntax still matters. Writing code teaches you how to think precisely and helps you read what the agent produces. But syntax is no longer the scarce part of many tasks. The scarce part is deciding what should be built, how it should fit the system, and what evidence is enough to trust it.

AI can feel like having a senior developer in your pocket. Treat that as access to implementation capacity, not borrowed accountability. The agent does not carry the pager, answer to users, or live with the maintenance cost.

03 / THE BORING PROCESS

Reliable results come from a repeatable feedback loop.

It is tempting to search for the model that can one-shot an application. Production work benefits more from a boring process that gets useful results from several capable models.

The process starts before code:

  1. Define the outcome and important constraints.
  2. Let the agent inspect the repository and existing conventions.
  3. Resolve ambiguous behavior before implementation.
  4. Work in a small, reviewable slice.
  5. Run fast feedback after each meaningful step.
  6. Review the complete diff against the requirement.
  7. Use independent checks before merge and deployment.

This approach accepts that both humans and agents make mistakes. It does not depend on a perfect prompt or a perfect model. A missed assumption should become visible in a plan review. A broken behavior should appear in a focused test. A bad import or type should fail a deterministic check. A deployment mistake should be caught by a smoke test or health check before users find it.

The “one minute of coding, one week of debugging” trap appears when code generation is treated as the complete development loop. A plausible local result skips the slower questions:

  • Which behavior must remain unchanged?
  • What happens when a dependency is slow or unavailable?
  • What state can be left behind after a partial failure?
  • How will we recognize the failure in production?
  • Can another engineer understand and reverse the change under pressure?

Use AI to reduce implementation effort, not the standard of evidence required for production.

The agent is most useful inside a system of feedback. Tests, static analysis, browser checks, logs, reviews, and deployment validation tell it whether the last action moved the code toward the intended result. Without those signals, the agent is guessing with greater speed.

04 / TIP 1: USE REAL TDD

Make the test fail before the agent writes the implementation.

Test-driven development gives an AI coding agent a concrete target and a tight feedback loop. The classical cycle remains useful:

  1. Describe one behavior.
  2. Write a test for the expected result.
  3. Run it and observe the expected failure.
  4. Write the smallest implementation that makes it pass.
  5. Refactor while the test stays green.
  6. Repeat with the next behavior.

The order matters. If the agent writes production code and tests together, a green result does not prove that the test could detect the missing behavior. The test may reproduce the same assumption as the implementation. Another existing code path may already satisfy it. The assertion may never exercise the part of the system you intended to change.

Agents do not always follow TDD because a prompt contains the letters “TDD.” State the workflow explicitly:

```text Implement this change with red-green-refactor.

First add the smallest behavioral test and run it before modifying production code. Confirm that it fails for the expected reason. Then implement the smallest change that makes it pass.

Do not weaken or remove the test to obtain a green result. After the focused test passes, run the related tests and the repository’s complete quality gate. Report the commands and results. ```

This loop takes longer and uses more tokens than generating the code and test in one step. That is a reasonable trade when the behavior matters. It gives you evidence that the test protects something the implementation did not already do.

For a bug fix, preserve the failure as a regression test. For new behavior, make the requirement observable at a stable boundary: a return value, exception, response, persisted state, emitted event, or user-visible result. Avoid assertions tied only to private helper calls unless that interaction is itself the contract.

If pytest is new to you, start by learning how to write, run, and deliberately fail your first Python test. You need to understand the feedback loop before you can judge whether an agent is using it correctly.

05 / TEST THE RIGHT LAYER

Let each test layer protect a different risk.

AI makes test code cheaper to produce. That is a chance to cover behavior that teams previously left untested, but more tests are useful only when each layer has a clear job.

Unit tests protect local rules

Unit tests should be fast, focused, and numerous where the code contains meaningful decisions. They are well suited to TDD because the feedback loop is short. An agent can enumerate ordinary inputs, boundaries, invalid values, and error branches much faster than a developer wants to type them.

Review the assertions. Generated unit tests sometimes lock onto one seeded string, duplicate the production expression in the expected value, or assert implementation details that a safe refactor should be allowed to change.

Integration tests protect boundaries

Integration tests show that components agree about data, transactions, permissions, protocols, and failure behavior. They are more expensive than unit tests, so choose boundaries that carry real risk: the database, an API client, a queue, authentication, file storage, or another process.

The question is no longer only “does this function return the expected value?” It is “do these parts of the system work together under the conditions production will create?”

End-to-end tests protect user intent

Browser tests can record the flows that make the application useful. They help preserve intent across refactoring because they interact with the product closer to the way a user does.

They are also slower and more fragile. A suite of more than one hundred browser tests may be perfectly acceptable before a pull request is merged, but painful inside the seconds-long development loop. Run the smallest relevant check while working and the broader suite at an appropriate gate.

Keep end-to-end tests centered on durable behavior. If every internal refactor requires rewriting them, the tests probably depend on implementation details or unstable selectors.

A human still has to use the product

No test fully captures how a calendar should feel when a meeting is dragged, whether an error message gives the user a useful next step, or whether a workflow solves the original business problem. Agents can operate browsers and inspect screenshots, but product judgment still needs a person.

You can have perfect unit coverage and still build the wrong application.

Use 100% coverage as a forcing function, not a certificate

One of Shep’s strongest recommendations is to stop accepting low test coverage simply because tests take time to write. With an agent, bringing a touched module to full coverage can be a small additional task rather than another sprint.

That is a useful challenge to old assumptions. It is not proof of correctness.

Coverage tells you which code executed. It does not tell you whether the expected result is right, whether important behavior is missing, or whether mocks removed the real risk. Scope the policy sensibly, review every new assertion, and avoid micro-tests that protect punctuation or private structure instead of a stable contract.

The valuable standard is not a number by itself. It is that every production rule has a test at the cheapest layer capable of detecting when the rule breaks.

06 / REFACTOR WITH CONTRACTS

Refactoring becomes safer when behavior is already explicit.

A greenfield project built with TDD has a major advantage: the tests record intended behavior before the implementation grows complicated. An agent can split a large module by responsibility, reduce duplication, or replace an internal design while the stable contracts remain protected.

Give the refactoring task a hard boundary:

```text Refactor the implementation without changing the public behavior or tests. Do not weaken, delete, or rewrite tests to make the refactor pass. Keep each step small and run the focused suite after every structural change. ```

“Do not touch the tests” is especially important when the purpose of the tests is to hold behavior constant. Otherwise an agent may update both sides until they agree, hiding a regression inside a tidy diff.

Legacy code needs another step. Existing tests may cover only part of the behavior, and the current implementation may contain accidental behavior that should not become permanent.

Before a large refactor:

  1. Ask the agent to explain the code path and its dependencies.
  2. Compare that explanation with product requirements and operator knowledge.
  3. Add characterization tests for behavior you have verified and need to preserve.
  4. Add focused regression tests for known failure modes.
  5. Refactor one responsibility at a time.
  6. Keep the old and new behavior observable until the transition is complete.

Architecture still needs direction. If a repository already contains a battle-tested component, an agent working with a narrow context may create a duplicate. Explicitly ask it to search for existing abstractions and follow established boundaries. Name relevant principles such as DRY or dependency inversion when they express a real design goal, then inspect whether the result actually improves cohesion rather than merely moving code.

The tests make change safer. They do not choose the architecture for you.

07 / TIP 2: FIND THE EDGES

Push the agent hard where failure is cheap.

You cannot learn an agent’s limits from a model announcement or a carefully selected demo. You learn them by giving it real work, observing the result, and changing the level of supervision.

A low-risk side project is a good laboratory. Try to complete a feature without manually writing the implementation. Ask the agent to plan, test, build, review, and document it. Let it work across backend, frontend, infrastructure, or mobile code where you understand enough of the foundations to evaluate what comes back.

The point is not to prove that you never need to code. It is to build a practical trust map:

  • Which tasks can the agent complete with ordinary review?
  • Where does it need a precise contract?
  • When does a long context reduce its performance?
  • Which checks help it recover without supervision?
  • Where does it duplicate code or miss an established abstraction?
  • Which risks still require your direct attention?

That map should change the way you delegate production work. You may allow broad autonomy for a low-impact rendering change while staying close to authentication, authorization, payments, data migrations, concurrency, or infrastructure. The right boundary depends on the product and its blast radius.

Experiment with models, skills, prompts, and review patterns. Keep the successful parts only when you can explain why they work. A workflow copied from someone else is a hypothesis, not a guarantee.

This is a better use of exploration than chasing a universal “best AI coding agent.” The useful question is which combination of agent, context, tools, constraints, and evidence works for this class of change in your system.

08 / TIP 3: PACKAGE THE PROCESS

Turn engineering practices into reusable agent skills.

Teams already encode development policy in linters, test commands, templates, checklists, and CI. Agent instructions and skills extend the same idea to work that requires judgment.

A focused skill can teach an agent how your team performs TDD, diagnoses a bug, prepares a specification, reviews a diff, or validates a deployment. The useful skills are small enough to understand, easy to adapt, and explicit about their stopping conditions.

Do not start by writing every workflow yourself. Matt Pocock’s collection of engineering skills includes examples for requirements discovery, TDD, debugging, architecture, implementation, and code review. Read the instructions, take the relevant idea, and adapt it to your repository and risk model.

The same discipline applies to multi-agent workflows. One useful pattern separates responsibility:

  1. A planning step defines the intended behavior and boundaries.
  2. An implementation agent works on a small part of the plan.
  3. An independent review agent checks the diff against both engineering standards and the original plan.
  4. The implementation is revised until the known gaps are closed.
  5. Deterministic checks decide whether the result can move forward.

Separation helps with context as well as review. An agent focused on one bounded task does not need to carry the entire exploration and implementation history. A reviewer with fresh context is less invested in defending the choices that produced the code.

Do not confuse more agents with more truth. Similar models can share blind spots, and a review loop can approve the wrong requirement with great confidence. Human ownership and executable checks still close the loop.

A reusable workflow earns its place when it improves outcomes across several tasks. Keep it short, version it with the code when appropriate, and remove rules that no longer change agent behavior.

09 / TIP 4: DOCUMENT INTENT

Keep the reason for the code close to the code.

Agents are excellent at generating documentation, but the biggest gain is not having more Markdown files. It is giving future developers and agents the intent they need at the place where they make a change.

A useful docstring or module contract explains:

  • the purpose of the code;
  • the important invariant or business rule;
  • behavior at a boundary;
  • a side effect that is easy to miss;
  • an error or safety condition callers must handle.

It should not narrate the syntax. “Returns the user” adds little to a function named `get_user`. “Returns only active users visible to the requesting tenant” records a contract that both a reviewer and an agent can test.

This adjacent documentation helps because an agent often reads a focused region instead of the entire system. The function, its tests, and a concise statement of intent can fit in the same context. That reduces the chance that a later edit optimizes the implementation while forgetting why the behavior exists.

Documentation can also participate in review. If a function changes but its stated contract does not, an automated check or review agent can flag the mismatch. Some teams may choose complete docstring coverage for public contracts because agents make the writing cost small.

Coverage is not correctness here either. A generated docstring can faithfully describe the wrong code. Review intent, not the presence of text. Keep architecture decisions, operational runbooks, and product requirements in the locations where their full context belongs, then link them from code when that relationship matters.

Good documentation creates a feedback loop:

  1. The requirement informs the documentation.
  2. The documentation guides tests and implementation.
  3. Tests verify the observable contract.
  4. Review checks that code and documentation still agree.

That loop helps humans and agents for the same reason: both do better work when the system explains what must remain true.

10 / THE PRODUCTION GATE

Put independent checks between the agent and production.

A strong prompt can improve the first implementation. A strong engineering system catches mistakes no matter who wrote the code.

Before merging an AI-assisted change, match the evidence to its risk.

Behavior

  • The requirement and important non-goals are explicit.
  • Focused tests protect normal behavior, boundaries, and known failures.
  • A regression test was observed failing before the bug fix.
  • A human has exercised the user flow when experience or product intent matters.

Code and architecture

  • The complete diff is understood, not merely summarized by the agent.
  • Existing abstractions were reused where they still fit.
  • New dependencies, permissions, migrations, and public contracts received focused review.
  • Documentation explains durable intent without repeating obvious code.

Delivery and operations

  • Formatting, static analysis, tests, and coverage checks pass independently.
  • A production-like build or smoke test covers deployment-path changes.
  • Health signals, logs, and alerts can reveal failure after release.
  • The team knows how to roll back or disable the change safely.

Security and data

  • Authentication and authorization behavior is tested at the boundary.
  • Secrets and personal data do not appear in prompts, logs, fixtures, or generated files.
  • Destructive operations and irreversible migrations receive human approval.
  • External tool access is limited to what the task actually needs.

Not every task needs the same ceremony. A copy edit and an authentication migration have different blast radii. Scale the depth of review and validation with the risk while keeping a baseline: understand the contract, inspect the diff, run the real checks, and know how a failure will be detected.

If you want to see these ideas applied across planning, repository instructions, tests, Docker, CI/CD, cloud deployment, and AI-assisted pull request review, follow the OpenAI Codex full course.

Then use one real task to improve your own workflow. Write the behavior first. Make the agent produce a failing test. Keep the implementation small. Ask an independent reviewer what the diff missed. Run the deterministic gate. The valuable productivity gain is not how quickly code appears. It is how quickly the team can reach a change it is prepared to operate.