London, United Kingdom

Contact us

Applications with demanding backends.

Secure. Trusted. Intelligent.

© 2026 Silver Vault Project

Company engineering studio

Back to Articles

The AI Coding Workflow That Actually Works: Context Code Tests Verification

The most reliable AI development workflow is not built around a perfect first answer. It is built around proving whether the answer works.

Throughout this series, we have looked at the infrastructure surrounding modern AI coding agents.

We explored:

  • 01 why coding assistants hallucinate when context is poorly managed
  • 02 how tools discover relevant code inside large repositories
  • 03 how AGENTS.md, project rules, and documentation teach agents how your software works
  • 04 how MCP can connect agents to documentation, APIs, databases, and observability platforms

But none of those things solve the most important problem.

How do you know the code the AI produced is actually correct?

An AI agent can understand your architecture. It can find the right files. It can follow your repository instructions. It can produce code that looks clean and convincing. And it can still be wrong.

That is why the most reliable AI development workflow is not built around getting a perfect answer from the model. It is built around giving the model a way to prove whether its answer works.

The workflow looks something like this:

UnderstandInspectPlanImplementType checkLintTestBuildRunVerifyCorrect

This feedback loop is one of the biggest differences between using AI as a code generator and using AI as part of a real software engineering process.

The wrong workflow: prompt → generate → accept

One of the easiest mistakes to make with AI coding tools is treating them like extremely fast developers whose output can be trusted because it looks reasonable.

  1. Describe feature
  2. AI writes code
  3. Looks good
  4. Merge

The problem is that language models are particularly good at producing plausible looking output. That is valuable when generating code. It is dangerous when plausibility is mistaken for correctness.

Consider an AI generated function:

cancelSubscription.ts
async function cancelSubscription(customerId: string) {
  const subscription = await getSubscription(customerId);

  await stripe.subscriptions.cancel(subscription.id);

  await updateCustomer(customerId, {
    subscriptionStatus: "cancelled",
  });
}

At a glance, it looks perfectly reasonable.

But there may be questions the code itself does not answer:

  • 01 What happens if Stripe succeeds but the database update fails?
  • 02 Is cancellation immediate or scheduled for the end of the billing period?
  • 03 Is customerId guaranteed to map to exactly one subscription?
  • 04 Should an event be published?
  • 05 Is this operation idempotent?
  • 06 Does another service already own this workflow?
  • 07 What happens to active entitlements?
  • 08 Are there existing tests defining the expected behaviour?

The code can be syntactically correct and still be architecturally or functionally wrong. That is why AI generated code needs evidence.

Fig. 01 — Plausible is not proven
A polished silver surface with hairline fractures visible under raking light
Language models are unusually good at producing output that looks finished. Verification is how you find the fractures.

Step 1: Understand the task before editing

The first step should not be code generation. It should be understanding.

For a trivial change, this may take seconds. For a multi file feature or production bug, the agent should inspect the system first.

Suppose the task is:

Add support for cancelling a subscription at the end of the current billing period.

A useful investigation might include:

  1. Find subscription service
  2. Find existing Stripe integration
  3. Inspect subscription database model
  4. Read existing cancellation tests
  5. Inspect entitlement logic
  6. Understand current lifecycle

Only then should implementation begin.

This principle is now visible in the workflows promoted by modern coding agents. Claude Code’s current best practices documentation explicitly recommends explore → plan → implement → commit for larger changes, while Cursor recommends using planning for tasks that span multiple files or require significant research.

The point is not that every typo needs a formal implementation plan. It is that the amount of investigation should match the risk and complexity of the change.

Step 2: Define what “correct” means

Before the agent writes code, define the expected result. This is one of the most valuable things a developer can do.

Instead of:

Add subscription cancellation.

give the agent concrete success criteria:

success criteria
A customer can schedule cancellation.

The subscription remains active until the
current billing period ends.

Stripe should not cancel the subscription immediately.

The database must record the scheduled cancellation.

Existing subscriptions that are already scheduled
for cancellation should not produce an error.

Unit and integration tests must pass.

Now the agent has something measurable. This turns a vague request into a set of behaviours.

What observable conditions would prove this task is complete?

Those conditions might include:

  • a test passes
  • an endpoint returns the expected response
  • the application compiles
  • a page renders correctly
  • a database migration applies successfully
  • an API contract remains unchanged
  • a visual interaction behaves correctly

This becomes the basis of verification later.

Step 3: Change the smallest reasonable surface area

