Skip to content

how I code now

Published:
18 min read

agents are not useful by default ⬇️

most days, I have 3-5 windows open in tmux, for any given project. neovim in one, docker/servers running in another (w 2-3 panes), and codex or opencode working in another.

sometimes there are more panes/windows. Logs, tests, a database shell, Git, or another agent trying to fix what the first agent did.

and it all lives inside ghostty + tmux, on arch + hyprland.

the agents write a decent amount of the code now. Probably more than I do on some days.

but that has not made understanding the codebase less important. infact, it has made it more important. cause producing code is becoming cheap. producing code that actually belongs in a system is still not.

why this setup

i did not build this setup for coding agents.

i ended up here because my laptop could barely tolerate Windows, VS Code, Chrome, Docker Desktop etc. my lappy is not a beast. It is decent, but Windows made sure I was aware of its limitations. i started debloating Windows. That helped a little.

Then I moved from chrome to Zen Browser (effing love Zen) but changing the browser was obviously not going to fix the whole thing.

vs code sucked too. so I started looking for a lighter editor. I tried Vim. Did not really love it at first. Then I ended up on Neovim, partly because of ThePrimeagen and all the other people online who made editing text look like a competitive sport.

At the beginning, I convinced myself that I was becoming very productive. but tbh, i spent more time learning how to navigate Neovim than getting anything done. I was installing plugins, changing keybindings, breaking the config, fixing the config, and then changing it again because someone on YouTube had a cooler way to switch buffers.

I told myself it was an investment. and annoyingly, it eventually was.

once the navigation became natural, nvim stopped feeling like a strange editor I was forcing myself to use. It became faster and more comfortable than anything I had used before.

with all the slop Microslop (dont sue me) was pushing in the name of “updates”, I had to replace Windows with holy linux.

i started with Linux Mint. my computer immediately had more room to breathe, and i could get lot more shit done with lot less strain on my system.

that got me into the habit of replacing the defaults in my daily setup. after enough “i use arch btw” memes and that PewDiePie video, i wanted to try Arch.

i loved it, never looking back. Not because I wanted to spend non-grass-touching amount of time ricing my desktop or measuring how many milliseconds it takes to open a terminal. I just liked having a system that was snappy, fairly small and built around the things I actually used.

i eventually ended up with Arch, Hyprland, Neovim, Ghostty, and tmux.

my dotfiles are here, for anyone who wants to inspect the damage.

the terminal became the workspace

Ghostty and tmux were probably the point where everything started fitting together.

before that, the terminal was one application among many. i would write code in an editor, switch to another window to run the app, another to inspect logs, another for Git, and then lose one of them behind twelve browser tabs.

with tmux, the terminal became the workspace itself.

I can keep Neovim open in one win, run the application in another, keep tests or logs below it, and open a new shell whenever I need one. The session stays alive, the layout stays where I left it, and I do not have to keep rebuilding the context in my head every time.

it really is not a revolutionary workflow. ppl have worked like this for decades. but once I saw the light, I was never going back.

The setup was already useful before I started seriously using coding agents. But it accidentally became a very good setup for them too.

The editor, shell, application, repository, tests, and agent harness all operate in roughly the same environment. I can watch what the agent is doing, inspect its changes in Neovim, run the code, stop it when it takes a bad turn, and take over directly without moving everything between different applications.

and i can work on multiple projects at the same time, while keeping all the sessions alive, each with their own environment, thanks to tmux-sessionizer and a simple script.

i did not intentionally create an “AI-native development environment.”

i just kept moving more of my work into the terminal, and the coding agents eventually moved in too.

copy-pasta code like cavemen

before agent harnesses era, my ai-assited coding workflow was mostly chat.

I would copy a function into claude, explain what I wanted, paste the answer back into the editor, and then discover that it had made three assumptions about the rest of the repository that were completely wrong.

So I would copy another file.

Then a type definition.

Then the database schema.

Then the error message.

Eventually, half the codebase would be sitting in a chat window with no reliable idea of how the pieces connected.

Then I would run out of my usage limits 😭😭.

It could still be useful. I used chat assistants for debugging, explaining concepts, generating small functions, and getting unstuck. But I had to manually carry all the context between the model and the repository.

Agent harnesses changed that interaction.

instead of me selecting the exact snippets that I thought were relevant, the agent could inspect the repository itself. It could search for existing patterns, read several files, edit them, run commands, observe errors, and try again.

not just that the models got better at writing code. The model could now operate inside a loop connected to the actual project.

i started using coding harnesses more seriously while working on an application for a flight school. It was not a tiny demo or a project generated from scratch. It was a real application with existing architecture, real operational flows, multiple parts of the system, and people who would actually be affected when something broke.

that is where I started understanding what these agents were genuinely good at, and where they could cause a lot of damage very quickly.

