Over the past few months I’ve been using AI tools to a much greater extent in my coding work.

In particular, I’ve increasingly been using them to generate the code that I would previously have written myself.

That does not mean I’ve moved over to “vibe coding”, at least not in the sense that I understand the term: repeatedly prompting an LLM until whatever it produces appears to work, running it, kicking the tyres a bit, and moving on without necessarily reading or fully understanding what has been produced behind the scenes.

I can certainly see why that approach is impressive, and in some situations it may even be useful, but I don’t really consider it software engineering and it isn’t how I want to build the projects I’m responsible for.

That probably follows the same theme as several of the articles I’ve written about AI in the past.

Ultimately, the person writing the prompt is still the person carrying the can.

There is no getting away from that.

If an LLM produces bad code, introduces a security problem, misunderstands a requirement, corrupts some data, or simply implements something in a way that doesn’t fit the rest of the system, I don’t think “the AI wrote it” is much of a defence.

I’m still the engineer.

I’m still the guy responsible.

I’m still the architect of the code.

The difference is that the LLM is increasingly doing the typing.

My Current Development Process#

My coding process now tends to look something like this:

  1. Define the features and interfaces I want the project to provide.
  2. Use an LLM to research possible technologies, libraries and approaches.
  3. Read about anything unfamiliar that looks promising.
  4. Create a Git repository.
  5. Create the skeleton of the project using the normal tooling, whether that’s dotnet new, npm, or whatever else is appropriate.
  6. Create or update an AGENTS.md file describing how I expect the coding agent to work.
  7. Break the project down into relatively small, manageable features.
  8. Prompt an LLM through a coding harness such as Codex, Antigravity or Claude Code to implement one of those features.
  9. Review the changeset it produces.
  10. If it isn’t right, reject or re-prompt it and review the changes again.
  11. Compile the project and run the relevant tests.
  12. Check the code in once I’m happy with it.
  13. Move on to the next small feature and repeat.

It’s a fairly simple loop, but for me the important part is that the loop still contains normal engineering discipline.

The LLM isn’t being given a vague description of an application and then allowed to disappear into a codebase for several hours making whatever changes it thinks are appropriate.

I’m deciding what gets built, how the work is divided up, and whether the result is acceptable.

Giving the Agent Some Rules#

One thing I’ve found increasingly useful is maintaining an AGENTS.md file in the repository.

That file acts as a set of working instructions for the coding agent.

It can contain requirements such as:

  • Coding conventions.
  • Commenting expectations.
  • How documentation should be maintained.
  • Whether the README should be updated alongside functionality.
  • How the project should be built.
  • What tests should be run.
  • What tools should be used.
  • What parts of the project should or should not be modified.
  • Any architectural rules that need to be followed.

Ironically, an LLM is quite useful for helping to produce the initial version of that file.

I can explain how I want the project to be developed, ask it to turn those ideas into a sensible set of instructions, and then review the result before allowing another agent to work against it.

The important word there is still review.

The file needs to make sense to me first.

Keep the Changes Small#

Probably the most important part of the whole process is keeping each changeset small enough that I can realistically review it.

AI coding agents can generate code much faster than I can read it.

That is both their biggest advantage and one of their biggest risks.

There is nothing particularly useful about an agent generating several thousand lines of apparently plausible code if reviewing it properly becomes such a large task that I’m tempted to simply accept it.

If I cannot reasonably review the changes an agent has made, then the task was too large.

At that point I’ll reject the changes if necessary and break the feature down further.

That might feel slower than simply allowing the agent to charge ahead, but I think it is considerably faster than trying to work out what happened later after a large pile of generated code has become part of the project.

It also keeps the Git history useful.

Each commit represents something relatively small and understandable, rather than an enormous “AI did some stuff” changeset.

An LLM Isn’t a Junior Engineer#

I’ve seen coding LLMs described as equivalent to junior engineers.

I don’t really like that comparison.

A junior engineer is a valuable resource that should be trained, supported and appreciated.

