I still like writing the code
What AI has changed about how I build software — and what it hasn't.
I’ve been messing around with computers for long enough that I can remember when getting a Windows game running on Linux felt like some sort of dark art.
I started playing with Ubuntu 4.10 not long after it appeared in 2004. Before that I’d already spent years around Mac OS X, so I wasn’t completely welded to Windows, but Linux was different. It was completely open in a way I hadn’t experienced before.
You could use GNOME. You could use KDE. You could replace half the operating system because you didn’t like the way something worked. A few years later we had Compiz Fusion, wobbly windows, desktop cubes and the frankly essential ability to draw fire across your screen. It was brilliant. It was also frequently an absolute pain in the arse.
I remember Wine being incredibly capable even back then, but nowhere near as friendly as it is now. You configured an awful lot yourself and then hoped whatever you were trying to run agreed with your interpretation of reality. Tools like Cedega and CrossOver made things considerably easier, although my main memory of Cedega is still mostly pain and things not working. Today we’ve got Proton, Bottles and an absurdly good Linux gaming ecosystem by comparison.
I’m not actually sure why I found fighting with all of that so appealing. I think it was partly because it was new, partly because I could change anything I wanted, and partly because there’s something deeply satisfying about being told something probably won’t work and then making it work anyway.
That theme has stuck around.
The rabbit hole properly started with World of Warcraft
Around 2005 I got completely obsessed with World of Warcraft. That eventually led me into the private-server community and projects around MaNGOS and what was then the Unified Database project.
Initially I wasn’t doing anything particularly clever. I was tinkering with the database, changing things, figuring out how everything fitted together and submitting the occasional database patch. But that escalated. I started dabbling with C++ and Lua, looking at scripting and learning how things like encounters and boss fights actually worked behind the scenes.
At the same time I started running more things on Linux servers rather than just playing with Linux on the desktop. That was probably the point where computers stopped being something I enjoyed using and became something I wanted to understand.
There is an important difference. Once you start asking why something works rather than just how to use it, you’re basically fucked. There’s no way back.
When programming became the job
At Fairfax School I originally worked much more on the systems and infrastructure side. Networks, servers, hardware, Windows, Linux, whatever needed doing.
Programming gradually became a bigger part of that role. I started building internal web systems and desktop applications, including things like DigitalSignage and ProxySwitch. I can remember one very specific moment where it felt like the shift had become real.
When the MacBook Air came out, work bought me one specifically so I could do development work wherever I happened to be. It sounds stupidly mundane now, but I remember thinking: oh shit, this is actually my job.
I wasn’t just the infrastructure guy who happened to write some software anymore. I was moving into development properly. And I loved it.
The code from that period is now old enough that looking at some of it causes physical discomfort, but people actually used those systems. ProxySwitch ended up being used by several local schools. That mattered far more than whether fourteen-years-later Jake approves of the abstractions.
Software became real when somebody depended on something I had written.
Good tooling should remove friction
A lot of my career since then has involved getting annoyed at unnecessarily painful ways of working. Sometimes sufficiently annoyed that I end up replacing them.
Just seeing the words Subversion or SVN still creates an immediate response in my brain:
Fuck this. We’re changing this.
I helped lead moves from FTP/SVN-based development to Git at both Tomorrow People and later Instiller. Mercurial was actually decent. I’m prepared to give it that. But Git won. And it won because it solves the problem incredibly well.
Even ten years ago I struggled to see much justification for a development team not using Git. Today there really isn’t one. That same thinking eventually led into CI, automation and infrastructure as code.
At Instiller I introduced Ansible into the infrastructure we run ourselves. We operate bare-metal systems, Rocky Linux servers and our own networks, and being able to describe that infrastructure rather than rely on somebody remembering which magic SSH commands they ran three years ago is obviously a massive improvement.
The pattern is pretty consistent. Find repetitive or fragile work. Automate it. Make the workflow better. Keep the interesting bit.
The interesting bit is still solving the problem
The part of programming I love most hasn’t really changed. It’s when you get one of those problems where you’ve spent hours on it, maybe the entire day, and nothing seems to make sense. You’ve read the same bit of code twenty times, tried three different approaches, convinced yourself you’ve found the problem twice and then discovered that, no, apparently computers just hate you personally. Then eventually something moves. You crack one little bit of it, you understand something you didn’t understand five minutes ago, and suddenly you can see a way forward.
I’ve got ADHD, which probably plays into this quite heavily, because once I get that first bit of progress I can completely tunnel into something. When you finally solve a problem that felt impossible six hours earlier, the dopamine hit is ridiculous. Fundamentally, I think that’s what I like about programming: being presented with something that currently feels unsolvable, understanding it, and eventually making it not be a problem anymore.
It’s probably part of why I got so addicted to Brazilian jiu-jitsu as well. BJJ is basically problem solving except the problem is another human being who is actively changing the problem while you’re trying to solve it. If I move one way they react, if I expose something they take it, and if I think I’ve solved one problem I’ve normally created another somewhere else. At least a compiler has the decency to keep the rules the same while you’re working. Usually.
That also gets quite close to the distinction I make between programming and engineering. Programming is the bit I probably enjoy the most: there’s something that needs doing and I get to sit there and actually write the code. Engineering is when you pull the camera back and look at the whole thing. How is this going to integrate? What problems might it cause somewhere else? What’s it going to look like in production, how will it scale, and what happens when somebody has to maintain it in two years? In reality most interesting development work involves both, and I enjoy both, but if you gave me the choice between spending an afternoon talking about software and spending an afternoon actually writing some, I’m probably opening Zed.
That distinction is probably why my relationship with AI is a bit more complicated than either “this is useless” or “I never need to write code again”.
AI has to earn trust
AI tools have interested me for a long time, but it took me quite a while to properly trust them. That’s not really because I think AI is uniquely dangerous or unreliable; I’m like this with pretty much every new tool I bring into the way I work. If I change editor, adopt a library, start using a different database, change a deployment workflow or introduce some new piece of infrastructure, I want to use it for a while and understand how it behaves. I want to know where it goes wrong, whether those failures are predictable, and whether I can actually trust it before I start depending on it.
AI hasn’t been any different. I use it quite heavily now: OpenCode is my main coding harness, I’ve built tooling around agent workflows, and I use models for investigation, planning, implementation, review and learning. At the same time, I still use Zed with the AI functionality turned off. That probably sounds contradictory until you realise that I actually like programming. Sometimes I know exactly what I want to write and I want to sit there and write it myself; I don’t need or want a model trying to finish every line for me.
When I do use an agent for development, I tend to work with it much more like I would another developer. There will normally be a conversation first about what we’re actually trying to do, what already exists, what the constraints are, whether my first idea is stupid, whether its first idea is stupid, and what we’re likely to break if we get it wrong. Then we might investigate the codebase, work through a design, break the implementation down and actually build it. I was working like that before I knew OpenSpec existed; OpenSpec just happened to put some structure around a workflow that already made sense to me.
What I’m much less comfortable with is giving an agent a paragraph of requirements, disappearing for an hour and then treating whatever comes back as finished software. It might work and it might even pass the tests, but I still want to know what the fuck it did. If I write something myself, I’ve been there for every decision along the way and I already have a fairly good model in my head of how it works. If an agent writes a large implementation without me, some of the development time has gone away, but the need to understand the result hasn’t. I still need to review it, test it, check the assumptions it made and make sure it hasn’t technically solved the ticket while quietly creating some absolutely cursed behaviour somewhere else.
That’s why I don’t really buy the constant claims that AI automatically makes developers two, three or ten times more productive. Sometimes it saves me a massive amount of time and lets me get through work that would otherwise have taken much longer. Other times I look at what it produced and realise I could probably have written the thing myself faster than I can now convince myself that its version is safe. Both of those things can be true. It’s still a game changer; I just don’t think the calculation is as simple as measuring how quickly the first version of the code appeared on screen.
Caduceus is probably where that thinking ended up
Caduceus is probably the project that best represents how I think about AI development at the moment. The basic idea is that I can label a GitHub issue and have an AI worker pick up the work, investigate the codebase, implement the change and produce a pull request. Phrased like that it sounds suspiciously close to the kind of thing I’ve just spent several paragraphs saying I don’t entirely trust, but the bit I find interesting is everything around the agent rather than simply how much code I can get it to write.
Caduceus itself is a Rust daemon and it owns the boring, deterministic parts of the workflow: polling GitHub, claiming work, creating isolated worktrees, tracking state, enforcing timeouts, dealing with Git and eventually producing the pull request. The model gets the messy reasoning problem: understand the code, understand the issue, work out what needs changing and implement it. That boundary is deliberate. I’m perfectly happy letting a model reason about a complicated software problem; I’m much less interested in asking one whether a lock exists or whether a process has timed out. Those things have actual answers, and normal software is extremely good at giving them to you.
That’s really the experiment I’m interested in with Caduceus: how much useful work can I delegate without giving up control of the system around it? Barkley Assistant does a lot of the implementation work on Caduceus itself as well, which makes the whole thing slightly recursive. I tend to treat it a bit like a very fast junior developer who has read an obscene amount of documentation. It can do a remarkable amount of work, but it can also occasionally do something that makes you stare at the screen and wonder how it arrived at that conclusion. So I still architect the system, work through the decisions, read what it produces and send it back when it’s wrong.
That setup is working very well for me at the moment, but I’m still pretty sceptical about how it holds up long term. There are bound to be things it gets wrong that I overlook in review, and some of those will probably turn into problems later. The workflows around it should make those mistakes easier to catch and contain, but they aren’t magic, and I’m not pretending this somehow removes the risk. That’s part of what makes it interesting to me in the first place: the setup looks promising now, but I want to see what survives contact with a lot more real use.
Open is still my default
I’ve always leaned heavily towards open source, so it probably isn’t surprising that I’ve ended up with a similar preference for open-weight models where they’re good enough for the job. That isn’t the same as thinking all software should be free. People need to get paid, companies need to make money and there’s absolutely nothing wrong with charging for something useful you’ve built.
For me it’s more about the amount of underlying technology that can be open without taking away the commercial value of the thing a company actually sells. We’ve seen plenty of examples of that over the years. Meta, despite me having very little interest in defending Meta as a company, has open sourced some incredible technology. Google has done the same, Microsoft has done it, and Red Hat built an enormous business around open-source software even if they haven’t always been quite the good guys people like to imagine. Everyone has commercial interests and that’s fine; I just think the wider technology ecosystem is healthier when useful building blocks can be inspected, run, modified and improved by the people who depend on them.
It’s one of the reasons Linux appealed to me in the first place, one of the reasons I like running and understanding our own infrastructure, and one of the reasons open models interest me now. I want there to be a choice. The open option doesn’t always have to be the best-performing or most polished option for that choice to be valuable. Maybe twenty years of Linux has just poisoned my brain, but I’m pretty comfortable with that.
The bit that worries me
The part of AI development I do worry about is that we’ve massively lowered the barrier to producing code without necessarily lowering the barrier to understanding software, and those are two very different things. You can already see some of the mess this creates in open source, where maintainers are dealing with a flood of AI-generated pull requests from people who often haven’t properly understood the project they’re contributing to. Outside open source we’re also seeing entire products built by people who don’t necessarily understand much of the generated code underneath them.
There’s a useful example of the underlying problem in this write-up about Express.js. Express had been dealing with a stream of junk pull requests, many of them pointless README changes. One theory was that a Git/GitHub tutorial had used the actual Express repository instead of a fork, so beginners were successfully following the steps without really understanding where their changes were going. Somebody even suggested blocking contributions from India, which was rightly rejected. India obviously isn’t the problem there. The problem is that the barrier to doing something with a real project’s workflow had dropped below the level of understanding needed to appreciate what you were actually doing.
That incident wasn’t about AI, and I think that’s what makes it useful here. AI lowers that barrier again, dramatically. You no longer need to understand Git particularly well, know the codebase, or even be capable of writing the change yourself before you can produce something that looks like a plausible contribution. That can be fantastic for learning, but it also gives people a lot of confidence to run things, submit things and build things they don’t yet understand. The cost of that confidence doesn’t disappear; quite often it lands on the maintainer who now has to review the result.
I don’t want to be cynical and say AI is making development worse, because I don’t think that’s the whole story, but in some respects it clearly can. If a vibe-coded application is handling real customer data, who actually understands the authentication, the tenancy boundaries, the permissions, the logging or the backups? What happens when somebody finds an SQL injection point six months later? The problem isn’t that AI wrote the code; the problem is when nobody involved understands the system well enough to know whether the code is correct, why it was written that way, or what to do when it fails. That has recipe-for-disaster written all over it to me.
At the same time, AI is probably one of the best learning tools I’ve ever had access to. I’ve used it a lot while learning Rust, not just to write things for me but to ask why something works the way it does, what the compiler is actually complaining about, whether there’s a better design, or whether I’m bringing assumptions across from another language that don’t make sense in Rust. I’ve done the same with PHP and TypeScript despite having worked with them for years. Being able to keep questioning something until it clicks is incredibly useful.
So I don’t think I can land neatly on either side of the usual AI argument. Used properly, it’s an incredible tool and it can make somebody who knows what they’re doing much more capable. It can also let somebody who doesn’t know what they’re doing produce an astonishing amount of software very quickly. Unfortunately, we as humans generally can’t have nice things without eventually finding the worst possible way to use them, so we’ll see where that goes.
I still like writing the code
The thing I still love most about this career is that I can have a week at work that goes in a completely unexpected direction and come out of it having learned something that changes how I think about programming, engineering or technology in general. I get to play around with new technologies, investigate projects, explore ideas, implement them and then actually see the benefit when something takes form and starts being useful. Even after doing this for years, that still happens often enough to keep it interesting.
The tools are almost unrecognisable compared with the ones I was using when I was changing World of Warcraft database records, trying to script boss fights and fighting with Cedega, and AI is obviously going to change them again. That’s fine. I’ve spent most of my career adopting tools that remove repetitive work and make the process better; I just don’t particularly want to automate away the bit that made me obsessed with this stuff in the first place.
I still like understanding how things work, I still like getting stuck on a problem and eventually cracking it, and I still like seeing something go from an idea to a thing that actually works. After all these years, I still really like writing the code.