with an appropriate task, right instructions and enough context, they could move through a repository much faster than I could manually.

They could trace a feature across several files, find where data entered the system, follow how it was transformed, inspect how similar features had already been built, and produce a reasonable first implementation.

They were especially useful for work like:

“the agent can complete the task” and “the agent should be trusted with the task” are very different things.

agents are not useful by default

there is a lot of hype around coding agents, some of it rightfully so.

depending on who you listen to, they either already replaced software engineers or are useless autocomplete machines that generate broken code.

my experience is less dramatic. they can be extremely useful. but they can also produce an incredible amount of slop before you realize that the entire approach was wrong. The difference usually comes down to judgment.

my basic rule is that I do not delegate work I cannot evaluate. that does not mean I need to know exactly how I would implement every task before giving it to an agent. That would defeat a lot of the point.

i need enough of a mental model to recognize when the implementation does not make sense. If I am working with a framework, library, system, or domain that I do not understand at all, I usually do not start by telling an agent to build the entire thing.

i learn the basic model first. i read the documentation. i write some code myself. i might build a hobby project around it. i try to understand the lifecycle, the constraints, and the common ways it can fail. Because if I cannot tell right from wrong, it is of no use to me.

thing is agents are very good at producing output that looks plausible. code may compile, UI may render, tests it wrote may even pass.

but none of that guarantees that the code belongs in the system.

working code can still be wrong

agents tend to optimize for the task directly in front of them.

fix this error. add this endpoint.

create this component.

make this button work. center this div.

given a narrow enough problem, they can often produce a locally correct solution.

but software does not exist as a collection of isolated local solutions. a change can work perfectly in one file while making the wider codebase worse.

An agent may:

the code can be valid but the architecture can still be wrong.

i have to correct it just for it to gaslight me with “oh, you are right..” kinda response every damn time.

this is where I think a lot of the discussion around AI coding becomes too focused on whether the model can “code.”

yes, it can code. ofcourse it can.

The more important question is whether it understands the system well enough to make the change without slowly turning the repository into a pile of individually reasonable decisions.

usually, it does not understand that automatically. even in the small to medium sized codebases I work in, i see this issue. i can only guess how messy it gets with larger complex codebases.

that part still has to come from the developer.

the model knows software, not your software

most good models have seen a lot of code.

They know common framework patterns. They know how APIs are usually structured. They know how React components, database queries, authentication flows, and background jobs are commonly written.

that does not mean they know how your repository should work.

Every codebase develops its own internal logic. It has naming conventions, boundaries, patterns, tradeoffs, and historical reasons for why certain things are where they are. Some of those decisions are documented. A lot of them are not.

The model may generate perfectly acceptable generic code that still feels alien inside the repository.

Maybe the project avoids a library for reasons the model cannot see.

Maybe a strange-looking implementation exists because it solves a production issue that is not written down anywhere.

it lacks the “repository taste”.

It is not just code style or whether someone prefers semicolons. It is knowing what kinds of changes belong in the system.

model can learn some of this by inspecting the repository. good instructions can help. but it will still sometimes fall back to the way software is generally written instead of the way this software is written.

The model knows software. It does not automatically know your software.

the dangerous part is the trajectory

a bad final diff is easy to notice.

the more dangerous problem is when the agent starts with one wrong assumption and builds an entire chain of work on top of it.

it misunderstands, it assumes, it hallucinates.

Once that happens, the agent can move very quickly in the wrong direction.

it creates one helper to support the wrong idea. then another abstraction to make the helper fit. then it updates several callers. then tests fail, so it patches the tests. then types fail, so it broadens the types.

at the end, you have a large diff that appears internally consistent because the agent spent twenty minutes making the repository agree with its first bad assumption.

this is also how a small task burns an absurd number of tokens. so I try not to treat agent work as something that only gets reviewed at the end. I want to catch trajectory errors early.

for anything non-trivial, I usually start by asking the agent to inspect the relevant parts of the repository and explain what it thinks is happening.

not implement. just inspect.

that first explanation is useful because it exposes the agent’s mental model. if the model of the system is wrong, the code will probably be wrong too. i can correct the assumption before it edits fifteen files.

for larger tasks, I also prefer a bounded plan. Not a giant ceremonial plan with forty steps, but enough to make the direction visible.

Which files does it expect to change? Which existing patterns will it follow?

Sometimes the best thing you can do is stop the agent five minutes in and tell it that the whole approach is wrong. That feels wasteful, but letting it confidently finish the wrong solution is more wasteful.

context files help, but they are not judgment

i use project instructions, memory files, skills and plan docs to make coding agents more consistent.

the exact format depends on the harness, but the idea is similar. i try to give the agent information it would otherwise have to rediscover or guess:

