What I learned in the past 6 months about using agentic AI

By

Six months of building with coding agents every day. Where the bottleneck moved, what I stopped doing, and the habits that survived contact with real projects.

~~~

In the past six months I built more software than in any other period of my life.

A new product went from zero to production in six days. This site got over 200 free browser tools and more than 80 free courses. I launched a dozen small products, put my software on GitHub, migrated a server, and wrote an MCP server that spends real money.

Almost all of it was built with coding agents. Cursor most days, Claude Code and Codex when they fit better.

I also wrote about most of it as I went, in deep dives and tutorials and a couple of essays. Here I want to put those posts side by side and pull out what held up.

The bottleneck moved

For most of my career, implementation was the slow part. An idea took weeks before I had something real enough to judge.

Now I can have a working version of most ideas in a day or two.

So the slow part is now the deciding. Is this worth building? What’s the smallest version? What must stay true? Should this exist at all?

Those questions were always there. Expensive implementation let me avoid them. Now they’re the whole job, and I can’t hide behind “that would take six months”.

This has a dangerous side. Building is a very convincing form of procrastination. I can produce a lot of software in a week without creating much value. I wrote about this in when more people build software: a working first version used to prove something. Now it’s cheap evidence. The real evidence comes later, when people return and pay and trust the thing.

I try to remember that before I open a new project folder.

I stopped trusting big frameworks

Early on I tried several agent skill collections. I borrowed ideas from all of them.

But they kept growing. Thirty skills, personas, pipelines, phases with names. I couldn’t hold them in my head, so I stopped using them. And the complexity showed up in the output. A process with that much ceremony produced code with layers and options nobody asked for.

So I built fstack. Thirteen skills, plain names, one job each. Every skill fits in about 150 lines. If it doesn’t, it’s doing too much.

The one I care most about is /fstack-simplify. It only removes things. Dead files, abstractions with one caller, configuration nobody requested, fallbacks for situations that never happen. It proposes deletions and tells me what I’d lose. Usually the answer is nothing.

Agents generate code faster than I can judge it, so I want something in the loop whose only job is taking code away.

The most useful part of a plan is what we’re not doing

Every plan I write with an agent has the same four sections: what we’re doing, the steps, what we’re not doing, and how we’ll know it works.

The third section exists because agents are good at expanding an idea. They see the possible abstraction, the settings page, the fallback, the dashboard, the future feature. Many of them sound reasonable. Most are not needed for the result I asked for.

Writing the exclusion list down gives the work a physical boundary. Anything outside it doesn’t enter the task. And when I review the diff, I compare it with the plan and look for both missing work and extra work that was never approved.

The repository remembers, the chat doesn’t

A new agent session starts with no memory of yesterday.

So it guesses the package manager, runs the wrong test command, edits a generated file, or misses the deployment constraint I explained last week. And I explain it again.

Every correction I find myself repeating goes into AGENTS.md, so the next session reads it before it starts guessing.

The valuable lines are rarely the build commands. They’re the strange facts. A config name that must match a hosted project or the build fails. The folder that looks generated but isn’t. Or the redirect rule that only works if it stays at the end of the file. Those are cheap to write down and expensive for an agent to rediscover by breaking something.

Maintaining that file has its own traps. Commands rot: someone renames a script, nobody updates the line, and a file with broken commands teaches the agent to ignore the whole file. Preferences also tend to get written like hard rules, and if every line reads as equally severe the agent can’t tell which ones matter.

The same idea covers the work itself. I keep a plain text work log that a Git post-commit hook fills in for me, grouped by date and project. I wrote about how that log works. When I want to know what I built, I open one file instead of five repositories.

More agents did not mean more software

Launching five agents is easy. Without structure, though, five agents give you five interpretations of the same task. One changes the data model, another invents a design system, two edit the same shared file, and the last one finishes early and says everything looks good.

You get more output and a lot more uncertainty.

What worked for me is a manager and workers. One strong reasoning model reads the whole codebase, owns the plan, and decides how to split the work. Workers get bounded tasks with clear inputs, clear file ownership, and a required report at the end. They may not redefine the plan or touch shared contracts. I wrote up the full setup in how I turned fstack into a software factory.

