How I'm Using AI in Software Development

Published on

Software Developer

In my previous post about AI and software development, some people understood my position as if I was denying the real improvements that AI brings to the table. That was not really the point.

I use AI in most of my personal projects today, and I use it because… well, it works. It improves speed, helps me explore codebases, makes boring implementation faster, and lowers the cost of turning ideas into something that actually runs.

But I still don’t think the whole thing became magic.

AI changed a lot of my day-to-day development work, but it did not remove the need for software engineering.

Where I keep control

The main thing I try not to outsource is the shape of the software.

I still hold the decisions around project organization, boundaries, dependencies, and the areas that my experience tells me may become trouble in the long run. AI can help me implement something inside a boundary, but I am usually the one deciding where that boundary should be. And this is because, even though the tool is good, it is too comfortable with “moving forward”.

If you let a session run for too many iterations without checking it, things will drift. At a glance, the code may still look good. The explanation may still sound confident. But the direction slowly moves away from what you want from the project.

So I must keep correcting the course.

That’s why I am also not a huge fan of fully automated setups with multiple agents changing multiple worktrees at the same time. I understand the appeal, and I accept that there might be a useful scenario there somewhere, but I don’t find it practical for the way I work. At some point it becomes hard to track what is going on, and one thing starts interfering with another.

Maybe it is just a “skill issue” on my side. Or maybe the app is just a throwaway one that does not even need short-term maintenance thought (no judgment here).

But, for now, I prefer a smaller loop that I can follow.

The project files

For a while I used a memory folder with Markdown files to hold decisions, and a specs folder to hold more detailed plans.

It kind of works.

The problem is that it also gets messy when goals drift or the project changes direction. You end up with a lot of historical context, but not always with a clear source of truth. Sure, you can use git log to check for older thoughts, but that is also suboptimal.

Lately, I have been trying to keep three simple files in the projects where AI assistance matters:

  • AGENTS.md, with the hints, conventions, and my way of doing things in that project
  • ARCHITECTURE.md, with the main ideas that hold the project together
  • PROJECT.md, with what the project is and why it exists

The README usually stays small. It works more like glue between these files, with the basic information needed to run the project.

If the project is big enough to need real documentation, then I keep that documentation in the repository too, usually with some static site generator. That is what I do in projects like DNSao.

I don’t want a documentation framework for the AI. I want a small number of files that can be read quickly and that describe the project well enough to prevent the most obvious mistakes.

My current workflow

The bigger change in my workflow was moving the discussion out of the AI session and into an actual issue tracker.

I use Gitea and OpenCode. At first I was using OpenCode too directly. I would open a session, discuss the task, go back and forth, implement things, and then later realize that some of the reasoning was basically trapped inside that session. And then I’d lose the session ID, try to find it, fail, and get mad at myself for failing at bookkeeping.

That is not practical for longer-running projects, so I migrated to Gitea issues to track the work and MCP integration so the agent can follow and document issues. This was useful, but I found that when things are “on track” I basically prompt in OpenCode, wait, then check output, prompt again, and so on and so forth, which was also not a good workflow.

So, I built a small daemon that checks issues in my Gitea repositories. When I mark an issue with an ai label, the daemon sends a request to a self-hosted OpenCode server, and that server runs a triage agent. The triage agent reads the issue, checks the repository code, and comments back. Sometimes it asks questions. Sometimes it writes a plan. Sometimes it points out that the request is unclear or that there is an easier path, and I respond in the issue.

This can go back and forth for a while, until the design is clear enough and the plan looks reasonable. After that, I comment approved, and the daemon sends another request to the OpenCode server, this time using a build agent. The build agent reads the whole discussion, follows the approved plan, implements the solution, and opens a pull request for me to review.

Then I review it like I would review any other pull request.

Right now, there is a limitation that if I want to request changes, I still need to comment back on the issue, not on the pull request. This is only because I am too lazy to update the daemon to also read pull request comments. Meh, it works now, and the agent can get all context from the issue, so it is “easier” this way.

Where it works well

I would say that around 90% of my requests go somewhat smoothly after the issue discussion. The triage step forces the task to become clearer, and the implementation step tends to be fairly good.

This is where AI is very strong for me. It can read more code than I would want to read for a small task and produce a pull request that is close enough that reviewing it is cheaper than writing it myself.

That is a real improvement from my previous experiences. And this is no longer true only for greenfield projects. It helps with maintenance too, as long as the problem is well-bounded and the codebase gives it enough signals.

Where it still breaks down

The remaining 10% is where the optimism gets expensive.

When there is a deeper problem in a bigger codebase, the model can drift both in analysis and in the creation of a “fix”. Even good models do this. They follow a plausible path, then another plausible path, and suddenly you are far from the actual root cause. In those cases, I usually need to give it deeper guidance, or debug and fix the issue myself in a smaller and more controlled way.

There is also a bad dynamic when I try to outsource the thinking during an emergency, or when something is broken in production. In that context, waiting for the AI response can become a trap. You ask, wait, read the confident answer, and because you want the problem to be solved, you may accept the plan more easily than you should.

And this is especially frustrating when the answer is confident and you know it is wrong.

Lately, when this happens, I am trying to send a good prompt, but I keep investigating myself. The AI becomes one more debugging tool.

The current equilibrium

So my conclusion is similar to the previous one, but a little updated.

AI solves a lot of problems. It is really helpful, and in many cases it is probably the main artifact in my toolbox now. It helps with prototypes, maintenance, code search, refactoring, boring implementation, and a lot of the glue work that exists around software development.

I also admit that it solves more problems now than it did before.

But the “you need to study AI or you will be left behind” chant still does not make sense to me. If you already know how to code, using these tools is not really hard.

That is why I don’t think software engineering is dead. I still think someone needs to actually understand the system.

Comments

I feel that comments on specific blogs have been dying down as the times goes. If you have any questions or want to talk about the post, contact me through the below links.