AI agents are capable of changing many files quickly. That does not mean they should.

A common failure mode is unnecessary expansion of scope. You ask for one feature. The agent decides to rename several abstractions, move files, rewrite a utility, upgrade a library, and refactor unrelated tests.

Each additional modification increases the number of assumptions that need to be correct.

Make the smallest coherent change that satisfies the requirement.

Suppose the relevant architecture is:

  1. SubscriptionController
  2. SubscriptionService
  3. BillingProvider
  4. StripeBillingProvider

If cancellation behaviour can be implemented by modifying SubscriptionService, StripeBillingProvider, and related tests, there is probably no reason to redesign the entire billing module.

Smaller changes are:

  • easier to understand
  • easier to test
  • easier to review
  • easier to revert
  • less likely to introduce unrelated regressions

This matters even more when an AI agent is performing the work.

Step 4: Let the compiler and type system challenge the AI

One of the cheapest forms of verification is compilation or type checking.

Suppose an agent invents:

invented API
await subscriptionRepository.cancel(id);

but the actual interface is:

real API
subscriptionRepository.scheduleCancellation(id, cancelAt);

A type checker can detect the mismatch immediately. Instead of asking the model whether the code is valid, run pnpm typecheck, tsc --noEmit, or the equivalent compiler for your language.

The compiler is not interpreting intent. It is evaluating deterministic constraints. That makes it an excellent partner for probabilistic AI systems.

  1. AI hypothesis
  2. Compiler
  3. Error
  4. AI receives evidence
  5. Correction

Use deterministic systems to constrain probabilistic systems.

Step 5: Let linters handle mechanical correctness

The same principle applies to linting and formatting. Instead of loading dozens of style instructions into the model—single quotes, sorted imports, unused variables, naming formats, deprecated APIs—encode those rules into tooling. Then run pnpm lint.

If the command fails, the agent gets concrete feedback:

lint
src/billing/service.ts
42:7 error 'result' is assigned a value but never used

That is far more useful than hoping the agent remembers every style rule.

Some coding tools already automate this loop. Aider, for example, automatically lints edited files by default and can be configured to run test commands after AI changes. If those commands fail, the errors can be returned to the model so it can attempt a fix.

The important part is not which coding assistant performs the loop. The important part is that the loop exists.

Step 6: Tests should verify behaviour, not implementation

Tests become even more important when AI is writing code. But there is a trap.

An AI can write both the feature and the tests. If the model misunderstands the requirement, it may create tests that validate its own incorrect interpretation.

For example:

Subscription cancellation should occur at the end of the billing period.

The agent accidentally implements immediate cancellation. Then writes:

the wrong test
expect(stripe.subscriptions.cancel).toHaveBeenCalled();

The test passes. But both the implementation and the test are wrong.

This is why good tests should be connected to expected behaviour, not merely generated from whatever implementation currently exists. A better test might verify:

behaviour
Given an active monthly subscription

When the customer requests cancellation

Then the Stripe subscription is configured
to cancel at the period end

And the customer retains access until that date

And the local subscription state records
the pending cancellation

The test now represents the requirement. That makes it useful as an independent verifier.

Test at several levels

Not every bug can be caught with unit tests. A strong verification pipeline may contain several layers.

  1. Unit testsVerify isolated business logic. They are fast and useful for edge cases—for example, that scheduling cancellation records cancelAtPeriodEnd without immediately cancelling the Stripe subscription.
  2. Integration testsVerify that multiple pieces of the system work together: API endpoint, service, repository, database. These catch problems that unit tests often miss.
  3. Contract testsVerify boundaries with external systems or internal services: API response formats, event schemas, webhook contracts, GraphQL schemas.
  4. End to end testsVerify the actual user workflow: log in, open billing, cancel subscription, see confirmation. The closer the test gets to real usage, the more assumptions it can validate.

The trade off is that higher level tests are generally slower and more expensive to run. The goal is not to run every possible test after every tiny change. It is to run the right verification for the risk of the change.

Step 7: A successful build is another independent signal

Tests may pass while the production application still fails to build. That is why a production build should often be part of the verification pipeline.

For example, pnpm build might reveal a missing module, an invalid server/client component boundary, a missing environment variable, a bundling failure, or a generated type mismatch.

These failures are useful because they come from a system other than the language model. Each independent verification mechanism reduces the number of incorrect assumptions that can survive.