The rule that saved me the most time:

Parallelize independent files. Sequence shared contracts.

Two workers editing the same router, schema, or config file are not running in parallel, they’re a race condition waiting to happen.

Even with all that structure, I mostly single-task. One agent, one problem, my attention on it. Running many agents in parallel looks impressive and feels terrible. It’s air traffic control, not programming. I do it for mechanical work, batches of similar files, and that’s it.

I read the files, not the summary

An agent’s report of what it did is not proof that it did it.

So I check the artifacts myself: I open the file, read the diff, run the build. “It should work” tells me nothing, and neither does a passing unit test when the question is whether the page renders. A worker saying it created a file doesn’t mean the file exists.

Every piece of delegated work gets one of three verdicts. Accept, when it matches the brief. Patch, when there’s a trivial problem I fix on the spot, like a missing import. Re-brief, when the approach is wrong.

Patch versus re-brief took me a while to get right. If I quietly repair a big failed delegation, nothing improves. The brief stays bad, the boundary stays unclear, and the same failure comes back next time. A corrected brief explains what went wrong and sends the task through again.

When I want a change reviewed, I do it in a fresh session when I can. The session that wrote the code already has a story about why the code works. A new one only sees the diff and the repository.

I ask before I let it write

Most people open the agent and ask it to write something. More and more, I do the opposite first.

I point it at code that already exists and ask questions until I understand it. What happens when this webhook arrives twice? Where does this email go after we store it? If this returns 200, did the user actually get access?

“Explain this file” gives me a summary, and a summary is the file rewritten in English. One sharp question at a time gives me a path through the system.

I also make the output separate facts from guesses:

Answer in three parts:

1. Confirmed behavior, with file and function references
2. Assumptions you could not verify
3. The smallest commands or tests that would resolve those assumptions

The answer can still be wrong. But now I can see which parts the model checked and which parts it assumed, and every claim comes with a location I can open.

I wrote the full method in use AI to understand code. What I keep from those sessions is the picture of how the system works, and the code changes come out better because I had it.

A prompt is advice. A boundary is code.

You cannot protect anything important with “always ask me first” in a system prompt. The model may follow it or it may not. If an action costs money, deletes data, or emails thousands of people, the rule has to be code the model cannot get around.

When I built StackPlan, I made one rule early: the LLM never picks a price. Pricing lives in a database with effective dates, and a deterministic cost model in TypeScript does the math in integer cents. The model only writes the explanation, and the prompt tells it not to invent or alter any number. If it hallucinates a price anyway, it doesn’t matter, because the UI renders costs from the engine’s JSON.

Then I let an agent buy a domain through the Cloudflare Registrar API, $12.20 with no refunds. Calling the API was a few lines. The real work was making sure the agent couldn’t buy the wrong domain, approve its own purchase, reuse a stale price, or retry a request that might have already gone through. The server owns the policy: ASCII names only, a $20 cap, quotes that expire in five minutes and are consumed before anything awaits. A person approves one exact domain at one exact price, in a form the MCP client draws, not the model.

The same thinking applies to everyday work. I put a few Git hooks at the point where agent work becomes repository history. Fast checks before a commit, the full suite before a push, and CI as the final gate that no local bypass can skip. The instructions explain how I want the work done, and the hooks check the handful of things that must hold before a commit lands.

Agents produce too much

The first result an agent produces is often correct and still full of habits, in code and in text alike. In writing, a bloated introduction, a forced group of three, a summary that repeats the opening. In code, a protocol with one implementation, a type that duplicates another, a comment that restates the line below it, an error caught and turned into an empty result.

I stopped fighting this inside the main task. An agent that’s writing a feature already has enough to track. “Don’t sound like AI” at the end of the prompt is one more instruction competing for attention.

Instead I run a separate pass. The anti-slop skill gets one narrow job: inspect what was produced, remove the generic habits, keep the meaning. Then I get a clean diff of what it changed, and I decide what stays. When it misses the same pattern twice, I add a rule. When a rule keeps producing awkward results, I remove it.