They are potentially a future senior engineer, technical lead or architect.

Part of the point of bringing junior developers into a team is that they learn the system, gain experience, develop judgement and eventually take on greater responsibility.

Replacing that development pipeline with tokens may make some short-term productivity numbers look attractive, but eventually somebody still needs to become the experienced engineer.

An LLM isn’t doing that.

I think of it much more as an extremely advanced code generator.

It is capable of producing surprisingly good code if it is given good instructions and enough context.

It is particularly useful for producing the sort of boilerplate code that software developers have written thousands of times before.

And that makes sense.

An LLM has been exposed to vastly more Python, .NET, C, C++, JavaScript and other source code than I will ever have time to read in my lifetime.

It is extremely good at recognising patterns and producing more of them.

That doesn’t make it an engineer.

It makes it an extremely useful engineering tool.

Research Is Probably Just as Valuable#

Code generation is only one part of where I find LLMs useful.

Research may actually be just as valuable.

I can ask questions about a particular technology, library or protocol, ask how something is normally implemented, ask what alternatives exist, or request small examples showing how an API is used.

That can dramatically reduce the amount of time required to get oriented around something unfamiliar.

I don’t have the capacity to read the entire Internet, remember every library I’ve encountered, retain the documentation for every platform I’ve used, or even know that some useful project exists in the first place.

An LLM can often point me in the right direction remarkably quickly.

Of course, it can also be wrong.

It may have been trained on outdated information.

It may have encountered poor information.

It may misunderstand what I’m asking.

And occasionally our silicon-based lifeforms/otherforms simply hallucinate something that doesn’t exist.

So I still do my own research.

The old adage applies quite nicely here:

Trust, but verify.

That applies equally well to generated code.

Building Software That AI Can Use#

The area I’m becoming increasingly interested in now is not simply using AI to build software, but engineering software so that an LLM can use it.

My inventory system is one example.

Traditionally, I might build a fairly substantial user interface containing lists, search boxes, filters, screens and various bits of navigation.

Some of that is still useful, particularly for administration and displaying structured information.

But for many everyday questions, a natural-language interface may actually make far more sense.

For example:

Where is the black filament I bought last week?

Or:

How many UCMOD boards do I have in stock?

Those questions are considerably easier to ask than they are to express through a traditional user interface.

Instead of opening an application, navigating through several screens, selecting filters and searching through tables, I could simply ask the question.

The interesting engineering problem then becomes how much capability should be exposed to the LLM.

I don’t particularly want it improvising queries directly against the database.

A better approach is probably to provide well-defined tools that the model can call.

Something along the lines of:

  • Find an inventory item.
  • Get the current stock level.
  • Find where an item is stored.
  • List the contents of a container.
  • Move an item between containers.
  • Retrieve recent purchases.

That leaves the application responsible for the actual business logic and data integrity while allowing the LLM to act as the natural-language interface.

Technologies such as MCP make that increasingly practical.

It also follows the same principle as the rest of my AI-assisted development process.

The AI is useful at the edges.

The engineering still sits underneath it.

Where I Think This Is Heading#

I don’t think I’m going back to writing every line of application code manually.

There doesn’t seem to be much point.

If an LLM can generate a predictable block of code in seconds, and I can understand it, review it and verify that it behaves correctly, then having it produce that code seems perfectly sensible.

But I also don’t intend to hand over complete responsibility for software development to an agent and hope for the best.

For me, the useful middle ground is becoming fairly clear.

I define the system.

I choose the architecture.

I break the work down.

The LLM produces much of the implementation.

I review what it produces.

I decide whether it gets committed.

Then we move on to the next piece.

That feels considerably less revolutionary than some of the claims being made about AI coding at the minute.

It is, however, genuinely useful.

And increasingly I’m beginning to think that the bigger change may not be AI writing our applications at all.

It may be applications increasingly being designed so that AI can use them.

Assuming, of course, it doesn’t burn too many tokens.