Think of it as multiple filters:

  1. AI generated change
  2. Type checker
  3. Linter
  4. Unit tests
  5. Integration tests
  6. Build
  7. Runtime verification

A bug that slips through one layer may be caught by another.

Fig. 02 — Independent filters
Stacked silver mesh filters catching different debris as light falls through
Type checking, linting, tests, builds, and runtime checks are not redundant. Each layer catches a different class of mistake.

Step 8: Run the application

Eventually, some changes need to be tested in the real application.

Imagine an agent changes a checkout button. The type checker passes. The linter passes. The unit tests pass. The application builds. But when you open the page, the cancel control is hidden underneath a modal. Or clicking it causes the page to refresh unexpectedly. Or the confirmation message never appears.

Static verification cannot catch everything. The agent needs runtime evidence.

Modern coding tools are increasingly building this directly into their workflows. Cursor’s current agent tooling includes browser control for testing applications and verifying visual changes, while Claude Code now documents both browser driven verification and a /verify workflow for building, running, and observing applications rather than relying only on tests or type checks.

This is an important evolution. AI coding is moving from:

I wrote code that appears correct.

toward:

I changed the system and observed that the expected behaviour occurred.

That is a much higher standard.

Fig. 03 — Runtime evidence
A dark control room observing a faintly glowing application through a glass viewport
A passing test suite is not the same as seeing the expected behaviour in the running application.

Visual verification matters for frontend development

Frontend code is a particularly good example. Suppose the requirement is to make the pricing cards display correctly on mobile. An agent can successfully produce valid CSS. But the real question is: does it actually look correct?

The verification loop needs to include the browser:

  1. Implement layout
  2. Run application
  3. Open page
  4. Resize viewport
  5. Inspect visual result
  6. Detect problem
  7. Modify CSS
  8. Verify again

This is much closer to how a developer or QA engineer would work. It also changes the role of screenshots. A screenshot is no longer merely an output. It becomes context for the next reasoning step.

Production bugs need runtime evidence

The same principle applies to debugging.

Imagine a customer reports:

Every few hours our order synchronisation stops working.

The agent searches the code and finds:

sync.ts
try {
  await syncOrder(order);
} catch {
  return;
}

It might conclude that the swallowed exception is the cause. Maybe. But perhaps the actual failure is an API rate limit, a database connection timeout, or an invalid token.

Guessing

Read code → guess cause → change code

Evidence

Reproduce or inspect failure → collect evidence → form hypothesis → change code → reproduce again → confirm behaviour changed

This is why logs, error traces, test failures, and runtime output are so valuable. They ground the agent’s reasoning in reality.

Failure is useful context

A test failure is not merely an error. It is new information.

Suppose the agent writes an implementation and receives:

assertion
Expected:
subscription.status = "active"

Received:
subscription.status = "cancelled"

The model now has evidence that its understanding of the requirement was wrong. It can reconsider the implementation.

This creates an iterative reasoning process:

HypothesisChangeVerificationFailureNew contextBetter hypothesis

This is much more reliable than demanding perfect reasoning in a single model response. In fact, the ability to fail cheaply may be one of the most important characteristics of a good AI engineering environment.

Fig. 04 — Cheap failure
A circular silver mechanical loop returning evidence to the start
The model does not need a perfect first attempt. It needs a loop that turns failure into the next piece of context.

Make failure cheap

A useful development system makes mistakes easy to detect, easy to understand, and easy to reverse.

Git plays an important role here. Before an agent begins a substantial change, you should know exactly what state the repository is in. After the change, you should be able to inspect git diff and answer: what exactly did the agent change? If something goes wrong, you should be able to restore the previous state.

Tools approach this differently. Aider integrates deeply with Git and supports automatic commits, diffs, and undo operations, while other agents provide checkpoints, branches, or ordinary Git workflows.

Never make AI generated changes difficult to inspect or reverse.

Review the diff, not just the final application

Verification should not replace code review. An application may work while the implementation is still poor.

Imagine the agent fixes a permission issue with:

authorization.ts
if (user.email.endsWith("@company.com")) {
  return true;
}

The requested scenario might now work perfectly. The tests may even pass. But the implementation may violate your authorization architecture.

That is why the human review layer still matters. When reviewing AI generated changes, look for:

  • 01 unnecessary scope
  • 02 duplicated logic
  • 03 security implications
  • 04 missing edge cases
  • 05 architectural violations
  • 06 changes to public APIs
  • 07 hidden performance costs
  • 08 migrations or destructive operations

