July 13, 2026
Why We Should Learn to Love the Robot
Why AI-assisted software is still authored by the person making the architectural decisions and checking what must be true.
It was 2022—maybe 2023—when I first encountered a large language model.
It was not my first language model. In college in the 1990s, I played with n-gram Markov models trained on Usenet posts. I would feed them text and ask them to respond. Occasionally, they generated something resembling English.
But this was the first time I encountered a model that could produce a coherent answer.
My first question was pure vanity:
Who is Adam DePrince?
It informed me that I was a 17-year-old male dancer. It had mixed together enough details from unrelated news articles that I could tell material about my late sister Michaela had probably appeared in its training data. Apparently, having seen the name “Adam,” it decided its imaginary dancer should be male.
My second question was:
Implement a function to generate the Fibonacci sequence in Python.
It gave me the classic naïve, non-memoized, exponentially slow recursive solution.
At that moment, I realized it was only a matter of time before the world changed.
Every Abstraction Feels Like Cheating
Four years later, I use AI heavily in my daily work. I write open-source software prolifically—perhaps obsessively—both for problems I find interesting and for problems I hope other people need solved.
I use AI, but I continue to say, “I wrote this.”
Here is why.
I started down the path toward computer science and mathematics when I was 11. Ideas resembling calculus—the area under a curve, the slope of a line—were already dancing around in my head. I understood trigonometry and desperately wanted to know how computers calculated transcendental functions.
That was when I received my first programmable computer: a TI-99/4A.
It had built-in BASIC and cassette tapes. For anyone unfamiliar with the machine, it was an atrocious programming environment: a low-resolution display, a BASIC interpreter running through another interpreted machine language, a processor with 256 bytes of fast scratchpad memory, and 16 KiB of video memory sitting behind far too many wait states on far too narrow a bus.
I loved it.
I became obsessed with numerical algorithms. Once, I left the computer running for ten hours to approximate pi using the Gregory–Leibniz series. In those ten hours, it completed roughly 110,000 iterations and produced a result accurate to perhaps five or six digits.
Later, I acquired a 32 KiB expansion chassis with a disk drive. Suddenly I had random-access files instead of cassette tapes, Extended BASIC, an assembler, and 32 KiB of bus-addressable RAM instead of relying almost entirely on the tiny scratchpad.
The nature of the machine changed, and my obsessions began to resemble the ones I still have today.
I spent years typing stock prices from the local newspaper so I could backtest trading strategies in BASIC. I rewrote numerical processing routines in assembly.
There was always a choice:
Easy to write, or fast to run.
Years later, in high school, my parents bought a PC. It had a proper BASIC interpreter, a vastly larger usable address space, and real performance compared with my TI-99/4A.
But the same division remained. Slow was easy. Fast was hard.
Then my parents gave me Turbo Pascal 4.0.
My world changed again.
I suddenly had something approaching assembly-language performance with something approaching BASIC’s ease of use—and roughly half a megabyte of heap when I ran the compiled program outside the IDE.
I felt giddy. I felt productive. I also felt as though I were cheating.
Was I still a real programmer if I was not writing assembly?
I reassured myself that I could write better assembly than Turbo Pascal generated.
For a while, that was even true.
The productivity gains were immense. I built toolkits that I later used professionally as a consultant, including a complete windowing system that was easy to reuse.
I remember friends coming to my house for a class-related event. We needed to generate custom printed labels for some reason. I no longer remember why, but I remember my response:
Give me fifteen minutes.
I opened Turbo Pascal, imported my graphics module, and wrote a program that took a block of text and printed the labels.
That was impressive in 1991.
Every major advance in abstraction has felt like that.
Computers improved. Languages improved. Compilers improved. By the 2000s, compilers were routinely generating better assembly than I could write casually. Today, modern vectorizing compilers often recognize opportunities to use SIMD instructions that I miss—although there is still plenty of room for humans in the loop.
With every step upward in abstraction, I became more productive.
Python replaced BASIC.
C++ replaced assembly.
Nanobind replaced PEEK, POKE, and CALL as the way I cross language boundaries.
Each transition discarded a little craftsmanship while giving me considerably more expressive power.
My Fingers Are a 50-Baud Channel
Despite all these improvements, I still had a long list of things I wanted to build and no time to build them.
It was not for lack of ideas.
It was not for lack of ability.
The problem was that my fingers are fundamentally a lossy, 50-baud communications channel through which I must transmit an enormous amount of boilerplate.
Goblin Store began as an idea I had more than a decade ago.
Memcached stores objects in RAM. But when it stores a large object, most of that object is not needed immediately. A network connection is a stream of bytes divided into packet-sized chunks. If I am sending one megabyte over a 10 Gbit/s connection, the last byte cannot leave the machine until roughly 800 microseconds after the first.
An SSD might respond in somewhere between 10 and 100 microseconds.
If you store large objects entirely in RAM merely because the first byte must be available quickly, you are wasting memory.
Going in the other direction is equally problematic.
If you serve a large file from a spinning hard drive, you might wait two to four milliseconds for its first byte. On a 10 Gbit/s connection, that seek time represents roughly 2.5 to 5 MiB of data the network could have transmitted while it sat idle.
The Linux kernel tracks access patterns and tries to retain hot files in RAM while allowing colder files to fall out of cache.
But for large-object serving, that is the wrong unit of caching.
You do not need the entirety of every hot file in RAM.
You need the heads of the hot files in RAM.
Byte ten million has plenty of time to arrive from storage before the network is ready to transmit it. Keeping that byte in memory provides little benefit. Those cached tails have a real cost: either colder objects suffer additional seeks, or you pay a cloud provider for RAM you do not actually need.
That is how Goblin Store was born.
It combines locked memory for object heads with asynchronous O_DIRECT storage I/O for the rest. When a request arrives, it begins streaming the tail from storage so the data is ready by the time the network reaches it.
The idea is simple:
- RAM for the head.
- SSD for the middle.
- Hard drives for the very long tail.
It was something I had wanted to test for ten years, but something I had never had enough time to implement.
Then I had AI doing the typing.
I described the architecture. I explained how the components should communicate, which APIs to use, how buffering should work, and where the boundaries belonged. The AI filled in the plumbing.
I benchmarked Goblin Store against Memcached. I gave Memcached a block of RAM, then served the same objects through Goblin Store while replacing most of that RAM with SSD and hard-drive capacity.
Goblin Store went mano a mano with it while running ~6–7× cheaper.
“Extstore,” you might say—Memcached’s own SSD-backed object store.
The head-in-memory design beat that too.
Typing the whole system myself through a 50-baud interface made the experiment economically impossible. Once AI handled the mechanical work, the economics changed.
The Author Is the Person Making the Decisions
This does not mean I tell a machine, “Build me a cache,” and then walk away.
I am still the author.
I read the diffs before they are committed. I keep track of the architecture, the data structures, and the invariants. I argue with the computer. I reject implementations. I redirect it when it wanders into an attractive but irrelevant solution.
The taste, discipline, and design decisions come from 35 years of performance-engineering experience.
That experience shows up in the benchmarks.
AI makes dumb decisions. It goes on strange tangents. Sometimes it lacks context. Sometimes it takes a cognitive shortcut and matches the problem to something superficially similar.
You still have to supervise it.
You have to understand what it is saying.
You have to recognize when it is wrong.
Maybe that changes someday. It has not changed yet.
Here is a real example.
Fast, Elegant, and Wrong
In another project, I was working on an AVX-512 implementation of Smith–Waterman. I am not aware of another particularly good AVX-512 implementation beyond my work on stride-align.
I asked an AI to build some scaffolding and a first-order implementation. It did.
At one point, I needed to reduce a __m512i containing 64 signed 8-bit lanes to its minimum value. The AI happily emitted a scalar loop.
Initially, I wondered whether it knew something about the compiler’s auto-vectorizer that I did not. Perhaps the compiler would recognize a loop over the vector type and turn repeated calls to std::min into the proper reduction.
It did not.
I started writing a divide-and-conquer reduction.
Compare one set of 32 bytes with the other set of 32. That leaves 32 candidates.
Then reduce:
32 → 16 → 8 → 4 → 2 → 1.
The algorithm is straightforward. Intel SIMD is not. Decades of extensions have left us with an assortment of register widths, instruction families, extraction operations, and mnemonics that vary depending on where the data begins and where it needs to end.
I was about to open Intel’s software developer manual and look up the exact intrinsics when I remembered:
I have an AI.
Remembering function names is exactly what these systems are good at.
So I asked:
Write the C++ intrinsics needed to find the minimum signed byte in a
__m512i.
It generated a compact tree reduction.
It also made a subtle mistake.
Its final steps used _mm_srli_si128, which shifts bytes while filling the newly opened lanes with zero. For a generic signed-byte minimum, that can manufacture a value that never existed in the input. If every actual lane is positive, the inserted zero becomes the false minimum.
The code was fast. It was elegant. It was wrong.
That is precisely why the human remains responsible.
Intel also provides the convenience intrinsic _mm512_reduce_min_epi8, but it still lowers to a sequence of instructions. Because this reduction sat on a hot path, I wanted to inspect and control that sequence myself.
The corrected version rotates the bytes instead of introducing zeros:
#include <immintrin.h>
#include <cstdint>
static inline int8_t reduce_min_epi8(__m512i v)
{
// 64 lanes -> 32 lanes
const __m256i lo256 = _mm512_castsi512_si256(v);
const __m256i hi256 = _mm512_extracti64x4_epi64(v, 1);
__m256i m256 = _mm256_min_epi8(lo256, hi256);
// 32 lanes -> 16 lanes
const __m128i lo128 = _mm256_castsi256_si128(m256);
const __m128i hi128 = _mm256_extracti128_si256(m256, 1);
__m128i m128 = _mm_min_epi8(lo128, hi128);
// Rotate rather than zero-fill. Each step doubles the
// number of original lanes represented by every result lane.
m128 = _mm_min_epi8(
m128, _mm_alignr_epi8(m128, m128, 8));
m128 = _mm_min_epi8(
m128, _mm_alignr_epi8(m128, m128, 4));
m128 = _mm_min_epi8(
m128, _mm_alignr_epi8(m128, m128, 2));
m128 = _mm_min_epi8(
m128, _mm_alignr_epi8(m128, m128, 1));
return static_cast<int8_t>(_mm_cvtsi128_si32(m128));
}
The AI saved me the mechanical work of remembering and looking up the extraction and comparison intrinsics. My review supplied the part that mattered more: determining whether the implementation was actually correct.
That is the relationship.
The machine types quickly.
The human knows what must be true.
A Fleet of Brilliant Junior Programmers
Working with AI feels like leading a fleet of caffeinated, PhD-level junior programmers.
They are fast. They have read almost everything. They can produce an astonishing amount of work.
They also do not fully understand the goal.
Part of the problem is that I, like most humans, fail to state things that seem obvious to me. I omit environmental constraints, preferences, architectural history, and business goals. A human coworker might absorb those things over months of meetings and water-cooler conversations.
An AI begins with whatever I managed to put into the prompt.
These systems will get better. But this is where we are today.
Knowledge Matters More, Not Less
AI changed my value proposition as a programmer.
In a world where a machine can recall SIMD intrinsic names instantly, memorizing those names has less value. The mechanics of typing matter less. Remembering the precise spelling of every function matters less.
Detailed technical knowledge matters more.
For example, I created DragonArray to build artifacts for the Loongson ecosystem.
When I began the project, NumPy supported Loongson’s 128-bit LSX vector extensions but not its 256-bit LASX extensions. A future Google Highway port may eventually change that. But “eventually” does not solve today’s problem.
Much of the port was mechanical, and AI handled that work well.
But the machine kept thinking in Intel SIMD.
I had to tell it:
Loongson has vectorized division. Use it.
And:
Loongson has instructions resembling F16C. Use them to build the float16 conversion bridge.
And:
On some Loongson server configurations, the base page size is 64 KiB. That can produce 512 MiB huge pages rather than the 2 MiB pages you expect from x86. Detect the page size and disable HugeTLB support here until we implement it properly.
The machine knew how to type the code.
I knew which code should exist.
That distinction is not cosmetic. It is authorship.
What Remains When the Machine Writes the Code?
I once took pride in having the entire 8086 instruction set memorized. I knew the tricks. I knew how the processor differed from the NEC V20.
When I moved to compiled languages, I lost something: the craft of writing every instruction myself.
But I gained the expressive power of a higher-level language.
Assembly became an expensive, labor-intensive luxury—something to use when the problem truly justified it.
The same transition is happening again.
Writing every line of code manually is slow. Increasingly, it is a luxury.
The machine can do much of it now.
Not as well as I can. Not without supervision. Not without making mistakes.
But often well enough that I can spend my time specifying the implementation in as much detail as necessary, reviewing the result, and correcting its structure.
What remains for me is the core of what I am actually good at:
Understanding the larger machine.
Understanding how the pieces fit together.
Understanding the requirements nobody wrote down.
Recognizing what feels wrong before a benchmark or production outage proves it.
Deciding what should be built.
Deciding what should not be built.
AI is freeing me to build projects I believe are useful: Redis-compatible databases that use less memory and, in many workloads, outperform the incumbents; large-object stores that make better use of storage hierarchies; SIMD implementations for architectures most of the industry ignores.
It also frees me to build silly things.
As I write this, I am working on a remote-server environment with agent forwarding, socket forwarding, and the ability to move large files through a Mosh session—because I have opinions about how its compression should work and which features it ought to provide.
For roughly $500 a month, AI gives me something that feels like a team of 30 programmers whom I can direct toward my vision of the future.
They are fast.
They are occasionally brilliant.
They are frequently ridiculous.
And they still need someone who knows what the machine is supposed to do.
That is why I say I wrote the software.