In this article I continue the more critical line of thought I began in Mindless Drones. I hate AI for what it makes us become, yet still hold that there is no point in not using it — at least for engineering, or for anything whose output can be judged to be of the same or better quality, only more of it. But never just more of it: which, sadly, is clearly the case for most non-engineering uses (and, let’s be honest, for many engineering ones as well).
Man’s collective mastery of nature, moreover—even if we could ignore the mounting evidence that this too is largely an illusion—can hardly be expected to confer a sense of confidence and well-being when it coexists with centralizing forces that have deprived individuals of any mastery over the concrete, immediate conditions of their existence. The collective control allegedly conferred by science is an abstraction that has little resonance in every-/day life. Scientific technology has made life more secure in many ways, but its destructive side, most dramatically revealed by the development of nuclear weapons, adds to the feeling of insecurity that derives from the individual’s diminishing control over his immediate surroundings. […] The structure of modern experience gives […] far more encouragement to a sense of helplessness, victimization, cynicism, and despair.
— Christopher Lasch, The True and Only Heaven: Progress and Its Critics (1991), pp. 385–86
Screenshot from a CMUX workspace; left hand side me speaking to the agent: “im not even sure what to look at” - to which it responds: “Let me just point at them directly - I’ll outline both titles in the live browser and screenshot”; right hand side, it, helpfully, highlighting the relevant element in the context of a browser window of the running application
So this is what happened to me the other day. Doing a PR review. Trying to fumble my way in, and my masterful prompt “im not even sure what to look at” getting insta-rewarded with that “blows my mind” moment.
Upon later consideration (and after I shared that success story) I was going to add, “hard to believe that it has come to this. That this is how I program now”.
Which reminded me that I still wanted to give the following a closer read: On the foolishness of “natural language programming”, by Edsger W.Dijkstra (of shortest-path-algorithm fame), which you can find here.
See more
This was written in 1978 and, while surely interesting, my takeaway is that I think what he had in mind were surely algorithms. Programming back then seemed to be pretty much centred around relatively small chunks of code (if I may overgeneralise a bit). So his argument was something like symbolic notation is perfectly unambiguous, language isn’t. And while we might imagine that the rather taxing act of programming could be alleviated if we just could “tell the computer”, this comes at a price: it leaves room for errors — the kind only a being equipped with reason can spot.
The problem is, requirements for computer programs have vastly outgrown the “sort me a list” phase. I mean: in every slightly more complex program that is not mathematics-shaped from the outset, an act of translation from natural language into symbolic language is necessary. We call that requirements engineering and it can be frustrating. But there’s no way around it. Programming, in that conception, is literally disambiguation. But disambiguation does not mean that there is a straightforward, that is, in itself unambiguous, path.
Seen through to its logical conclusion, it seems that virtually all programming is natural language programming.
A new question which started bugging me recently is how, if I start allowing my AI to make more and more edits in my “second brain”-type notes archive, can I tell what’s what, i.e. what changes did the agent make and which ones did I make. For whole notes, the answer lies in the diffs. That is, either I store the next version and am that version’s author, or the agent does and is therefore that version’s author. The diff implicitly tells us what that particular edit consisted of.
Now today I took part in a discussion about - amongst other things - identifying after the fact whether code has been written by AI. Primed by my earlier thoughts, I quickly converged on the following idea.
See more
We can use git (I know, there is nothing new under the sun) to track each and every edit the agent makes as a commit - under its own git handle. I think this might be counterintuitive at first — to have such fine-grained commits, that is, so many of them — but of course we have LLMs to parse through the mess later. But it’s clear what it gives us: it is one way to solve the attribution problem
(In case you wonder: agent harnesses provide the means to intercept tool calls, so the part about how to make the automatic commits is trivial.)
I keep thinking about this article I Sold Out for $20 a Month and All I Got Was This Perfectly Generated Terraformlink and how it maps to my own experience with my own projects, one of which is a Todo app I make heavy, heavy daily use of and which has become quite important to me.
I kind of have a sense of where it’s at, architecturally, and also a lot of experience working with web apps to support that. And my wish to actually spend time interacting with the code converges toward zero the more I work on it.
It’s not that I don’t care. But the reality is that this app helps me solve problems (managing my time), and every improvement to it feels like a better investment than looking at the code and making sure it has the properties I imagine it should have to be called maintainable.
See more
First of all: whatever “maintainable” even means in this situation, since we’ve got much more leverage now with LLMs. Of course, we also produce more. So there is surely a commensurate amount of diligence required in any case. But I doubt we can know how much without experiencing it firsthand. But that wasn’t supposed to be my point here.
Okay, coming to that point: I really feel that the way you speedrun your own projects, steering their course in real time instead of painstakingly making progress over the years, gives you a wholly different experience — namely, that of a product manager who works closely with his techies to get things done. The techies are always worried and concerned about technical debt and maintainability and the future. But really, no matter how often they voice those worries, I doubt that a PM who hasn’t been repeatedly bitten can actually pay more than lip service to understanding it. To be sure, this is meant empathetically. They shouldn’t. For how should it be clear how much worry is justified, and how much is just the devs buying themselves time?
And now I’m here wearing both hats. You understand? It’s not that I don’t care. It’s that I want — need — stuff done. And I feel that I can afford to postpone dealing with any potential mess. Which in part stems from my confidence as a programmer, and in part from my sense, as a dev in general, that it probably won’t even happen.
This started off at work with a requirement to give users an FAQ-type answer-bot able to answer simple questions, but our knowledge base consisted of completely unstructured data. I’ve never done this before, but it came together very quickly — by which I mean how to get some sense of control over the outcomes.
What needs to be done is to formulate question-answer pairs “by hand”, in a test catalogue. Just state what you’d ideally expect your bot to answer, imagining the user asks this particular thing or that. Come up with 20 or so of those. Then feed the bot a system prompt and the knowledge base, pass the question, and compare the answer to the formulated expectation. Do this for the whole test catalogue. Let an LLM “improve” the system prompt, and repeat the process until you have a system prompt which produces the kind of usable answer you’d like to see, given a knowledge base.
Now, this technique is coming in handy for my hobby project of ingesting books in a meaningful way (by my own definition). One task I’m interested in is getting highlighted passages automatically into my resource archive.
See more
The same technique applies here. The underlined-text extractor looks at a page and produces the highlighted passages. Now we do this by hand for 20 or so pages and jot down our expectations, against which we run the same procedure. Run it once, compare the outcomes to the expectations, tweak the system prompt of the extractor, try again.
The one innovation, from my naive vantage point of re-discovering the wheel for myself, is to measure this (there exist pretty standard similarity measures) — which I’ll surely carry over to my work problem.
In any case, what also needs tuning are some intuitions. Driving the prompt by expectation matches the habitual driving of implementation by specification. But what costs me a little is letting the system prompt be completely generated. This stems in part from my conviction that some hand-written and curated instructions are really worth a lot (no proof!). In this case, I have learned to totally let go.
I treat the system prompt as basically some activation pattern — not different from the weights of the model itself. I believe this is the right way to see it.
Yesterday I stumbled on another article which talked about this technique as analogous to neural stimulation, where you mention certain words, then observe the neural activation in the neural net, and then directly “overstimulate” those weights (manipulating the thresholds) to nudge outcomes in certain conceptual directions. Which I think is interesting, but why not just mention those words again and again? Don’t think of a blue elephant, don’t think of a blue elephant, don’t think of a blue elephant type of thing.
Anyway, be that as it may — definitely interesting.
Drawing on Karl Popper, David Deutsch, and a Rich Hickey quote about “guardrail programming,” this essay speaks about the tension between specs and code — particularly relevant now in the context of AI-generated software. The argument: tests are not equivalent to code, nor a substitute for it; their purpose is to reduce cognitive load by telling a story about the system’s behaviour. What survives the shift to LLM-assisted development is architecture and methodology.
Recently I found I had good success with what I dubbed text-driven development. I was always a little bit fond of the idea of behaviour-driven development with its core idea of specifying application behaviour in natural language, but still trying to nudge people towards gathering examples, in the interest of making things as little ambiguous as possible. Well, I think it’s a neat idea, but anyways.
Now, another thing I think can be handy are to have an up to date natural language documentation of an application, and since one can also argue that specification is nowadays as much worth as working code thanks to LLMs, I thought why not write prompts in such a way that they are fully human readable, but keep the structure of tests, which “tell a linear story” of various aspects of an a system.* Here is what this would look like github.com/eighttrigrams/tracker/meeting-series.md.
See more
Now all good and well. Long story short is that I did not feel much desire of keeping this up. A couple of months ago I was all about writing my own little software factory, a pipeline of steps which you feed tickets and which spits out code, but in controlled ways and with considerable quality, due to all sorts of machinations. It find it quite exciting to participate in something akin to unexplored territory, where you are in the same position as everyone else basically. But the truth is, these models are good, and are getting better, and all that extra feels completely unnecessary at the scale I develop, apps primarily for my own use. I have yet to come across an instance where I feel I have to actually pay for my technical debt. Maybe I’m just super-talented!
Ok, jokes aside, I think it definitely pays off when you know what you are doing and have all that experience under your belt. In the same way that a free jazz musician almost cannot sound bad even if he literally hits all the wrong notes. Whereas the vibecoder who can one-shot space invaders but gets stuck easily one shotting his own functioning app for his business or whatever would be more like a total music noob hitting the keys in random manner. That doesn’t sound the same. But anyway, with my explorations I also feel like a youngster who just overdoes things, but — seen favourably — precisely as a matter of developing judgment.
* The idea of so called cross-cutting concerns is related. I always find it a little misguided, as I think all concerns are cross cutting and it is the purpose of tests to manage this complexity by narrative means. Btw., there existed an idea of “literate programming,” which you can look up.
Good that that debate has been put to rest, if there ever really was one. LLMs are here to stay, and I don’t know anyone who’s tried them who doesn’t use them for something — which means they think they can get a speedup on that particular something, whatever else they say about LLMs being stupid or “not real AGI” or “Schießmichtot ” (shoot-me-dead, a German expression meaning basically whatever).
What I find striking, though, is how different the two experiences of vibecoding vs. “coding at work” are. I’m under the impression that I get a significant boost at work as well, but it’s more about rapid initial exploration of workable solutions than pumping out code really fast. Now, the experience from vibecoding surely carries over into how effectively you can wield your tool of choice at work, but I had this thought today about how so many things have shifted slightly. For example, I’ve found it more useful to maintain a simple complaints sheet with one-liners of what doesn’t work than to describe bugs properly. In the evening hours, I can feed those, ever so slightly rephrased, to my LLM and let it do its job while I watch a video, and get my improved app for the next day.
Now, one observation here for me is that fixing minor imperfections is often so fast that it becomes almost tolerable when fixing one thing breaks another.
See more
This is exactly the sort of thing you want to prevent “at all costs” normally, which you do by a) ensuring modules are shared and b) testing.
As exciting as it was at the beginning to listen to Yegge, I thought it outright crazy to lean into “letting go” that hard. But the longer I do this, the more I have to concede certain things. For example, that even duplication isn’t always totally bad. I mean, I even heard Donald Knuth make that case before LLMs became big — that literally “forcing” DRY comes at the cost of readability and makes code harder to understand and maintain. We should remember, it’s always tradeoffs — always. DRY is no exception. And DRY is at this moment still a notorious weakness of LLMs, which you can either put a lot of effort into counteracting, or accept to a certain degree.
Now — bear with me on this one — there’s this school of thought, which I believe comes from the microservices world, where testing certain things is basically impossible, so you shift focus to observability and resilience. You accept failure and make sure you can recover quickly and that you’re on top of what’s going on. People have already suggested doing away with PRs since they’ve become a new bottleneck (can’t find where I read that, but on the topic of PRs, I found this interesting), so isn’t there a case to be made for treating at least certain classes of errors as acceptable? It depends on the cost of things going wrong, obviously. But the point is, it might be easier - and ultimately less expensive - to fix some problems as they’re discovered, even if they break again later, than to test for them at great expense or to prevent errors “at all costs” by developing them to a high standard — which boils down to making sure they “share the same component ”.
Currently having a blast with LLMs and books. The low-hanging fruit is saving scanned pages in a folder and asking the LLM to look at each page and rename the files according to the page numbers, i.e. p.35.jpeg, p.36.jpeg and so forth.
My plan was to get full books into my archival/retrieval app Rhizome. Turns out current LLMs are superb at transcribing full pages, but apparently there are copyright barriers in place, for obvious reasons. So for the basic transcription, even if imperfect, I do OCR with tesseract, which does a perfectly fine job. I want to query and summarise over the books to get basic questions answered, not accurate source citations out of the box. For scanning, I use the vFlat app to straighten and scan pages quickly with my phone.
See more
What really thrilled, though, me was the following: I asked the LLM to identify underlinings it thought I’d made because I was unsure about translations of certain words (as a non-native English speaker). I then asked it to pull those out into a list, in the following format (example if from the book The Prize):
PAGE: p.34
WORD: penurious
SENTENCE: Such care in small things might seem penurious to some people, yet to him it was the working out of a life principle.
IN CONTEXT: Appearing excessively frugal or miserly, describing how Rockefeller’s extreme attention to small expenses looked to others.
IN GENERAL: Extremely poor or poverty-stricken; or excessively unwilling to spend money; miserly.
GERMAN TRANSLATION: Solche Sparsamkeit bei Kleinigkeiten mochte manchen knauserig erscheinen, doch für ihn war es die Umsetzung eines Lebensprinzips.
What a word means varies with context, so I thought it important to ‘corner’ a word from multiple angles. Seems to me a much more promising way to learn new vocabulary, and to retroactively make full sense of sentences I’ve come across.
As someone who is a fervent adherent to automated testing, I should have known better anyway, but for fairly complex vibecoded projects of mine, I found myself neglecting to add tests at some point. My intuition came from a place of accepting loss of control to some degree, leaning into it, one might say. After all, there are people advocating for exactly that; notoriously, Yegge. But also people arguing for giving up on PR reviews — the new bottleneck. Be that as it may, my post-hoc reasoning was roughly that I’d have to at least look at and curate the tests, if not the code directly. And since I had no wish to do that, I might as well leave it be.
Courtesy Simon Willison for setting me back on track, so to speak. He was basically saying that it simply helps keep the agent in check, when it touches a thing and this breaks another thing — basically the same situation as when two teams touch the same codebase: regression testing.
See more
Here’s a bit of additional reasoning of mine: even if both tests and code have been written by an agent, there is a kind of triangulation happening, if the code is known and proven — by usage — to be stable and bug-free. The idea is the following: you know (provided you did red/green style checking, as also suggested by Willison, and is good practice) by using the app that it works. And the tests reflect that working state of the system. So if the tests fail, you really should expect some part of the application to be in trouble, even if you’ve never seen the tests up close.
Also, I found Opus 4.6 had this tendency to change that one particular spot in my codebase whenever I touched it. My ideas of what I wanted to achieve there were obviously were a little different from what it knew from its training data. Tests, and this is known, also can communicate intent. So in this case some tests help cement the desired behaviour. This case, then, clearly asks for manual curation of the test.
I’ve decided to follow through with my conviction expressed here and in other places, “to interact mostly with material and sources we have a direct relationship to”. So I vibecoded my own blog here, and am back to good old fashioned Atom feeds. Ah, the youth. You just don’t see it when you are younger and just use whatever is shiny and fancy (which was: Substack at a particular time), without thinking about long term implications.
There are well known problems with platforms, but what is my personal pet peeve are these recommender systems which I find absolutely vile. Somehow this thought occured to me is that this is maybe because whereas your brain tries its best to predict its environment (or form it according to what you want to see), these things try to predict you. Which makes you a kind of prey, isn’t it?
See more
On that note, I’ve gone over to build my own curated feed into my personal issue tracker and cancel some subscriptions. My inbox now receives feeds from other blogs, YouTube videos from channels I’m interested in, email and so forth. And I can tune in or down whatever I need to see more or less of. All in one place, satisfying men’s hunger for news, but avoiding one’s attention system to get hijacked. Let’s see how this goes 💪.
For a couple of days already I have been ruminating over putting down an ontology for Tracker (GitHub), my personal issue tracker, somewhere, when it hit me all of a sudden, that I already have a perfect place for this: my Personalist app, about which I’ve written here.
The thing with software ontologies is that they shift (have to!) slightly over time, as new requirements come in, or existing ones change, and in sync with that, as our understanding of the domain, and of the application which models it, changes. This is why Personalist is the perfect place, because it can capture this changing understanding across time.