The agent proves that the code works. The reviewer determines whether it belongs in the system. Those are related but different questions.

The complete AI coding loop

Putting everything together, a strong workflow might look like this:

  1. 1–4. Understand, inspect, define, planUnderstand the requirement, inspect relevant code and documentation, define success criteria, and plan the smallest coherent change.
  2. 5. ImplementMake the change. Keep it focused.
  3. 6–10. Verify mechanicallyRun formatter and linter, type checker or compiler, targeted tests, broader tests when appropriate, and the production build.
  4. 11–13. Observe and inspectRun and verify behaviour, inspect the diff, then fix failures or unintended changes.
  5. 14–15. Commit and reviewCommit once the change is coherent. Human review still decides whether it belongs.

Not every change needs all fifteen steps. Fixing a typo probably does not require an integration test suite. Changing authentication probably does. The depth of verification should match the risk of the change.

Verification should be part of the repository

There is another important implication. If every developer has to remember a different sequence of commands in each package, AI agents will struggle too.

Instead, provide stable commands:

package.json
pnpm lint
pnpm typecheck
pnpm test
pnpm test:integration
pnpm build

For more complex repositories, consider providing a single higher level command such as pnpm verify, which might execute lint, typecheck, unit tests, and build.

If pnpm verify succeeds, the repository has passed its standard engineering checks.

That is significantly more robust than explaining the process repeatedly in prompts.

Move from instructions to enforcement

Throughout this series, one idea has appeared repeatedly: do not ask the AI to remember something that your engineering system can enforce automatically.

01

Formatting

Instead of “remember not to break formatting,” use a formatter.

02

Types

Instead of “remember to use the correct types,” use a compiler.

03

Behaviour

Instead of “make sure the feature works,” write tests.

04

Merge gates

Instead of “don’t merge broken code,” use CI.

05

Contracts

Instead of “follow the API schema,” validate the contract.

This changes the architecture of your AI workflow.

division of labour
AI
 │
 ├── reasoning
 ├── exploration
 └── implementation

Engineering system
 │
 ├── compiler
 ├── linter
 ├── tests
 ├── build
 ├── browser / runtime verification
 └── CI

Treat generated code as a hypothesis

Treat AI generated code as a hypothesis. The agent is saying: based on what I currently understand, I believe this change satisfies the requirement.

Then your engineering system asks: can you prove it?

The type checker examines one part of that hypothesis. The tests examine another. The build examines another. The browser or runtime examines another. The human reviewer examines the architecture and intent. Only after those checks should confidence increase.

This is much healthier than treating AI output as either brilliant or hallucinated. The model proposes. The engineering system verifies.

The best AI workflow looks surprisingly familiar

There is an interesting conclusion to this entire series. As AI coding agents become more advanced, the best way to use them starts looking less like prompt engineering and more like good software engineering.

Give them clear requirements. Give them a well structured repository. Give them access to the relevant context. Keep changes focused. Use version control. Write tests. Automate checks. Observe real behaviour. Review the result.

Those principles existed long before modern coding agents. AI simply increases their importance because it dramatically increases the speed at which code can be produced.

If you can generate ten times more code, you need equally strong mechanisms for determining whether that code should exist.

The key principle

Reliable AI assisted development is not about eliminating mistakes. It is about building a system where mistakes are discovered before they become production problems.

The strongest workflow is therefore not:

  1. Prompt
  2. AI
  3. Code

It is:

ContextReasoningCodeVerificationEvidenceCorrection

The model does not need to be perfect. The system around it needs to make imperfection manageable. That is how AI coding agents become useful engineering tools rather than extremely fast sources of unverified code.

Building software that can be trusted

AI has made writing software faster. Businesses still need software that holds.

They benefit from software that is reliable, maintainable, secure, testable, scalable, understandable, and designed around the actual business problem.

Whether we are building a website, Shopify application, SaaS platform, internal business system, enterprise application, integration, or AI enabled product, we approach the problem the same way: understand the requirements, design the architecture, build deliberately, verify continuously, and make sure the software can evolve after launch.

AI can accelerate that process. It does not replace it. If you are planning a new software product, introducing AI into an existing system, or dealing with an application that has become difficult to maintain, get in touch to discuss how we can design and build the system properly—from architecture through production.

Get in touch
Published by Silver Vault Core Team

Engineering Studio at Silver Vault Project. We design, build, and operate resilient applications and distributed cloud systems that hold in production.