these are useful because repeating the same instructions in every prompt is annoying, and the agent will forget or ignore things that only exist in my head.

But I also think context engineering gets treated as more magical than it is. A good AGENTS.md file cannot rescue a vague task. A memory file cannot fix an incorrect understanding of the architecture. A list of rules cannot encode every design decision that might come up during a long implementation.

And an agent can read a rule, acknowledge the rule, and then violate it ten minutes later because the immediate task pushed it toward an easier path.

Instructions reduce repeated mistakes. They do not create judgment. You still have to read the code.

choosing the task matters

one of the bigger skills with coding agents is deciding what to give them and how to shape that delegation. not every kind of task benefits equally.

agents are usually strongest when the work is bounded and the output can be verified.

mechanical migrations are a good example. If I need to update the same pattern across several files, an agent can do that much faster than I can, and the diff is relatively easy to inspect.

they are also useful for repository exploration. I can ask an agent to trace how authentication works, find every place a type is used, or explain the flow between the UI, service layer, and database. that can save a lot of time before I even ask it to change anything.

debugging can work well when there is observable feedback. The agent can run a command, read the error, inspect the relevant code, make a change, and run it again. Constrained refactors are another good use. If the target architecture is clear, the agent can handle a lot of the mechanical movement while I watch for places where behavior changes.

where I trust agents less is when the problem itself is still unclear. ambiguous product decisions. new architecture with poorly understood constraints. Subtle UX work. security-sensitive code in an area I do not understand. large rewrites justified mostly by “clean this up.”

literally anything where the hardest part is deciding what correct even means.

agents are very willing to turn ambiguity into implementation. that is not always helpful.

I review the diff, not the summary

agent summaries are useful. they are also written by the agent that made the changes.

so I do not treat “implemented the feature successfully” as verification that the feature was implemented successfully.

i read the diff. or i atleast look at git diff --stat for smaller changes if I’m too lazy to read whole thing.

i look for new abstractions that did not need to exist and check whether it followed the patterns already used in nearby files.

i look for duplicated logic and inspect any database, authentication, or permission changes carefully.

i check edge cases the agent may not have considered and look at what it deleted, not only what it added.

sometimes I ask another model or a fresh context to review the first agent’s work (its very effective sometimes to setup reviewer agents). A second agent is not automatically correct either, but it can catch assumptions that became invisible inside the original session.

The review loop is usually something like:

  1. Ask the agent to inspect and explain the current system.
  2. Correct its understanding before implementation.
  3. Agree on a bounded direction.
  4. Let it make the change.
  5. Review the actual diff.
  6. Run tests, type checks, linting, and the application.
  7. Check for duplication and architectural leaks.
  8. Clean up anything that technically works but made the system worse.

for small tasks, this can happen quickly.

for larger ones, I prefer incremental changes instead of letting the agent disappear into the repository and return with thirty modified files.

sometimes it is efficient to DIY

sometimes (actaully a lot of times), it comes to a point where steering the agent becomes more expensive than doing the work yourself.

agent keeps patching symptoms and repeatedly misunderstands the same constraint. The diff grows much larger than the task. it invents abstractions to compensate for its confusion.

you keep rewriting mem files in increasingly specific ways until you have basically described the implementation line by line. at that point, I take over.

the amount of times i painstakingly typed “i did this manually, now continue with the implementation”

i then write the important part myself, establish the pattern, and maybe hand the repetitive remainder back to the agent.

using these coding agents well does not mean forcing them into every task. sometimes you gotta know when to stop using them and do it yourself.

so what is my job now?

whenever someone asks me “hOw ARe yoU usiNg AI? whAt Do yoU thINk of AI? blah blah blah”, I describe myself as a part-time dev and full-time architect.

i still like the general idea, but I do not think that description is completely right. architecture, review, debugging, deciding constraints, and understanding the system are all still software development.

i still write code too. Especially when I am learning something new, when the implementation is sensitive, or when I want to establish the pattern the rest of the repository should follow.

difference is that I write a smaller percentage of the total code while being responsible for a larger amount of it. The agent does more of the typing.

I spend more time deciding what should be typed, where it belongs, how it fits with the rest of the system, and whether it should exist at all.

on a good task, the agent feels like a very fast developer who can move through the repository, follow instructions, and handle a lot of implementation work.

on a bad task, it feels like a very fast developer who misunderstood the architecture ten minutes ago and has been confidently building the wrong thing ever since.

either way, I am responsible for the result.

my current setup

The exact tools will probably change. This is just where I am right now.

there is nothing particularly sacred about this combination.

i use it because it is fast, everything is visible, and I can move between writing, running, reviewing, and delegating without the environment getting in the way.

writing code is no longer the bottleneck it used to be.

knowing what should be written, recognizing when it is wrong, and keeping the whole system from slowly sloppifying matters more.