You Blinked And Everything Changed
Mitchell Hashimoto had this great tweet on the day I decided to join Twitter. You can read it here . The TLDR: he pointed an agent at a renderer and let it run in a Ralph loop for hours, a few hundred dollars of tokens deep, with one job: make it faster. It got frame times from 88ms down to 1.5ms and memory allocations from about 150,000 down to 500. On paper, a miracle. Then he mentioned his own hand-written version ran at 20 microseconds, that’s 0.020ms, with zero allocations in the hot path. Roughly seventy-five times faster than the agent’s “miracle.”
The point he was making is you cannot just trust an agent to have perfect systems knowledge, and you cannot hand over control of the low-level system and expect elegant solutions to come back.
This stopped me dead in my tracks. I was the guy he was talking about. The one who didn’t understand any of the low-level stuff. There was no way I was going to become a senior-engineer-level Rust writer, and I’d really been enjoying building software. So how do you reconcile that? One way is to keep shipping garbage. The other is to get good.
Since I have a screen reader doing the reading, which means it isn’t reading all the weird punctuation out to me anyway, I figured I’d use my clanker to teach me in a way that might actually be valid for how my brain takes in information. First step was defining what I was even trying to learn. I wasn’t trying to become a Rust wizard, or a computer language polyglot. I needed to drop down one level at a time in my own systems design thinking. I had gotten good at taking a broader system and decomposing it, but the execution was the line I had to stop at. I had no way to evaluate the elegance of the solution. So I asked my clanker to test me.
Me: Test me. Give me ten lines of my own code and let me read it before you explain the symbols.
Clanker:
fn rotate_and_remove(&mut self, device_id: &str) {
self.current_epoch += 1;
self.active_devices.remove(device_id);
}From your security spike, a device getting kicked out during a key rotation. What does it do, not what’s it for.
Me: Bumps the epoch by one, pulls the device out of the active list. But that’s all? There’s a separate table holding everyone’s keys and nothing here touches it. So the kicked-out device’s key just stays. Add a device, a key goes in. Remove one, nothing comes out. That table only ever grows.
Clanker: That’s the catch, found by reading structure, not syntax. What goes in, what comes out, is anyone balancing them. Invisible to every test, shows up months later as memory creeping for no reason.
The truth about this answer I gave was that I was still relying on mostly the plain English words. It was a combination of that and guessing what certain syntax meant based on its English meaning. I needed to understand this and I needed to start small. So this next test happened, one with just a few lines.
Me: Okay but I want to go lower than that. Give me something tiny and let me try to read the actual mechanics.
Clanker:
struct Counter {
seen: Vec<String>,
}
impl Counter {
fn record(&mut self, label: String) {
self.seen.push(label);
}
}Me: Wait. I don’t even know what Vec<String> is. I see you putting a string into it but I don’t understand how that means it holds many strings.
Picture me dropping my glass here. I understood none of it. I had been generating this code for months. I had so many questions. What is a struct? What is the difference between a struct and a type? How do you get a vector to hold more than just letters? What is the difference between declaring a function and invoking it? An hour of back and forth later I had attempted multiple of these small syntax-deciphering puzzles till the one with all four correct answers happened.
Clanker: Fresh one. Read it cold, use the signature as your tell.
fn brew(beans: Vec<String>) -> Pot {
let mut pot = Pot { grounds: Vec::new() };
for bean in beans {
pot.toss(bean);
}
pot
}Which is the source, which is the destination? What grows? What gets handed back?
Me: beans is the source, the pile you read from. pot is the destination, the thing you fill. The shelf that grows is pot.grounds, where the beans land. pot has the changeable marker because it’s the one getting filled, and brew hands back the pot at the end.
Clanker: Four for four, cold, no gloss. Three exchanges ago you flipped every one of those. That’s the rep sticking.
Did I get this because of reps? Did I get this because it was about coffee and anything to do with coffee is automatically better? Who knows. All I cared about was that for one day, I’d learned to decipher a ten-line Rust file without anyone reading me a single ampersand.
Now if you’re a techie, you are probably rolling your eyes at this. Yet another non-dev in the field now thanks to a clanker. If you’re a normie like me, all of these chats are probably as confusing to you as they were to me a few hours ago.
Here is the thing though, I already legitimately do a version of this exact thing. When I write music, I compose for instruments I don’t know all the time. I can only write for these instruments because I have had extensive conversations with players of the instruments, studied examples of what is possible and what is not, and also figured out the notation methodology to represent those choices.
Like for example, I can’t play any instrument in a string quartet, but I know what is physically possible to play on the instrument, I know all the different articulations that are theoretically possible and I know the symbols in sheet music to represent them. Just like code though, I don’t write my own sheet music yet, as the previous blog post showed. So I knew a version of this was possible with code and systems design too. I just had to construct the right ladder.
And this ten-line file today was the first rung of that ladder. In a bit maybe it will be a 50-line file and then a 100-line file, then multiple 50-line files working in tandem. The idea is that doing these reps with reading the code over time will build the muscle of being able to make systems decisions closer to a language level.
And the reason this matters more in code than in music, the one place code is genuinely harder, is the playback. A cellist physically cannot turn my part into something structurally different without me hearing it. The notes are the notes. But a clanker can hand me back something that sounds exactly like what I asked for and is built wrong underneath, because code doesn’t play its own guts out loud the way an instrument does. The divergence hides. So I don’t just have to learn to write the part. I have to learn to hear the playback honestly enough to catch when the player went off-script.
So that’s where this is going. Reading enough of this code, doing enough reps, reasoning about it enough, until I get to a point where I don’t have to tell an agent “hey, go make the epoch keys rotate” and hope. I can tell it to make the epoch keys go on a merry-go-round, ride the London Eye, twirl around in a mug of coffee, and zeroise themselves from memory after, and when it hands the thing back, I can read it and know it actually did what I wrote. Not because I trust it. Because I can hear the playback.
I have one more piece of personal housekeeping and growth related to vibe coding as a non-dev that I want to share in my next blog before I get into Facets, my local-first end-to-end encrypted app. So if you want to read on, some other tools I use to make sure I can mitigate some of the downsides of not being an engineer, as well as some upsides, check out the next post.
Blinding Pixels