The chattiness in the terminal is a different problem, and every harness has a switch for it. Claude Code has a Concise output style, Codex has a personality setting and a flag to hide reasoning summaries, Cursor takes a four-line user rule. I collected all of them in how to make AI harnesses talk less. The one rule that works everywhere: ask for a shape, not a length. “Be brief” is an adjective and the model drifts back. “List the file paths you changed” is something it can check itself against.

The server is just another place to run an agent

For most of these six months the agent ran on my Mac. Then I started pointing it at servers.

The easy version is running Claude Code directly on a disposable VM. I did that on Sprites and built a small web app that lives on the server, with a public URL anyone can visit. Prompt, build, ship, all from the same box. Nothing to worry about, because the box gets destroyed at the end.

The version that changed my mind was less disposable. Earlier this year I resized the DigitalOcean droplet that runs my newsletter software the wrong way and locked it into a $48 a month plan. You can never shrink a disk. The only way out was a new, smaller server and a full migration: Apache, PHP, MySQL, certificates, cron jobs.

Codex did it over SSH, with access to both machines. It read the old setup, reproduced it on the new one, added the swap file that fixed the memory problem that had started the whole mess, imported the database, and tested the site on the new box by forcing DNS resolution before I changed anything public. It also disabled the cron job on the new server so two machines wouldn’t send the same emails. I hadn’t thought of that yet.

Half an hour of agent time for what would have been a weekend I didn’t want to spend.

It wasn’t perfect. My browser had the old IP cached, the old Apache had just been stopped, and I got a connection refused error right after the cutover. The agent diagnosed that too and turned the old server into a temporary proxy to the new one. And the migration left a couple of subtle problems that only showed up when I sent the next newsletter. So the agent did most of the work on that server, and I was still the one who had to watch what happened after the summary said done.

I switch tools without guilt

For a while I treated choosing a tool as a commitment. Now I pick whatever gets me moving on this project, today.

When I’ll maintain a project for months, I want to live in the codebase, so Cursor is the default. Repo-wide mechanical work, or anything that isn’t visual, goes to a CLI agent in the terminal. And on a Saturday when I know exactly how to build something but don’t want to wire forms and migrations, a vibe coding tool that starts me at 70% is fine.

I wrote a longer version in IDE, CLI, or vibe coding tool. Optimize for momentum first and control second, and expect to switch when the constraints change.

Models are the same story. The names in my AGENTS.md will keep changing, the roles won’t: one strong model plans and reviews, faster ones execute bounded tasks.

Where the joy went

Something changed in how building software feels. The easy, satisfying work, the CRUD endpoint, the settings page, the form, is exactly what agents got good at first. That loop of sit down, type, an hour later something works, was what a lot of us called the joy of programming, and agents eat exactly that.

What’s left is deciding, reviewing, catching subtle problems, saying no, all day.

For me the joy didn’t disappear. It moved. From writing a function to an idea becoming a real thing people can use in the same week I had it. From clean code to clean products. Taste still matters, maybe more, it’s just applied one level up. I wrote about this more carefully in AI and the joy of programming.

What keeps it from turning into burnout, for me, is the single-tasking I mentioned above, plus building useless things for fun, some with agents and some by hand, because nobody said a hobby has to be optimized.

What I still don’t know

I don’t know if writing code by hand is worth doing anymore. Some days it feels like developing your own film after digital cameras. Other days I write something small myself and remember why I started.

I don’t know if the parallel-agents workflow is something I’ll grow into or something I’ll keep avoiding.

I don’t know what software is worth when everyone can produce a first version in a week. I suspect the value was never in the hours, but I’m watching prices and margins like everyone else.

And I don’t know if the shift from writing to directing is joyful for most people, or just for some of us. Clearly, right now, it isn’t for a lot of developers.

This whole thing is a few years old. Ask me again in six months.

Tagged: AI · All topics

Want me to talk about your product? You can sponsor this site.

~~~

Related posts about ai: