Last week I found an image tag in a Dockerfile that has never existed. Not stale, not misspelled. The model simply invented it whole. The code looked tidy, the tests passed. The author of the pull request said he had checked everything by eye.
That same day I watched a man spend his fourth hour manually shuffling data between two formats. A short script plus tests would have closed the job, but he refuses, on principle, to touch “generative crap.”
Both cases annoy me.
I use AI every day: writing code, picking apart other people’s repositories, running experiments, building datasets. My home hardware runs a self-hosted stack of dozens of services, and without AI I could not maintain it alone. About a year ago I started a journal: every time the tool lied to me, and every time it genuinely bailed me out, an entry goes in. It has grown into two columns, and both deserve to be shown in full, evidence included.
At the end I will explain what the Luddites have to do with any of this and why I think they were right. That is not a figure of speech; I have the numbers.
Bill one: the 2.6-fold speedup that never was
My reranker benchmark measured 2.7 requests per second. The production service on the neighboring node, same image, same model, was doing 7. A 2.6-fold difference out of nowhere.
I decided I had struck gold: the production node had memory and kernel tuning, so tuning must be what buys the 2.6×. The AI assistant I was running the experiment with backed the hypothesis and helped assemble the controls: same image, same governor, same model. Everything checked out. I was already planning a reboot experiment, downtime and all, to finish off the proof.
Before the reboot, I asked the running service one question:
$ curl -s localhost:8082/props | jq .total_slots <!--hq-->
4 # my bench, meanwhile, ran with --parallel 1 <!--hq-->
The production service was running four parallel slots. My bench ran with one, set explicitly. And the service itself never set --parallel at all: the default had changed between llama-server versions, and one had quietly become four. There was no such thing as “tuning buys 2.6×.” The real contribution of tuning, after an honest A/B: 6–14%.
The lesson came cheap only because I asked the running process for its effective config instead of believing the command line. A missing flag does not mean “the default I remember.” It means “the default of whichever version is actually running.”
Bill two: an nDCG above one
For my RAG stack I built a system for scoring retrieval quality. By definition, nDCG lies between zero and one. Mine reported 1.63. On the next run, 2.13.
The evaluation code was written with AI. The tests were green, including two tests asserting “perfect input yields 1.0.” The bug sat in the units: the numerator counted hits over document chunks, while the normalizing ideal was computed over annotation anchors. Different sets, different cardinalities, and the metric calmly sailed past one on any real corpus.
Why the tests didn’t catch it: both “perfect” tests called the function without an argument that production always passes. The tests exercised a branch the production code had never taken once. It was an outside review against primary sources that found this, not my automation.
Since then I have a rule for any metric: the boundary invariant gets its test first. If a value is required to stay within an interval, that is checked by a dedicated test before anyone starts believing the metric.
Bill three: healthy for a month, four documents out of thirty
The most expensive bill. The RAG stack had been in production for a month. Every healthcheck green, endpoints responding, monitoring silent. Then I loaded thirty real documents into it.
Four got indexed.
The chunker cuts documents into pieces of 545–574 tokens. The embedder, missing one non-obvious flag, capped the physical batch at 512. Anything longer was not truncated — it failed with a server error. All the while the service was dutifully advertising a capacity of 2048 tokens per slot: the advertised capacity was verified by no test at all, every test I had embedded short strings.
For a month the automation kept answering “alive.” Nobody had asked it “but can you actually chew a document of normal size?” The class of failure called “formally alive, actually unusable” is caught only by real use: load a corpus, ask a question, look at what came back.
The tag from the first paragraph, by the way, comes from this same column of the journal. Right format, right hash length, a string indistinguishable from a real one. A model’s lie cannot be spotted by the shape of the answer at all: it looks like a normal version, a normal URL, a normal test, a normal chart. Which is why “just pay closer attention” is nearly useless advice. You need an external loop.
The second column
Showing the bills while staying silent about the winnings would be dishonest. Over the same year, with the same tool:
- a self-hosted stack of 34 containers brought to a working state (repository);
- a measurement campaign of two-hundred-plus runs, with the raw data of every one published;
- three small open models for Russian (they live on Hugging Face);
- a data preparation, training, and quantization pipeline that now gets reused.
A year ago I did not know how to do a good part of this. AI did not do the work for me, but it sharply shortened the path from “no idea where to start” to a first working version.
The price is in the journal too: verification eats a quarter to a third of the time saved. Every exact fact has to be confirmed, numbers re-measured, patches run through tests, and then the tests themselves need checking. Even after that, the balance stays positive. In day-to-day work, AI resembles a coworker who types faster than anyone on the team and never admits there is something he doesn’t know.
The protocol these bills led me to changed after every screwup. By now it is short.
- Every exact name is verified externally, and every fact is labeled with its provenance. Version, tag, flag, hash, URL. Look in the registry, the documentation, or the sources — not in the same model’s next message. There are three labels: verified myself, took from the documentation, got from the model. The second and the third must never be mixed.
- A green test does not count as proof that things work. Walk the real path at least once: load an actual document, look at what the search actually returned.
- Defaults are pinned explicitly, and one variable changes per experiment. Ask the running process for its effective config.
- Raw results live next to the conclusion. A reader who can recompute the table will find the mistake before it becomes someone else’s production.
- The full price gets counted. The first memory measurement under load showed 7.7 GiB; the process’s actual consumption turned out to be 187 MiB, the rest was file cache.
The methodology is homemade. It is just that without it, the hours you saved come back a month later as tech debt.
What the Luddites have to do with it
Now for the part I promised. While I was keeping this journal, the industry split in front of me into two camps: one declared the tool a useless slop generator, the other declared the skeptics Luddites. The second word gets used as a diagnosis. A pity: the real history of the Luddites is far more interesting than the caricature, and it is precisely about our journal.
The Luddites were not peasants frightened of machines. The UK National Archives describes them region by region: the Nottingham framework knitters had worked stocking frames for generations; the Yorkshire croppers stood at the top of the trade’s skill pyramid. People who knew the technology better than anyone in the country broke machines selectively, at specific manufacturers who were cutting rates. In a letter to the Home Office from February 1812, the Nottingham magistrate Robert Baker described a case in which cloth carried off during a raid was returned to its owner the next day: the men breaking frames were pointedly not robbers. Eric Hobsbawm called this collective bargaining by riot. The violence was real too, up to and including the murder of the manufacturer William Horsfall, and the hangings at York in 1813 were for specific crimes, not for economic views.
The demands had a guild side as well: restoring the seven-year apprenticeship meant closing the entrance to the trade — that is, striking at other workers. When the barrier to entry collapses, skill stops being a shield, and all that is left is holding the door shut by force. They could not hold it: the apprenticeship clauses were repealed in 1814.
The Luddites’ main argument was about quality. The cheap “cut-ups” Nottingham rioted against were cut and sewn instead of knitted whole, and they came apart at the seams. Byron, delivering his maiden speech in the House of Lords against the death penalty for frame breaking, said exactly this: the work gets done faster, comes out worse, and goes for export. The speech survives in Hansard. The knitters were completely right about the quality. It did not help them at all: “good enough and noticeably cheaper” beats “excellent and expensive” in most markets. Sound familiar? The first generated draft of code is usually worse than what a strong engineer will write. And for most tasks the market does not want the best code — it wants code that is good enough by Thursday.
Productivity rose before wages did
Now the numbers that make me think the Luddites were right on the merits.
Robert Allen named the first half of the nineteenth century “Engels’ pause”: over 1780–1840, output per British worker grew by roughly 46% while real wages grew by 12%. The rate of profit doubled over the same period. The calculation rests on Feinstein’s pessimistic series; Clark’s optimistic series comes out milder, but historians argue about the depth of the gap, not about its existence.
After 1840 everything changed: by 1900 output was up 90% and wages 123%. Labor caught up with productivity and pulled ahead. So the machines did, eventually, make everyone richer. The problem is the word “eventually”: the gap outlasted the working life of anyone who met mechanization at its start.
With the handloom weavers, mechanization finished the job in two strokes, and the first blow came not from the looms but from the trade’s own influx of people. Machine spinning had made weaving profitable: yarn got cheaper, demand grew, and the craft could be learned in weeks — conveniently, the seven-year apprenticeship had just been abolished. That same date, 1814. People flooded into the trade, and it was they who crashed the rates: by 1817 a weaver’s earnings had fallen by more than half, even though the whole industry counted about two and a half thousand power looms. There was, as yet, physically nothing to crash wages with.
The looms arrived as the second stroke. By 1830 there were 55 thousand of them, up from 14, while parliamentary debates still counted around 240 thousand handlooms: people were not vanishing from the statistics, they went on working the trade while the trade got cheaper. A weaver who in the best years had made up to 23 shillings a week by 1826 was making 5–8, and in the non-crisis 1830s parliamentary commissions recorded the same levels. Those shillings are nominal; adjusted for deflation, the fall is closer to threefold. Still a several-fold collapse in living standards within a single generation, and a weaver could not borrow prosperity from his own grandson.
The two-stroke scenario is worth trying on software. First the technology makes the profession accessible and well-fed: bootcamps, the promise of a short path in, an influx of people. Then it takes away both the earnings and the entrance. The transition does not have to look like a mass layoff on one particular Monday. Sometimes the job title stays while its price slowly falls apart. Sometimes productivity grows and the savings go to someone other than the people who rebuilt their own way of working.
The question the Luddites asked — and paid for asking — was not “are machines good.” It was: who captures the gains of the transition and who covers its costs. Carl Benedikt Frey works through this logic in The Technology Trap, and Brian Merchant in Blood in the Machine.
Who pays for the transition now
The most obvious candidate: the people just entering the profession.
The Stanford Digital Economy Lab recorded a relative employment decline of 16% among workers aged 22–25 in the occupations most exposed to AI (the first version of the preprint said 13%). More experienced workers in the same fields held up better. According to a study built on a US database of online job postings, junior developer postings fell 14–15% relative to senior after ChatGPT came out.
Both sources are still preprints, the data is mostly US, and the same period absorbed other blows to hiring: the Section 174 tax change, which from 2022 forced American companies to capitalize development costs instead of deducting them immediately (the deduction was restored in the summer of 2025 for tax years beginning after December 31, 2024), the end of the zero-rate era, the unwinding of covid-era overhiring. The Stanford authors themselves caution that the whole effect cannot be pinned on AI, and in a February 2026 update showed that under the strictest controls the decline for the most AI-exposed young workers only shows up from 2024 onward. But the entry problem no longer looks imaginary.
If a team removes every task a beginner could do, senior engineers will not start materializing out of thin air five years from now. Today the team saves money on this; later it gets a shortage of people who can answer for a system as a whole, not merely accept or reject a generation.
To those entering now, a separate word. The influx of people into software was a rational response to a signal the industry itself had been sending all through the 2010s; there is nobody to blame. What has changed is the proof of entry: a course certificate no longer works, verifiable artifacts do. Your own repositories, your own measurements, your own contributions to open projects. That is unfairly more expensive than it was five years ago. It is still cheaper than seven years of apprenticeship.
There is also a difference from 1812, without which the analogy turns into fatalism. A Luddite lived under a ban on workers’ associations (the Combination Acts, 1799–1824), without the vote (urban workers partially got it in 1867, rural ones in 1884), with the Corn Laws propping up the price of food, and with the gallows for a broken frame. The sixty-year gap is not explained by the steam engine. It is explained by the injured side having not a single channel through which to present the bill. Today the channels exist: the right to organize (a union, unlike a guild, bargains with the employer over the share of the gains instead of holding the door against other workers), portable skills, open weights, the ability to stand up inference on your own hardware and walk away from a vendor. My home stack exists for exactly this reason. The channels carry no guarantees: when the researcher Daniel Kokotajlo was leaving OpenAI, he was offered a trade — his silence about his criticisms of the company for his earned equity of roughly two million dollars. He refused, the story went public, and the company backed away from the practice of such agreements. The channels work only as long as people use them.
At the personal level the answer is boring: learn the tool, but do not hand it ownership of the system. Keep your data, your tests, your infrastructure, and the ability to change vendors. At the team level it is harder: preserve the kind of work where a person learns something, instead of becoming the operator of a “generate again” button.
In place of a conclusion
In 1804, Jacquard improved a loom in which the pattern was set by a sequence of punched cards. Babbage took the principle for the Analytical Engine, and Ada Lovelace wrote that the machine “weaves algebraical patterns just as the Jacquard loom weaves flowers and leaves.” The Luddites never broke a Jacquard loom; the mechanism reached Britain after the gallows at York. But programming grew out of the same industrial world where the machine first began to change the price of professional skill. Now the argument has come to us.
Refusing AI on principle looks like a losing strategy to me. So does using it without verification, and those people worry me more: the principled refusenik at least is not pushing hallucinations to production.
The author of that pull request, by the way, still takes the model at its word. The man who was moving data by hand is still moving it. Both are certain that time is on their side.
The Luddites’ question stands: who captures the gains and who pays for the transition?
I am curious about your journal. What has AI already broken for you personally, and what has it given you — both columns. Especially from people who hire juniors, and from people trying to get in right now.
Yes, this text was prepared with the help of AI. I am leaving this line in deliberately. The links to primary sources sit right in the text, the raw data of my measurements lives in open repositories, and several handsome phrasings did not survive verification and were thrown out. I suggest arguing not about whether AI was involved here, but about whether the text stands up to verification.