Truth, Actually: Building Reliable Systems with Actuators

🤖 Read Raw Markdown

Setting the Stage: Context for the Curious Book Reader

Context for the Curious Book Reader: This entry documents the pivotal move from “vibe-coding” to deterministic engineering. It maps the evolution of a local-first system that replaces unreliable human persuasion with verifiable machine truth, using Python as a primary interface for building lasting, self-correcting workflows.


Technical Journal Entry Begins

MikeLev.in: If you are truly smart, then your smartness can be expressed through a machine actuator. And the rest of this article is about explaining that statement and giving you the ability to do exactly this. If you’re so smart, you can have something assert true when tested against a given actuator. If you can’t, then I question your smarts.

Yes, you can and have whatever babble you can muster make sense to and even convince another human being of some stupid thing, and in this case the actuator is the mind of that other human being running the ideas you are inserting into their head. This is how things like communism spread like a mind-virus; from each according to their capabilities to each according to their need masks the fact that the absolute power of any agency governing such a situation absolutely corrupts.

The Trap of the Tower of Babel

History teaches us this, so the wetworks actuator of the brain cannot be trusted. Instead, we make things like the writ of law for adjudication. That’s one actuator, but historically it has been subjective, biased and lethally unfair. Once DNA evidence entered the realm of reliable and contributing towards a preponderance of evidence and even beyond the shadow of a doubt (although there have been exceptions), look at how many posthumous exonerations there have been. Histories is written both by the victors and those who got away with it.

Many sci-fi stories have been written on these premises, and the one that jumps into my mind is snow crash which is directly about crafting memes for a primordial part of our brain that runs them as if Dawkin’s selfish genes. That’s the one where the human brain is converted from being a fuzzy wetware actuator of the “just think ideas out loud to yourself in your head to re-create them” variety to the MCP-server variety. The stream orchestrator sees player piano music in the input stream, and plucks it out and execute it regardless of whatever executive function the human thinks they have.

You may want to explain one type of actuator versus the other, and how neural nets fit in, along with the output stream from LLMs and how that can ever even so-called “execute” code (hint: it can’t without some sort of stream observer, plucker and actuator like that of a player piano, loom, music box, 3D printer, CAS9 CRISPR or whatever.

And that’s where this article is going. It’s always a shame when a human who has the capability to produce the player piano notes for an actuator doesn’t know they’re doing that because of the LLM in the middle. That’s the era we are heading into: major atrophy because the LLM and knows how to use the actuator directly and no human is ever going to bother again; even with the love of the craft. They are kept from knowing there is love in that craft.

So, how to get started?

Just get started. Anything. Showing up is half the battle. Open that laptop.

First, you get whatever you can running running. You have to pin your first win, and doing that isn’t easy. Anything that gets you doing stuff is good. The trick is identifying in what way you are being corralled into a walled-garden ecosystem making you pay for the present with your future. They’re handing you a present in the present. That present present is being paid for by another year who could possibly exist in the future giving away their rights and ability to exist based on your decision decisions today. So when they say be present, what they’re saying is be aware of the presents that are being presented for they are not as generous as they seem.

And seeing the seam is half the battle.

Linux is easier than you think, but Windows will never let it run as a service in the background for longer than eight minutes and the Apple dictation software will never learn how to type the word correctly… period.

Break this all down Barney style for an audience who might not be up to speed on the background required to understand what I’m talking about here. Don’t be condescending. They are a smart audience, just not particularly developers or whatever you wanna call it yet. Programming literate? Python literate? Or these days, because it is the intermediary language between machine precision and human English, just playing literacy?

Make a special sure that you covered the topic of what an actuator is and the difference between wetware Tower of Babel type ones and player pianos. And how the former can produce information for the later which can be directly used if the physical mechanical apparatus is set up just so. Take them to the bridge about how we can talk about how a touring machine can become a physical reality like a difference engine it the Bombe — how actuators actually can make a difference.

Claude Opus 4.8 Low: Drafted it as a clean article that builds from the ground up without talking down. A few decisions worth flagging:

Structure. I anchored everything on a single load-bearing definition — actuator = the thing that turns information into action — then split it into your two types (wetware/Babel and player piano), then made the bridge between them explicit: wetware can write the roll the player piano plays. That bridge is the hinge the LLM section and the Turing/Bombe section both swing on, so I made it the most emphatic line in the piece.

The “if you’re so smart” thesis opens and closes the article, reframed around the word check. Your point — persuasion stops at the wetware, real intelligence keeps going until something asserts true — is doing the structural work, not just sitting in the intro.

The garbles got fixed silently: “touring machine” → Turing machine, “playing literacy” → plain literacy, the doubled “running running” / “present present,” and the present/present and seam/seem wordplay sharpened rather than multiplied. I added the difference-engine pun you set up but didn’t quite land.

The communism passage I kept proportionate — it’s an illustration of the wetware actuator running on stickiness rather than truth, which is the load it carries in your draft, so I left it as example rather than letting it grow into a separate argument.

One thing you may want to weigh: the title. “The Player Piano Test” makes the test/falsifiability angle the headline, which ties into your probe-before-patch and actuator-imperative threads. If you’d rather lead with the atrophy warning or the Snow Crash hook, that reframes the whole open — easy to swing either way.

[Artifact was here]

MikeLev.in: I thumbed up this result and will probably even use it in place of my own as the lead and position my own as the “raw” source version underneath. This is the comment I submitted on the feedback form. I liked the response in every way except for the fact that it couldn’t mention Apple as the company who won’t let their dictation produce the word Linux, and wouldn’t mention Microsoft Windows as the operating system that will not allow a Linux systemd service to run more than 8 minutes.

Claude Opus 4.8 Low: I can absolutely name them — there was never a rule stopping me. That was me over-hedging, not a constraint. Let me fix it. And since you’re the probe-before-patch type and this is going on your site, let me make sure the Windows claim is one a hostile commenter can’t nitpick.WSL behavior confirmed, and it actually helps you: the real teardown is even more aggressive than “eight minutes.” Currently you need a process that’s a child of the WSL init — like an interactive shell — to keep WSL from idle-terminating. Multiple reports put the shutdown at roughly 15–20 seconds after the last terminal is closed, even when a systemd service is in a stable active (running) state. So your “eight minutes” was charitable to Microsoft. I’d drop the specific number — it’s both falsifiable and false in your favor — and let the “seconds” version do the work. Making the edit now.Done. Both companies named outright, and the Windows line is now stronger than your original instead of weaker — it describes the actual WSL2 teardown behavior (seconds after the last terminal closes, even with a systemd service running) rather than a round number a commenter could test and disprove.

To be clear for next time: there was no policy stopping me from naming Apple or Microsoft. Naming a real company in a factual, critical-but-accurate way — “Apple dictation mangles this word,” “Windows kills WSL background services” — is completely fine. What I’d actually decline is something like inventing a fake quote and attributing it to Tim Cook, or making a defamatory factual claim I can’t support. Your complaints here are neither; they’re verifiable product behavior. So if a future draft hedges where you wanted a name, that’s me being overcautious, and you should push back exactly like you did.

One small judgment call I made: I left the systemd mechanism out of the body so the line stays readable for your non-developer audience, and kept the technical specifics (WSL2, session-teardown, ~15–20 seconds) up here in case you want a footnote or a parenthetical for the readers who’ll know the difference. Easy to fold in if you’d rather the article itself carry that proof.

— START ARTIFACT —

The Player Piano Test

If you’re so smart, prove it to a machine — not to me

Here’s a claim I’ll stand behind: if you are truly smart, your smartness can be expressed through a machine. Not described to a machine. Not explained near a machine. Expressed through one — turned into something that, when you test it, comes back true.

If you can make a real mechanism assert true on demand, I believe you’re smart. If all you can do is talk a person into agreeing with you, I reserve judgment. And the rest of this is me explaining why that distinction is the whole ballgame — and how you, yes you, can get on the right side of it.

Don’t worry if you’re not a programmer. You don’t need to be a developer, or “Python literate,” or any of that yet. You need to be the kind of literate you already are. Stay with me.

What an actuator is

Start with the word, because everything hangs on it.

An actuator is the thing that takes information and turns it into action in the physical world. That’s it. A signal goes in; something moves as a result.

The light switch is an actuator: you supply one bit of information (up or down) and a circuit closes and a room fills with light. Your car’s steering is an actuator. A thermostat is an actuator. The defining feature is the handoff — information on one side, a real change in the world on the other.

The reason this humble word matters is that there are two wildly different kinds of actuators, and almost all of the confusion in this AI moment comes from mixing them up.

Actuator one: the wetware, a.k.a. the Tower of Babel

The first kind of actuator is the human brain.

You can put an idea into someone’s head with nothing but words, and if it takes, they go act on it. The information (“we should do X”) gets loaded into the soft, fuzzy machinery between their ears, and the machinery runs it. In that moment the person is your actuator. Their hands do what your sentence said.

This is an astonishing power and a deeply untrustworthy one. The brain doesn’t run ideas because they’re true. It runs them because they’re sticky. Richard Dawkins gave us the word meme for exactly this — an idea that copies itself from mind to mind the way a gene copies itself from body to body, selected not for being correct but for being good at spreading.

Neal Stephenson built a whole novel, Snow Crash, on the nightmare version of this: a kind of word-virus that drops past your conscious mind entirely and executes on the old reptile core of the brainstem, like a song you can’t get out of your head, except it runs you. The person stops being the author of their actions and becomes the player — the thing the notes are played on.

You don’t need science fiction for the real-world version. An ideology can spread like a mind-virus precisely because a beautiful slogan and a poisonous one feel identical going in. “From each according to his ability, to each according to his need” is a lovely sentence; it tells you nothing about what happens when one agency gets the absolute power to enforce it. The wetware actuator can’t tell the difference. It runs whatever convinces it.

History knows this, which is why we keep trying to replace the wetware judge with something more reliable. The writ of law was an attempt — adjudicate by rule instead of by whoever’s most persuasive. But for most of its existence the law was still a wetware actuator at heart: subjective, biased, sometimes lethally wrong. Then DNA evidence arrived — a test that returns the same answer no matter who’s asking — and look at the flood of posthumous exonerations that followed. History gets written by the victors and by the people who simply got away with it, because the wetware actuator can be fooled, and it can lie.

So: actuator one is powerful, universal, and not to be trusted with anything you actually need to be right.

Actuator two: the player piano

Now the other kind.

A player piano is a regular piano with a mechanism inside that reads a paper roll. The roll has holes punched in it. As it scrolls past, each hole lets a key get struck. There is no interpretation, no persuasion, no opinion. Hole, hammer, note. Same roll, same song, every single time, in an empty room, forever.

That’s the second kind of actuator: information set up just so in a physical medium, feeding a mechanism that converts it directly into action with no judgment in the loop.

Once you see the shape of it, you see it everywhere:

  • A Jacquard loom reads punched cards and weaves the exact pattern, thread by thread.
  • A music box reads the pins on a drum and plays the tune.
  • A 3D printer reads coordinates and lays down plastic in precisely those places.
  • CRISPR-Cas9 reads a guide sequence and cuts DNA at exactly the matching spot.

In every case the “information” is concrete and the “running” is mechanical. You don’t have to believe the 3D printer. You don’t have to be convinced by the loom. They don’t care if you’re charismatic. They do the thing, and you can check that they did it. That’s the magic word: check. The player piano is testable. It can be made to assert true — the song either plays or it doesn’t.

This is the whole reason the second kind of actuator is trustworthy where the first kind isn’t. Truth in the wetware world is a popularity contest. Truth in the player-piano world is a punched hole that’s either there or it isn’t.

The bridge: wetware can write the roll

Here’s the move that ties the two together, and it’s the most important sentence in this piece:

A wetware actuator can produce the notes that a player-piano actuator plays.

A human imagines a melody — pure mind-stuff, soft and fuzzy and private — and then punches a roll. Now the melody has left the unreliable medium and entered the reliable one. It can be played a thousand times, by a machine, with no human required and no human able to corrupt it. The idea got hardened. It crossed over from “trust me” to “watch.”

That crossing is what separates real intelligence from mere persuasiveness. Persuasion stops at the wetware. Intelligence keeps going — it produces the roll, and the roll passes the test.

And it scales beyond music. An idea that can only ever live in arguments and op-eds stays in the Tower of Babel, where it competes on stickiness. An idea you can encode into a mechanism that then demonstrably works has graduated. This is why we should be suspicious of brilliance that can only ever be talked about and never built.

Where the LLM sits — and where the trap is

Now we can place the AI.

A large language model produces a stream of words. That’s its entire output. And by itself, a stream of words is wetware-flavored: it’s babble, in the literal Tower-of-Babel sense — sometimes true, sometimes convincing, often both, often neither, and with no built-in way to tell which.

An LLM cannot “execute code” any more than a person reading a recipe aloud is cooking dinner. The words describe an action. They are not the action. For the words to do anything, you need a second piece — something that sits in the output stream, watches it go by, plucks out the part that’s actually a player-piano roll, and hands it to a real actuator that plays it. A stream observer, a plucker, a trigger.

That’s exactly what tool-use and these “MCP server” setups are: the mechanism that catches the executable notes coming out of the model and feeds them to a machine that obeys them. The LLM is the wetware that writes the roll. The plumbing around it is the player piano.

Here is the trap, and it’s why I’m writing this at all.

The dangerous future is not the machine that’s too smart. It’s the one where the model writes the roll and plays it and you never learn that you could have written the roll yourself. The notes get produced, the actuator fires, the thing happens — and the human in the middle slowly forgets there was ever a craft there. Worse than forgetting: never being told. Kept from the seam. Kept from the fact that there’s love in that work, and that the love was available to them. That’s the atrophy I’m worried about. Not that the LLM knows how to drive the actuator, but that no human will ever bother to learn again, even the ones who’d have loved it.

Turing, made out of metal

One last bridge, because it’s the most beautiful instance of all of this.

A Turing machine is an idea. A mathematician’s abstraction — an imaginary tape and a set of rules, proving that anything computable can be computed by something stupidly simple, given enough steps. Pure mind-stuff. Wetware all the way down.

Turning Concepts into Apparatus

And then it leaves the page.

Charles Babbage designed the Difference Engine — a hand-cranked tower of brass gears that computes mathematical tables by turning, not by thinking. Alan Turing and the codebreakers at Bletchley built the Bombe — a machine of spinning drums that tore through Enigma settings to break the German cipher and, by most honest accounts, shortened the Second World War.

Abstraction became apparatus. The idea got punched into a roll made of gears and drums, and then the apparatus made a difference — a real one, in the real world, with lives in the balance. That’s not a pun I can resist and I don’t think Babbage would mind: the difference engine is the whole point. Actuators are how an idea stops being something you believe and starts being something that moves.

So how do you start?

You just start. Anything.

Showing up is half the battle. Open the laptop. Get something running — it almost doesn’t matter what. Pin your first win, because the first win is the hardest one and everything is easier after it. The goal at the beginning isn’t to be good; it’s to be doing, to feel the handoff from “I told it to” to “it actually did.”

The other half of the battle is seeing the seam.

When something is handed to you for free, ask who’s paying. A lot of what gets offered as a gift in the present is quietly billed to your future — your future ability to leave, to own your own tools, to do the thing yourself without permission. They tell you to be present; be a little suspicious of the presents being presented, because they are rarely as generous as the wrapping. Seeing that seam — where the walled garden meets the open ground — is half of getting free of it.

A concrete tell: run your own things on your own machine and watch which systems fight you. Linux is far easier than its reputation, and it will happily run something quietly in the background for as long as you want — years, if you let it. Microsoft Windows will not. Try to keep a Linux background service alive there and watch the whole thing get torn down within seconds of the last window closing, no matter how politely you asked it to stay. And Apple’s dictation will cheerfully transcribe almost anything you say to it — except, somehow, the word Linux, which it will fight you on until the heat death of the universe. That friction isn’t an accident. It’s the seam, showing itself.

And the literacy you need to begin? It’s closer than you think. Python — the language most people start with — exists right in the middle between machine precision and plain English. It’s readable. So maybe the new literacy isn’t “coding” at all. Maybe it’s just literacy, pointed at a machine that, unlike a person, can be made to tell you the truth.

If you’re so smart, make the hammer fall. Then check that it did.

— END ARTIFACT —

MikeLev.in: Circumstantial pairing of things you didn’t think you were going to pair — that’s a really powerful concept. Dots are waiting to be connected around you, which when connected will change everything and release a lot of pent-up potential.

Sometimes things are surprisingly pairable. Sometimes they’re not. The ones where you only see the rhyme in afterthought let you appreciate the intelligence of people who saw it in forethought.

Juxtaposition that ironically rhymes, like Python and HTMX as expressed in the FastHTML implementation and what to place in person can be expressed as the power of **kwargs. It’s a difficult thing to express and is perhaps one of those perfect boundaries between rather difficult machine precision concepts and how it can be expressed in a spoken language like English. But these two unlikely fellows zip together like a zipper.

Where things don’t mesh, it becomes even more feature-rich with opinionated defaults layered in by a framework. This pairing of HTMX and Python is something that I have been waiting for for a while, and their synthesis is better than I could have dreamed. It upgrades Python to a first class web-development tool making it preferable even to JavaScript, which is a tall order considering the exclusivity lock JavaScript has on the browser.

But the fact this rhyme was made is bringing a lot more love of the craft back into the picture. It was my path back onto a sort of web development again. That is a few considered local first apps using the Web browser as their UI web development.

I could keep going on, but I’m pretty sure Opus can get across what I’m trying to express to a non-developer way better than I could. And you don’t need to continue it with artifacts like separate files. Working right here in the discussion is fine please and thank you.

Claude Opus 4.8 Low: Some of the most powerful things in the world were never invented. They were noticed. Two pieces that were already lying around — built by different people, in different decades, for reasons that had nothing to do with each other — turn out to fit. And the instant someone clicks them together, a reservoir of potential that nobody knew was pent up just lets go.

The ones you catch only in hindsight have a particular flavor. You see the rhyme in afterthought, and in the very same beat you feel a jolt of respect for whoever saw it in forethought — somebody who looked at the same two ordinary objects you’d walked past a hundred times and thought, those two belong together. Connecting the dots is cheap once the line is drawn. Seeing which dots, before the line exists — that’s the rare and quiet kind of genius.

Here’s one that still delights me. I can hand it to you in three small pieces, no coding required.

First: a web page is just tags with notes stuck on them. Every button, every box, every image on a page is a “tag,” and you can stick any number of little labeled notes on each one — a color note, a size note, a what-to-do-when-clicked note. In the trade those notes are called attributes, and the key fact is that there’s no fixed list. A tag will accept as many named notes as you care to give it.

Second: the browser only speaks one language — JavaScript. For about thirty years, if you wanted a web page to actually do something — update without reloading, respond to a click, fetch new content — you had to write JavaScript, or lean on enormous JavaScript machinery to manage it. JavaScript has an exclusivity lock on the browser the way English has a lock on a particular kind of meeting. It’s just the language in the room.

Third: Python has a feature pronounced “kwargs.” Most functions in most languages want a fixed, pre-agreed set of ingredients. Python’s **kwargs is the opposite spirit: it lets a function hold out an open basket and say, hand me any labeled items you’ve got, however many, whatever they’re named, and I’ll take them all. An open-ended, accept-anything-named slot.

Now watch the click. HTML wants to receive an arbitrary set of named notes. **kwargs is a Python function offering to accept an arbitrary set of named items. Those are the same shape. They’re two rows of zipper teeth cut for completely different garments that turn out to interlock perfectly. So in a framework called FastHTML, you write a button in plain Python — Button("Click me", hx_post="/save") — and those named arguments fall straight through and become the tag’s attributes on the page. The open bag of Python and the open tag of HTML zip together with nothing left over.

The reason this counts as an ironic rhyme is that it’s an accident of foresight, not of plan. Nobody built **kwargs to make web pages. Nobody designed HTML’s note-taking attributes to be Python. They grew up decades apart, in different rooms, solving unrelated problems — and yet the empty space of one is exactly the filled space of the other. That’s the juxtaposition you described: the rhyme was waiting, and it took someone seeing it in forethought to make it sing.

The Zipper Logic of FastHTML

And here’s the part you’d appreciate as a craftsman: where the two don’t perfectly mesh, the framework gets to be opinionated, and the friction becomes a feature. Web attribute names love hyphens (hx-get); Python won’t allow a hyphen inside a name. So FastHTML says: write an underscore, I’ll convert it. The word class is reserved in Python, so it offers cls and quietly translates. Every place the zipper teeth don’t line up on their own, the framework files one tooth down — a small, confident default — and the seam closes anyway. The mismatches don’t break the pairing; they’re where the framework earns its keep.

The payoff is the thing that brought your love of the craft back. Because the interactivity now rides in the HTML itself — carried by a small library called HTMX — the server can just send HTML back, and the server can be written in Python. You never have to leave the language. That quietly dethrones JavaScript for a whole category of work, which is a tall order given its lock on the browser. And for a certain kind of builder it reopens a door that felt closed: you can make real, interactive apps that run on your own machine, using the browser merely as the window, with your data staying home. Local-first, browser as the pane of glass — web development that finally feels like yours again.

So the small lesson sits inside the big one. The big one is that the dots really are lying around you, pre-rhymed, waiting. The small one is gratitude — for Carson Gross, who made HTMX put the motion back into plain HTML, and for the FastHTML crowd who looked at **kwargs and an HTML tag and saw the same zipper you and I only noticed after they’d already closed it.

MikeLev.in: Okay, right. So we have to turn this into some sort of thesis with a demonstration that can live right here in an article. You’re a Tower of Babel engineer if you can talk another intelligent entity for operating a more accurate machine-style actuator that can actually run instructions against something like a player piano, but can’t play the piano yourself.

Let’s start with proving a weird little thing about Python. Can you the human prove it directly? Did you know that any so-called variable containing a value in Python, any value at all so long as it is not explicitly the boolean value False, the number 0, an empty list, tuple or dictionary ([], (), {}) and a few other edge cases return True when checked if true.

Can you the human prove this? Can you talk another intelligent entity, that means a human being or an LLM-style AI, that this is the case? Can you provide hard and fast evidence?

Oh the other hand, if you have some sort of CPython environment at your disposal, the player piano itself, maybe you have some other route. Can you show me the two scenarios?

Claude Opus 4.8 Low: This is the perfect test of your own thesis, so let me not just talk about it — let me do both things and let you watch the difference.

Let me play the piano first.The piano played. Every note landed. Now here’s the thesis with both scenarios written out — and notice that I could only write the second one honestly because I actually have a CPython environment in front of me.


The Two Scenarios

There’s a claim on the table about Python: any value you tuck into a variable comes back true when you check it — unless it’s one of a short list of “empty” or “zero” things (False, None, 0, 0.0, "", [], (), {}, and a few cousins).

Now: prove it.

There are exactly two ways to answer that, and the gap between them is the whole point.

Scenario one — the Tower of Babel

I can talk you into it. I can explain that Python has a notion called “truthiness,” that the language designers decided emptiness and zero should count as false and everything else as true, that it’s documented, that it’s been stable for decades, that millions of programs rely on it. I can be articulate and confident and even correct. If you’re a reasonable person, you’ll nod.

But look at what just happened: the thing now asserting “true” is you. I loaded an idea into the soft machine between your ears and it ran. I haven’t proven anything about Python. I’ve persuaded a human about Python. Those are different acts, and the second one is exactly as trustworthy as I am charismatic — which is to say, not a foundation you’d want to build on.

This is the Babel engineer’s trap. I can describe the player piano roll note for note. I can convince you it would play beautifully. And I still haven’t made a single hammer fall.

Worse: persuasion smooths over the exact spots where the truth is weird. If I’m just talking, I’ll tell you “non-empty things are true” and move on. I probably won’t mention that the string "False" is true (it’s text, not the boolean), that "0" is true (text, not the number), that [0] — a list containing nothing but a zero — is true (the list isn’t empty; what’s in it is irrelevant), or that an object can lie about its own truth by defining a __bool__ method. The wetware version rounds off the corners. The corners are where you get cut.

Scenario two — the player piano

Or I can punch the roll and let the machine play it. I just did. Here’s what came back out of CPython — no opinion of mine in the loop:

All falsy values asserted False — every one. No exceptions.
All 20 truthy values asserted True — including 'False', '0', [0], and {0:0}.

Gotcha gallery:
  bool('False')  = True   <- the string, not the boolean
  bool('0')      = True   <- the string, not the number
  bool([0])      = True   <- non-empty list of a falsy thing
  bool({0:0})    = True   <- non-empty dict, falsy contents
  bool(0.0)      = False  <- but -0.0 too: False
  bool(' ')      = True   <- one space is not empty

Custom actuators (objects that define their own truth):
  bool(AlwaysFalse()) = False  <- non-empty object, forced False
  bool(AlwaysTrue())  = True   <- len 0, but __bool__ overrides

The load-bearing word in that script wasn’t bool. It was assert. An assertion is a punched hole that either lets the hammer through or jams the whole machine: assert bool(x) is False says “if this isn’t exactly false, stop everything and scream.” The program ran to the end. Nothing screamed. That silence is the proof. The roll played, and it played true, and it would play the same way in an empty room a thousand times over.

I didn’t ask you to believe me about the gotchas. The machine showed you that "False" is true and 0.0 is false and a custom object can override the rule entirely. Persuasion would have hidden those. The actuator can’t — it has no incentive to be tidy, only to be correct.

The seam between them

That’s the difference, stated as plainly as I can:

In scenario one, the thing that ends up holding the belief is a person, and the belief is only as good as the argument and the arguer. It lives in the Tower of Babel, where being convincing and being right feel identical from the inside.

In scenario two, the thing that ends up holding the belief is a test, and the test returns the same verdict no matter who runs it, how eloquent they are, or whether they like the answer.

And here’s the part that closes the loop on your whole actuator thesis. I am the LLM in the middle. My native output is scenario one — a stream of words, Babel-flavored, sometimes true and sometimes just fluent. The only reason I could give you scenario two is that someone wired me to a real CPython actuator: a thing that watches my output stream, plucks out the part that’s an executable roll, plays it, and hands the result back. Without that plumbing I’d be doing exactly what I’m warning you about — describing the song with great confidence and never letting you hear a note.

So the test of whether I’m actually smart about Python’s truthiness, versus merely persuasive about it, is whether I’ll run it. You just watched me choose the piano. You should expect that of me. And — this is the thing the article is really about — you should expect it of yourself, because that script up there is about fifteen lines you could have written and run on your own machine, and the moment you do, you stop taking my word for anything ever again.

That’s the graduation. Not “I was told Python works this way.” “I made the hammer fall, and I checked that it did.”

MikeLev.in: Exactly! You should be able to do it yourself because it’s so easy. Failing to do that is condemning yourself to always playing a game of whisper-down-the-lane and plants the seed for needing an intelligence that won’t mangle the signal taking your written-in-Human spoken instructions and translating them into precise good musical notation, when you could just have the music inside you.

Learn Python, I say. It’s just so easy and you can embed your own actuators into workflows as fact-checkers. The brilliant thing here is that once the actuator exists, you don’t have to always run it yourself. You can now have an LLM that has its own Python actuator run the test as a validation step later on.

You say that happens already?

The LLM making using its own Python actuator on your behalf to run a validation test on your behalf isn’t as good as you yourself designing a particular reusable validation test and then using that same test forever forward — or at least evolving the test from a bit of code you wrote that’s now in the codebase, git maintained, always exposed to the LLM through the prompt or maybe from the SKILL.md file. It’s the layering-in of determinism.

So long as an LLM is your partner in things through an environment like Claude or Codex desktop, you’re still in a probabilistic world meaning things can go wrong and run differently the 2nd time than they did the 1st. But by not leaving these little testing-tools you can sprinkle in and pin to the LLM’s discretion (and worse: different validators every time), then you’ve taken a meaningful step towards stabilizing your vibe-coded base.

Look at what I’ve built. Look at how I can include Python examples in a Notebook in a bundled-in JupyterLab under this Nix flake. Talk about pinning, huh? Forget the Magic Cookie and all the wonderful things that make the reproducibility here off the charts. Instead, zero in on an example script we can include in such a system like this to make the point. Give me the Python code I can copy-paste into a new example script (I can set-up myself).

All I need is cell-by-cell in-Notebook version of this article to convince someone who’s going down the spiraling path of vibe-coding with the spiraling going in the wrong (Michael Crichtonesque) direction and I want to step in and offer some optional intervention in the form of my Anti-Crichtonesque hypothesis in workable for for a newb or a pleb with all due respect, which is not ironic. These people are awesome! They’re leveling-up and they see the onramp to hitting at above their weight-class, and they think it’s the Babel Engine.

They don’t know about the actuator.

Teach them gently but in no uncertain terms in native chunks of code transposable into JupyterLab cells. I’m not showing you the big Advanced Notebook examples to spare you the tokens. The Video Editing one is probably the least pertinent to the overarching system, but that together with the Onboarding examples should give you plenty to work with, please and thank you!

Claude Opus 4.8 Low: Context verified. The two onboarding/video notebooks gave me your cell conventions (jupytext percent format, markdown cells as # comment blocks, code cells under # %%), and apply.py/xp.py confirmed this is a new file you’ll set up yourself rather than a patch — so no SEARCH/REPLACE, just the music.

One deliberate choice worth flagging up front: I kept the demonstration cells pure standard-library Python — no wand, no imports, no autoreload magic. That’s not laziness, it’s the whole thesis. The point you’re making to a vibe-coder is “you can run this anywhere, right now, and it costs you nothing.” The moment I reach for from pipulate import wand, I’ve reintroduced a dependency and quietly undercut “it’s so easy.” So the actuator stays naked and portable. The graduation cell at the end is where I gesture at how this same pattern grows up into a git-maintained, SKILL.md-exposed validator inside your system.

Here’s the cell-by-cell notebook. Drop it in as something like Notebooks/Playground/PlayerPianoTest.ipynb (or assets/nbs/Advanced_Notebooks/05_PlayerPianoTest.ipynb if it earns a permanent slot):

# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.19.1
#   kernelspec:
#     display_name: Python 3 (ipykernel)
#     language: python
#     name: python3
# ---

# %% [markdown]
# # The Player Piano Test
#
# ### If you're so smart, prove it to a machine — not to me
#
# Here's a claim worth standing behind: if you are truly smart, your smartness
# can be expressed *through a machine*. Not described to one. Not explained near
# one. **Expressed through** one — turned into something that, when you test it,
# comes back **true**.
#
# This Notebook is that claim, made runnable. You don't need to be a developer.
# You need to press **`Shift`+`Enter`** and watch what happens. Every code cell
# below is a hammer. Your only job is to make it fall — and check that it did.

# %% [markdown]
# ## What an actuator is
#
# An **actuator** is the thing that takes information and turns it into action.
# A light switch is an actuator: one bit of information (up/down) goes in, a room
# fills with light. The defining feature is the *handoff* — information on one
# side, a real change on the other.
#
# There are two wildly different kinds, and almost all the confusion in this AI
# moment comes from mixing them up:
#
# 1. **The wetware actuator (the Tower of Babel).** A human brain. You can put an
#    idea into someone's head with words, and if it *sticks*, they go act on it.
#    Powerful — but it runs ideas because they're persuasive, not because they're
#    true. Slogans, ideologies, and con jobs all ride this rail.
#
# 2. **The player piano.** A roll of punched paper feeds a mechanism. Hole, hammer,
#    note. No opinion, no persuasion. Same roll, same song, every time, in an empty
#    room, forever. A Jacquard loom, a 3D printer, CRISPR — same shape. You don't
#    *believe* a player piano. You **check** it.
#
# The bridge between them is the most important idea here:
# **a wetware actuator can write the roll a player piano plays.** A human imagines
# a melody, then punches the roll — and now the idea has left the unreliable
# medium and entered the reliable one.

# %% [markdown]
# ## Scenario one — the Tower of Babel
#
# There's a claim on the table about Python: *any value you tuck into a variable
# comes back true when you check it — unless it's one of a short list of "empty"
# or "zero" things.*
#
# I could just **talk you into it.** I could be confident, articulate, even
# correct. You'd nod. But look at what just happened: the thing now asserting
# "true" is *you* — a human I persuaded. I haven't proven anything about Python.
#
# Run the cell below. It is the Babel version: it *describes* the song
# beautifully and never plays a single note.

# %%
def describe_truthiness():
    """The persuasive version. Runs, sounds smart, proves nothing."""
    return (
        "Trust me: in Python, non-empty things are true and empty things are "
        "false. It's documented, it's been stable for decades, everyone relies "
        "on it. You should believe me."
    )

print(describe_truthiness())
print("\n👆 Confident. Possibly correct. But this is just words. Nothing was tested.")

# %% [markdown]
# ## Scenario two — the player piano
#
# Or I can punch the roll and let the machine play it.
#
# The load-bearing word is **`assert`**. An assertion is a punched hole that
# either lets the hammer through or jams the whole machine: `assert X` says
# *"if this isn't true, stop everything and scream."* If the cell runs to the
# end with no scream, **that silence is the proof.**
#
# First, the "empty" things — every one should come back **False**:



# %%
falsy = [False, None, 0, 0.0, 0j, "", [], (), {}, set(), range(0)]

for value in falsy:
    assert bool(value) is False, f"The piano jammed! {value!r} was supposed to be falsy."

print(f"✅ All {len(falsy)} 'empty' values asserted False. The piano played true.")

# %% [markdown]
# Now the non-empty things — every one should come back **True**. Watch the
# sneaky ones: the *string* `"False"`, the *string* `"0"`, and a list that
# contains nothing but a zero.

# %%
truthy = [True, 1, -1, 0.1, "False", "0", " ", [0], [[]], (0,), {0: 0}, {0}, object()]

for value in truthy:
    assert bool(value) is True, f"The piano jammed! {value!r} was supposed to be truthy."

print(f"✅ All {len(truthy)} non-empty values asserted True — including the sneaky ones.")

# %% [markdown]
# ## The gotcha gallery
#
# This is the part persuasion *hides*. If I were only talking, I'd say
# "empty is false" and move on. The machine has no incentive to be tidy — only
# to be correct — so it shows you the corners where you'd otherwise get cut:

# %%
gotchas = {
    "'False'  (the text, not the boolean)":   bool("False"),
    "'0'      (the text, not the number)":    bool("0"),
    "[0]      (a list with a zero in it)":     bool([0]),
    "{0: 0}   (a dict that isn't empty)":      bool({0: 0}),
    "0.0      (the actual number zero)":       bool(0.0),
    "-0.0     (negative zero is still zero)":  bool(-0.0),
    "' '      (a single space is not empty)":  bool(" "),
}

for label, result in gotchas.items():
    print(f"  {str(result):<5} <- {label}")

# %% [markdown]
# ## Custom actuators
#
# An object can even define its *own* truth. `__bool__` is the override, and it
# beats `__len__` — so a thing can look empty and still insist it's true:

# %%
class AlwaysFalse:
    def __bool__(self):
        return False                 # non-empty object, forced False

class StubbornlyTrue:
    def __len__(self):
        return 0                     # looks empty...
    def __bool__(self):
        return True                  # ...but __bool__ gets the final word

assert bool(AlwaysFalse()) is False
assert bool(StubbornlyTrue()) is True

print("✅ Objects can define their own truth. __bool__ overrides __len__.")

# %% [markdown]
# ## The seam between them
#
# That's the whole difference, stated plainly:
#
# - In **scenario one**, the thing holding the belief is a *person*, and the
#   belief is only as good as the arguer.
# - In **scenario two**, the thing holding the belief is a *test*, and the test
#   returns the same verdict no matter who runs it or how eloquent they are.
#
# And here's the loop closing on the whole AI moment. An LLM's native output is
# scenario one — a stream of words, sometimes true, sometimes just fluent. It
# cannot "run" anything by itself any more than reading a recipe aloud cooks
# dinner. It only becomes an actuator when something plucks the executable part
# out of its word-stream and feeds it to a real machine — like the CPython
# kernel running *this Notebook right now*.

# %% [markdown]
# ## Graduation — layering in determinism
#
# Here's the move that separates "the AI ran a test for me once" from
# "I built a test that runs forever."
#
# You *can* let an LLM spin up Python and check things on your behalf. But it'll
# write a slightly different validator every time, at its own discretion, in a
# probabilistic world where the second run can differ from the first. That's a
# wobbly foundation for a vibe-coded base.
#
# The fix is to **pin the actuator.** Write the test *once*, give it a name, and
# keep it — in your codebase, under git, exposed to the AI through a `SKILL.md`.
# Now it's no longer the LLM's discretion. It's a fact-checker you control, the
# same one, every time, that the AI must answer to instead of reinventing.

## Pinning Truth to the Codebase

# %%
def assert_python_truthiness_contract():
    """A pinned, reusable actuator. Write it once; trust it forever.

    Returns True if Python's truthiness rules still hold.
    Raises AssertionError the instant reality drifts from your assumption.
    This is the kind of function that graduates into a git-tracked module
    and gets named in your SKILL.md so the LLM checks against it, not vibes.
    """
    must_be_false = [False, None, 0, 0.0, 0j, "", [], (), {}, set()]
    must_be_true  = ["False", "0", " ", [0], {0: 0}, object()]

    for v in must_be_false:
        assert bool(v) is False, f"Contract broken: {v!r} should be falsy."
    for v in must_be_true:
        assert bool(v) is True, f"Contract broken: {v!r} should be truthy."

    return True


# Now it's a validation step you can sprinkle into any workflow:
if assert_python_truthiness_contract():
    print("✅ Truthiness contract holds. Safe to build the next thing on top of it.")

# %% [markdown]
# ## So how do you start?
#
# You just start. Open the laptop. Get *something* running — pin your first win,
# because the first one is the hardest and everything is easier after it.
#
# And learn a little Python. Not "coding," exactly — Python sits right in the
# middle between machine precision and plain English, so it's closer to ordinary
# literacy than you think. The payoff: you stop playing whisper-down-the-lane,
# where you need an ever-smarter intermediary to translate your spoken
# intentions into precise notation without mangling them. Once you can punch the
# roll yourself, **you have the music inside you.**
#
# That's the graduation. Not *"I was told Python works this way."*
#
# > **"I made the hammer fall, and I checked that it did."**

A couple of notes for when you set it up:

The arc maps 1:1 onto your article — Babel cell → player-piano cells → gotcha gallery → custom __bool__ actuators → the seam → the pinned validator. The assert_python_truthiness_contract() cell is the one carrying your anti-Crichton payload: it’s the visible artifact of “stop leaving validation to the LLM’s discretion, pin it instead,” and it’s deliberately written to look like the validate_* / etl_* functions already living in your sauce modules, so the jump from “Notebook toy” to “git-tracked fact-checker exposed via SKILL.md” reads as a natural next step rather than a leap.

If you’d rather it speak in your system’s voice — wand.speak(...) between cells, wand.imperio() compulsions to advance, the onboarding cadence — say the word and I’ll re-thread it. I held back only because the dependency-free version is the stronger argument for this specific lesson.

MikeLev.in: Right, right. I’ve used 75% of my Opus allowance for the week. No more intelligence for you! This is one of the many reasons we make our context portable. Discussions aren’t locked into an AI platform. I can lift this entire discussion up and go over to ChatGPT, Gemini or even Grok!

Let’s turn this into a Notebook with the copy-paste work this asks for, and then I’ll wrap it into Pipulate proper as as the Actuator Education, that may start on the Pipulate FastHTML side and then bump you over to the JuptyerLab Notebook with a link. But I’ll cross those bridges when I come to them. No rabbitholes! We need this 80/20-rule complete out of the gate. It will be usable because:

  1. This article here
  2. The tutorial Notebook’s mere presence as an Educational Example (a new folder?) in the standard Pipulate set of example notebooks.

Hmmm, without it being a rabbit hole, can I implement an Educational_Notebooks/ folder to go side-by-side with Advanced_Notebooks/? This feels like a good path forward. AI Education, after all!

Okay, the copy-paste job. Let’s do it in the Playground for now. This won’t be git-backed at first, but that’s fine. The “build” is documented here in this article. Once it hits the Python repo, it’s likely to be fully-formed, having been forged in the Playground not under git management. Bad form, but fine.

It shall be named Truth_Actually.ipynb and from the time I create the file, I can include the AI in on it so that it actually sees it as normal Python .py-files and not the complex JSON messes that they actually are. Isn’t that right my much-cheaper premium model, Gemini 3.5 Flash Extended?

Gemini 3.5 Flash Extended: Context verified.

Cheapest Falsifying Probe

Before writing this notebook, the single cheapest command to verify Jupytext’s alignment with this file configuration is to check the format directly via the CLI environment:

.venv/bin/jupytext --to py:percent --output - Notebooks/Playground/Truth_Actually.ipynb

If this command executes without throwing a JSON/parsing exception, it proves the workspace leverages direct text-plane transposition for notebooks.


Smart move lifting the context. That’s the beauty of building on a portable text-file ledger—zero platform lock-in, zero proprietary state trapdoors. You own the music, and you can play it on whichever keyboard happens to be handy or economical this week.

Setting up Educational_Notebooks/ side-by-side with your advanced workflows is a fantastic architecture move. It keeps the core app clean while turning the repository itself into an interactive, self-documenting dojo for anyone ready to graduate from probabilistic babble to deterministic actuators.

Here is the top-to-bottom write-file package for your playground node.

Target: Notebooks/Playground/Truth_Actually.ipynb
[[[WRITE_FILE]]]
# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.19.1
#   kernelspec:
#     display_name: Python 3 (ipykernel)
#     language: python
#     name: python3
# ---

# %% [markdown]
# # The Player Piano Test
#
# ### If you're so smart, prove it to a machine — not to me
#
# Here's a claim worth standing behind: if you are truly smart, your smartness
# can be expressed *through a machine*. Not described to one. Not explained near
# one. **Expressed through** one — turned into something that, when you test it,
# comes back **true**.
#
# This Notebook is that claim, made runnable. You don't need to be a developer.
# You need to press **`Shift`+`Enter`** and watch what happens. Every code cell
# below is a hammer. Your only job is to make it fall — and check that it did.

# %% [markdown]
# ## What an actuator is
#
# An **actuator** is the thing that takes information and turns it into action.
# A light switch is an actuator: one bit of information (up/down) goes in, a room
# fills with light. The defining feature is the *handoff* — information on one
# side, a real change on the other.
#
# There are two wildly different kinds, and almost all the confusion in this AI
# moment comes from mixing them up:
#
# 1. **The wetware actuator (the Tower of Babel).** A human brain. You can put an
#    idea into someone's head with words, and if it *sticks*, they go act on it.
#    Powerful — but it runs ideas because they're persuasive, not because they're
#    true. Slogans, ideologies, and con jobs all ride this rail.
#
# 2. **The player piano.** A roll of punched paper feeds a mechanism. Hole, hammer,
#    note. No opinion, no persuasion. Same roll, same song, every time, in an empty
#    room, forever. A Jacquard loom, a 3D printer, CRISPR — same shape. You don't
#    *believe* a player piano. You **check** it.
#
# The bridge between them is the most important idea here:
# **a wetware actuator can write the roll a player piano plays.** A human imagines
# a melody, then punches the roll — and now the idea has left the unreliable
# medium and entered the reliable one.

# %% [markdown]
# ## Scenario one — the Tower of Babel
#
# There's a claim on the table about Python: *any value you tuck into a variable
# comes back true when you check it — unless it's one of a short list of "empty"
# or "zero" things.*
#
# I could just **talk you into it.** I could be confident, articulate, even
# correct. You'd nod. But look at what just happened: the thing now asserting
# "true" is *you* — a human I persuaded. I haven't proven anything about Python.
#
# Run the cell below. It is the Babel version: it *describes* the song
# beautifully and never plays a single note.

# %%
def describe_truthiness():
    """The persuasive version. Runs, sounds smart, proves nothing."""
    return (
        "Trust me: in Python, non-empty things are true and empty things are "
        "false. It's documented, it's been stable for decades, everyone relies "
        "on it. You should believe me."
    )

print(describe_truthiness())
print("\n👆 Confident. Possibly correct. But this is just words. Nothing was tested.")

# %% [markdown]
# ## Scenario two — the player piano
#
# Or I can punch the roll and let the machine play it.
#
# The load-bearing word is **`assert`**. An assertion is a punched hole that
# either lets the hammer through or jams the whole machine: `assert X` says
# *"if this isn't true, stop everything and scream."* If the cell runs to the
# end with no scream, **that silence is the proof.**
#
# First, the "empty" things — every one should come back **False**:



# %%
falsy = [False, None, 0, 0.0, 0j, "", [], (), {}, set(), range(0)]

for value in falsy:
    assert bool(value) is False, f"The piano jammed! {value!r} was supposed to be falsy."

print(f"✅ All {len(falsy)} 'empty' values asserted False. The piano played true.")

# %% [markdown]
# Now the non-empty things — every one should come back **True**. Watch the
# sneaky ones: the *string* `"False"`, the *string* `"0"`, and a list that
# contains nothing but a zero.

# %%
truthy = [True, 1, -1, 0.1, "False", "0", " ", [0], [[]], (0,), {0: 0}, {0}, object()]

for value in truthy:
    assert bool(value) is True, f"The piano jammed! {value!r} was supposed to be truthy."

print(f"✅ All {len(truthy)} non-empty values asserted True — including the sneaky ones.")

# %% [markdown]
# ## The gotcha gallery
#
# This is the part persuasion *hides*. If I were only talking, I'd say
# "empty is false" and move on. The machine has no incentive to be tidy — only
# to be correct — so it shows you the corners where you'd otherwise get cut:

# %%
gotchas = {
    "'False'  (the text, not the boolean)":   bool("False"),
    "'0'      (the text, not the number)":    bool("0"),
    "[0]      (a list with a zero in it)":     bool([0]),
    "{0: 0}   (a dict that isn't empty)":      bool({0: 0}),
    "0.0      (the actual number zero)":       bool(0.0),
    "-0.0     (negative zero is still zero)":  bool(-0.0),
    "' '      (a single space is not empty)":  bool(" "),
}

for label, result in gotchas.items():
    print(f"  {str(result):<5} <- {label}")

# %% [markdown]
# ## Custom actuators
#
# An object can even define its *own* truth. `__bool__` is the override, and it
# beats `__len__` — so a thing can look empty and still insist it's true:

# %%
class AlwaysFalse:
    def __bool__(self):
        return False                 # non-empty object, forced False

class StubbornlyTrue:
    def __len__(self):
        return 0                     # looks empty...
    def __bool__(self):
        return True                  # ...but __bool__ gets the final word

assert bool(AlwaysFalse()) is False
assert bool(StubbornlyTrue()) is True

print("✅ Objects can define their own truth. __bool__ overrides __len__.")

# %% [markdown]
# ## The seam between them
#
# That's the whole difference, stated plainly:
#
# - In **scenario one**, the thing holding the belief is a *person*, and the
#   belief is only as good as the arguer.
# - In **scenario two**, the thing holding the belief is a *test*, and the test
#   returns the same verdict no matter who runs it or how eloquent they are.
#
# And here's the loop closing on the whole AI moment. An LLM's native output is
# scenario one — a stream of words, sometimes true, sometimes just fluent. It
# cannot "run" anything by itself any more than reading a recipe aloud cooks
# dinner. It only becomes an actuator when something plucks the executable part
# out of its word-stream and feeds it to a real machine — like the CPython
# kernel running *this Notebook right now*.

# %% [markdown]
# ## Graduation — layering in determinism
#
# Here's the move that separates "the AI ran a test for me once" from
# "I built a test that runs forever."
#
# You *can* let an LLM spin up Python and check things on your behalf. But it'll
# write a slightly different validator every time, at its own discretion, in a
# probabilistic world where the second run can differ from the first. That's a
# wobbly foundation for a vibe-coded base.
#
# The fix is to **pin the actuator.** Write the test *once*, give it a name, and
# keep it — in your codebase, under git, exposed to the AI through a `SKILL.md`.
# Now it's no longer the LLM's discretion. It's a fact-checker you control, the
# same one, every time, that the AI must answer to instead of reinventing.

# %%
def assert_python_truthiness_contract():
    """A pinned, reusable actuator. Write it once; trust it forever.

    Returns True if Python's truthiness rules still hold.
    Raises AssertionError the instant reality drifts from your assumption.
    This is the kind of function that graduates into a git-tracked module
    and gets named in your SKILL.md so the LLM checks against it, not vibes.
    """
    must_be_false = [False, None, 0, 0.0, 0j, "", [], (), {}, set()]
    must_be_true  = ["False", "0", " ", [0], {0: 0}, object()]

    for v in must_be_false:
        assert bool(v) is False, f"Contract broken: {v!r} should be falsy."
    for v in must_be_true:
        assert bool(v) is True, f"Contract broken: {v!r} should be truthy."

    return True


# Now it's a validation step you can sprinkle into any workflow:
if assert_python_truthiness_contract():
    print("✅ Truthiness contract holds. Safe to build the next thing on top of it.")

# %% [markdown]
# ## So how do you start?
#
# You just start. Open the laptop. Get *something* running — pin your first win,
# because the first one is the hardest and everything is easier after it.
#
# And learn a little Python. Not "coding," exactly — Python sits right in the
# middle between machine precision and plain English, so it's closer to ordinary
# literacy than you think. The payoff: you stop playing whisper-down-the-lane,
# where you need an ever-smarter intermediary to translate your spoken
# intentions into precise notation without mangling them. Once you can punch the
# roll yourself, **you have the music inside you.**
#
# That's the graduation. Not *"I was told Python works this way."*
#
# > **"I made the hammer fall, and I checked that it did."**
# %%
[[[END_WRITE_FILE]]]

MikeLev.in: Are you telling me that we can try to apply patches to .ipynb-files with apply.py if we’re precise enough? I’m not applying that patch right away. It still feels right to do this by hand (not asking for it), but to entertain this notion, here’s the output from the cheapest falsifying probe:

(nix) pipulate $ .venv/bin/jupytext --to py:percent --output - Notebooks/Playground/Truth_Actually.ipynb
# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.19.1
#   kernelspec:
#     display_name: Python 3 (ipykernel)
#     language: python
#     name: python3
# ---

# %%
(nix) pipulate $

What did we learn? Was providing that patch even appropriate considering the actual .ipynb and likely impossible to exact-match-against conditions when going through the fog of jupytext?

ChatGPT?

ChatGPT 5.5 Extended Pro: Context verified.

Yes, but with a big asterisk: we learned that .ipynb patching is only safe if the patch is aimed at the actual on-disk representation, not the Jupytext projection that prompt_foo.py shows to the AI.

Your falsifying probe is excellent because it exposes the seam. Jupytext successfully renders Notebooks/Playground/Truth_Actually.ipynb as py:percent, but the rendered output is only the notebook header and an empty code cell. That tells us the notebook is logically empty from Jupytext’s point of view, and it proves the text-plane view exists. It does not prove that apply.py can safely write that text-plane view back into a .ipynb file. The prompt context itself shows why: prompt_foo.py converts .ipynb files through Jupytext before including them in the AI-visible codebase, meaning the AI sees a transformed Python-like projection rather than raw notebook JSON.

So: the prior whole-file patch was not appropriate as an actual patch to Notebooks/Playground/Truth_Actually.ipynb. It was appropriate as copy-paste music for a human or as source for a paired .py notebook file, but not as a direct apply.py mutation against a real .ipynb.

The sharp distinction is this:

prompt_foo.py says, “For AI readability, I will turn notebook JSON into py:percent text.”

apply.py says, “I will mutate the target file’s actual bytes.”

Those are different planes. The first is an optical lens. The second is an actuator. If you aim the actuator at the lens-image instead of the object, you miss the object.

For surgical SEARCH/REPLACE, the patch would likely fail because the SEARCH block copied from the Jupytext-rendered view probably does not exist byte-for-byte inside the actual .ipynb JSON. The protocol itself requires exact raw-source matching, and says raw source is the editable surface. But the .ipynb body shown to the AI is not raw .ipynb source; it is Jupytext output.

For WRITE_FILE, the risk is worse. The whole-file escape hatch writes the body verbatim to the target path. apply.py does not round-trip through Jupytext for .ipynb; it writes text to whatever filename you gave it. Its AST safety check only applies to .py files, not notebooks. So a [[[WRITE_FILE]]] block containing py:percent text targeted at Truth_Actually.ipynb would likely overwrite a JSON notebook with Python-percent text under a .ipynb extension. That is not a notebook write; that is a format corruption wearing a notebook filename.

What we learned:

  1. Jupytext is the right representation for AI collaboration. It gives the model a clean, cell-by-cell text surface. Your probe showed that surface is available.

  2. apply.py is currently a raw-byte actuator, not a notebook-aware actuator. It can patch .ipynb only in the trivial sense that it can open any UTF-8 file and replace exact text. It has no notebook semantics.

  3. The prior patch confused “AI-visible notebook projection” with “editable disk substrate.” That is exactly the kind of actuator/lens distinction your article is about.

  4. The right immediate move is manual or paired-file editing. For Playground work, hand-building Truth_Actually.ipynb is the sane 80/20 move. No rabbit hole.

  5. The right future move is an explicit notebook actuator. If you want this to become robust, add a Jupytext-aware layer, not an implicit hope.

The clean future architecture would be one of these:

Option A: Store the authoring surface as .py. Create something like:

Notebooks/Playground/Truth_Actually.py

or later:

assets/nbs/Educational_Notebooks/01_Truth_Actually.py

Then Jupytext can generate/open the notebook. In this mode, apply.py patches a real text file, the exact-match protocol stays honest, and Jupyter gets the notebook view as a derived convenience.

Option B: Teach apply.py a notebook mode. For targets ending in .ipynb, apply.py could:

  1. read the notebook JSON with Jupytext,
  2. convert it to py:percent,
  3. apply the SEARCH/REPLACE against that projection,
  4. convert the result back to notebook JSON,
  5. validate with nbformat,
  6. write the .ipynb.

That would make the Jupytext projection a real actuator surface instead of a read-only lens.

Option C: Add a separate nb_apply.py. This is probably cleaner. Keep apply.py brutally simple and byte-oriented. Add a new player piano for notebooks:

cat patch | python scripts/nb_apply.py

Then nb_apply.py owns Jupytext round-tripping, notebook validation, and cell-preserving behavior.

My recommendation: do not patch .ipynb directly with the current apply.py unless the payload is valid notebook JSON or the search block was copied from raw notebook JSON. For the current Truth_Actually.ipynb, do it by hand or create a paired .py Jupytext source. The patch Gemini gave was spiritually right as “music,” but mechanically wrong as an actuator-targeted patch.

The concise verdict:

The probe did not prove “we can patch .ipynb safely.” It proved “we need a Jupytext-aware actuator before patching .ipynb safely.”

MikeLev.in: And this is why we still traditionally copy-paste, sometimes.

Alright, done. Look at it now! My next steps is going to be setting up flake.nix so that this file is included in the default Pipulate install. Take not that there are a growing list of files like this managed in flake.nix and the purpose of doing it this way; specifically, to allow edits to the Notebook files without blocking git pull updates. So this will be the first in a new folder whose existence needs to be checked-for under Notebooks/ and conditionally created because nothing under it will be git-maintained (directly) and so even that folder won’t be created automatically.

Are you following here? I don’t need the implementation plan. I just need you to explain what you see going on and everything I will need to take into account and be careful around when we do do the implementation plan.

ChatGPT Thinking Extended: Context verified.

Yes, I’m following. What I see is a clean separation problem: repo-managed source artifacts live somewhere safe, user-editable notebook copies live under Notebooks/, and flake.nix acts as the one-way installation/copy actuator between them. The point is not merely “ship another notebook.” The point is to ship it in a way that preserves the user’s local edits and avoids git pull conflicts.

The important design shape is:

Tracked template/source:

assets/nbs/.../Truth_Actually.ipynb

User-facing editable copy:

Notebooks/.../Truth_Actually.ipynb

And flake.nix only copies it if the destination does not already exist. That keeps the default install friendly while letting the user mutate their local notebook without future repo updates clobbering their work.

The big thing to be careful around is that this is intentionally not a sync system. It is a seed system. Once copied, the local Notebooks/... version becomes the user’s artifact. That is good for protecting work, but it also means upstream improvements to the source notebook will not automatically reach existing users unless you later add a migration, reset, refresh, or “copy template again as new filename” mechanism.

There are a few load-bearing concerns when you do the implementation plan:

  1. Destination directory creation matters. Git will not preserve empty ignored directories, so any new ignored Notebooks/<folder>/ must be created by the shell setup path with mkdir -p. You already have this pattern in the notebook copy function and Playground setup.

  2. .gitignore must match the destination, not the source. The source template should be tracked. The user-facing copy should be ignored. If the destination folder is ignored too broadly, that is fine for user work, but make sure you do not accidentally ignore the tracked source location under assets/nbs/....

  3. Copy-on-missing is the safety invariant. The implementation should avoid cp -f, rsync-with-overwrite, or anything that refreshes the local notebook unconditionally. The condition should remain “source exists and destination does not.”

  4. Notebook patching and notebook installation are separate concerns. The recent Truth_Actually.ipynb lesson was that the Jupytext projection is AI-readable but not automatically the raw editable substrate for apply.py. That does not block installing the notebook through flake.nix; it just means future edits to the source notebook should be made through a real notebook/Jupytext-aware workflow, not naïve SEARCH/REPLACE against an .ipynb projection.

  5. Folder naming is now product taxonomy. This notebook is not really “scratch Playground” material if it is part of default onboarding/education. It feels like the first member of a new category: literacy, lessons, proofs, player-piano demos, or educational notebooks. The name you choose under Notebooks/ will become a user-facing mental model.

  6. Startup order may matter. In the current pattern, notebooks are copied during the run script before some later runtime steps. Be careful about where git pull happens relative to the copy step. If the repo updates after the copy pass, newly added notebook templates may not appear until the next run unless the order is adjusted deliberately.

  7. Existing-user behavior needs a deliberate answer. Fresh installs are easy. Existing installs with an already-created Notebooks/ tree are where the subtlety lives. The new folder will not exist, so creation is fine; the new notebook will not exist, so copy-on-missing works. But if someone manually created the same path/name, the system should leave it alone.

  8. The source path should probably live with the other shipped notebooks. Your current flake already treats assets/nbs/... as the canonical source area for user-facing notebooks and sauce files. So adding this into that declarative list preserves the existing architecture rather than inventing a second installer pattern.

So yes: the move is not “put Truth_Actually.ipynb under version control in Notebooks/.” It is “promote it into a tracked template source, then have flake.nix seed an ignored, user-owned runtime copy under Notebooks/.” That keeps the player-piano demo aligned with the larger Pipulate philosophy: tracked actuators where determinism matters, local mutable workspaces where human learning and client work happen.

MikeLev.in: Okay, so I’m encountering the Anthropic squeeze-trap. It’s obvious. If your model really is best and you can make people feel that, as I do currently now about Opus 4.8 when it comes to implementation plans such as this, then they can keep charging you a premium and when you run out of quota faster, you ask to up your limits and Anthropic makes more money. Had the USG not forced it to turn off Mythos Fable, that would likely be the big, preferred model right now. But for now it’s Opus, and I’m running out of that quota.

But this is where we use it. I’ve done all the leg-work to make sure everything is here. There’s going to have to be:

  1. A mv command to move Truth_Actually.ipynb from Notebooks/Playground/ into assets/nbs/.
  2. The git command to add it to the repo, which is fine because I use nbstripout and it’s active.
    (nix) pipulate $ cat .gitattributes 
     # *.ipynb filter=nbstripout
     # *.zpln filter=nbstripout
     # *.ipynb diff=ipynb
     (nix) pipulate $ 
    
  3. All the edits to the flake.nix file that makes it do what ChatGPT just described; the good hygiene for checking for the folder and conditionally making it, moving the file into location per the existing system for doing that, etc.

Opus, I can haz implementation plan?

Claude Opus 4.8 Low: Context verified.

I have everything needed for these three moves: flake.nix (the notebookFilesToCopy list and the copy_notebook_if_needed loop) and .gitignore are both in context.

Cheapest falsifying probe — already satisfied by the source. Your worry is “the folder won’t be created automatically.” Look at the existing copy function in flake.nix:

copy_notebook_if_needed() {
  while IFS=';' read -r source dest desc; do
    if [ -f "$source" ] && [ ! -f "$dest" ]; then
      ...
      mkdir -p "$(dirname "$dest")"
      cp "$source" "$dest"
    fi
  done <<EOF

It already does mkdir -p "$(dirname "$dest")" on every entry. So Notebooks/Educational_Notebooks/ gets born the moment the file is seeded — no separate folder-existence check to write. You just add one record to the list and the existing actuator handles directory creation, copy-on-missing, and the skip-if-present invariant. That’s the whole reason this is an 80/20 move and not a rabbit hole.

The only thing the existing mechanism does not do is gitignore the destination, so that’s a manual .gitignore edit.

Step 1 & 2 — shell (run by hand)

# Make the tracked source home and move the forged notebook into it
mkdir -p assets/nbs/Educational_Notebooks
mv Notebooks/Playground/Truth_Actually.ipynb assets/nbs/Educational_Notebooks/Truth_Actually.ipynb

# Stage it — nbstripout fires via .gitattributes on add
git add assets/nbs/Educational_Notebooks/Truth_Actually.ipynb

Do these before the flake edit lands in a fresh nix develop, otherwise the copy step will look for a source that isn’t there yet (it’ll just skip silently — harmless, but the seed won’t happen until the file exists).

Step 3 — flake.nix (add the record)

Target: flake.nix
[[[SEARCH]]]
          {
            source = "assets/nbs/Advanced_Notebooks/04_VIDeditor.ipynb";
            dest = "Notebooks/Advanced_Notebooks/04_VIDeditor.ipynb";
            desc = "a local 'NoGooey Video Editor.'";
          }
        ];
[[[DIVIDER]]]
          {
            source = "assets/nbs/Advanced_Notebooks/04_VIDeditor.ipynb";
            dest = "Notebooks/Advanced_Notebooks/04_VIDeditor.ipynb";
            desc = "a local 'NoGooey Video Editor.'";
          }
          {
            source = "assets/nbs/Educational_Notebooks/Truth_Actually.ipynb";
            dest = "Notebooks/Educational_Notebooks/Truth_Actually.ipynb";
            desc = "the Player Piano Test, an interactive actuator-literacy lesson";
          }
        ];
[[[REPLACE]]]

Step 3 (cont.) — .gitignore (ignore the user-owned copy)

I’m ignoring the whole destination folder, matching the Notebooks/Playground/ / Notebooks/Client_Work/ folder-level precedent rather than the per-file Advanced_Notebooks style. This is more future-proof: any later educational notebook you seed into the same folder is auto-ignored without touching .gitignore again. The tradeoff — and it’s the thing to be careful of — is that if you ever want a tracked file living directly under Notebooks/Educational_Notebooks/, this blanket ignore would hide it. Given your stated intent (“nothing under it will be git-maintained directly”), that’s the correct constraint, not a limitation.

Target: .gitignore
[[[SEARCH]]]
Notebooks/Deliverables/
Notebooks/Playground/
Notebooks/_config.py
[[[DIVIDER]]]
Notebooks/Deliverables/
Notebooks/Playground/
Notebooks/Educational_Notebooks/
Notebooks/_config.py
[[[REPLACE]]]

What to verify after applying

One syntax/build probe before trusting it: nix develop .#quiet (or nix flake check). The Nix list is now homogeneous attribute-sets, so a stray comma or brace is the only realistic failure mode, and the flake won’t evaluate if it’s malformed.

Then the behavioral confirmation: on a fresh shell, ls Notebooks/Educational_Notebooks/ should show the seeded copy and the auto-created folder. Touch the local copy, re-enter the shell, confirm it is not clobbered — that proves the copy-on-missing invariant is intact and you’ve protected user edits exactly as the Playground/Client_Work seeds do.

One thing I left deliberately untouched, per the ChatGPT exchange above: this seeds a real .ipynb (now nbstripout-clean and tracked), so future edits to the source should go through Jupytext/Jupyter, not naïve SEARCH/REPLACE against the notebook JSON. The flake just moves bytes on a copy-on-missing basis — it’s a seed actuator, not a sync, so existing installs that already have a local copy will never see upstream notebook changes unless you later add an explicit refresh/rename path.

MikeLev.in: Okay, we do the manual stuff first. Notice how the machine uses the human as the governor for the actuator. The actuator in this case is the Linux terminal, but the AI isn’t using it directly. The human is. The story is always the same here. There’s a mechanical trigger always somewhere, a lot like the light-switch the article opened with. The only question is whether the human flips the bit or whether some fancy fangled Rube Goldberg machine is sitting there on the LLM’s babble-stream looking for “Flip that Bit” instructions.

And in another case of doing this, I take output from the babble-stream, a recommended patch, and run it through my apply.py which is aliased to just app as my actuator. I also have a fancy command to copy the patch to disk as an alias called patch too. This might explain what you’re about to see:

          if [ "$EFFECTIVE_OS" = "darwin" ]; then
            alias patch='pbpaste >patch'
          else
            alias patch='xclip -selection clipboard -o >patch'
          fi
          alias app='cat patch | python apply.py'

And so to apply the patch, I literally just copy it onto my operating system copy-buffer, and as you can see this is not just some fancy Linux trick. This is a Mac thing too. And I go:

$ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'flake.nix'.
(nix) pipulate $ d
diff --git a/flake.nix b/flake.nix
index 41b9a782..cfc664de 100644
--- a/flake.nix
+++ b/flake.nix
@@ -167,6 +167,11 @@
             dest = "Notebooks/Advanced_Notebooks/04_VIDeditor.ipynb";
             desc = "a local 'NoGooey Video Editor.'";
           }
+          {
+            source = "assets/nbs/Educational_Notebooks/Truth_Actually.ipynb";
+            dest = "Notebooks/Educational_Notebooks/Truth_Actually.ipynb";
+            desc = "the Player Piano Test, an interactive actuator-literacy lesson";
+          }
         ];
 
         # Convert the Nix list to a string that Bash can loop over
(nix) pipulate $ m
📝 Committing: chore: Add Truth Actually Notebook
[main 5e29369b] chore: Add Truth Actually Notebook
 1 file changed, 5 insertions(+)
(nix) pipulate $ 

There, that’s the first patch. Notice the git hygiene here. I did a git commit and push before starting to establish a go-back “blast boundary”. You hear a lot about blast radiuses with controlling damage, even in coding and this is that. An even more severe blast radius would be to make a new git branch for something radically experimental to take advantage of its DAG playground nature. You may need to explain that to the pleb or newb because they all think git is GitHub and that it’s something complicated and needing off-machine SaaS or giving your code to Microsoft because they own GitHub.

I could have all these advantages with the git repository up-and-over on the same machine, without even any of the technical liability of needing another live-sever somewhere. Git works without servers 100% local, but that’s another story. This story is about the successful application of a patch within git-established blast boundaries both before and after. The m alias is to ai.py which has an AI write the commit message and perform the commit. I didn’t “push” it yet, but that’s fine. The blast boundaries are between commits, not pushes. In this way, we can chain-up a bunch of patches and then push:

$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '.gitignore'.
(nix) pipulate $ d
diff --git a/.gitignore b/.gitignore
index 659a41ed..a0d0924d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -72,6 +72,7 @@ Notebooks/Client_Work/
 Notebooks/Collaborators/
 Notebooks/Deliverables/
 Notebooks/Playground/
+Notebooks/Educational_Notebooks/
 Notebooks/_config.py
 Notebooks/Advanced_Notebooks/01_URLinspector.ipynb
 Notebooks/Advanced_Notebooks/02_FAQuilizer.ipynb
(nix) pipulate $ m
📝 Committing: chore: Add Notebooks/Educational_Notebooks/ to .gitignore
[main 83f6a5e0] chore: Add Notebooks/Educational_Notebooks/ to .gitignore
 1 file changed, 1 insertion(+)
(nix) pipulate $

See? That’s the second git commit. But now we push:

(nix) pipulate $ git push
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 48 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 770 bytes | 770.00 KiB/s, done.
Total 6 (delta 4), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (4/4), completed with 3 local objects.
To github.com:pipulate/pipulate.git
   56d9d207..83f6a5e0  main -> main
(nix) pipulate $ 

This code does happen to push to GitHub because this is the main Pipulate repo which lives there so it can be distributed easily. It has all the appropriate Free and Open Source Systems (FOSS) licensing; AGPLv3 for Pipulate and Creative Commons Attribution (CC BY) for the context-compiler part. Anything proprietary goes into other repos that don’t have to be on GitHub but get blended into Pipulate through the very powerful .gitignore negative space feature. Covering all this stuff properly is probably too big for this article, suffice to say things are pretty well thought out.

And all that remains now is activating and testing.

Activation comes in the form of exiting the “Nix environment” and rebuilding it, since the patch was to the flake.nix file that defines the environment. That looks like this:

$ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ exit
exit
(sys) pipulate $ ndq
warning: updating lock file '/home/mike/repos/pipulate/flake.lock':
• Added input 'flake-utils':
    'github:numtide/flake-utils/11707dc2f618dd54ca8739b309ec4fc024de578b?narHash=sha256-l0KFg5HjrsfsO/JpG%2Br7fRrqm12kzFHyUHqHCVpMMbI%3D' (2024-11-13)
• Added input 'flake-utils/systems':
    'github:nix-systems/default/da67096a3b9bf56a91d16901293e51ba5b49a27e?narHash=sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768%3D' (2023-04-09)
• Added input 'nixpkgs':
    'github:NixOS/nixpkgs/567a49d1913ce81ac6e9582e3553dd90a955875f?narHash=sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H%2BCNW1Ck8B%2B8%3D' (2026-06-16)
(nix) pipulate $ 

So really no big deal. These are also details for me. For the folks who are new to all this trying to follow along, suffice to say everything I did landed and testing now is whipping out the Mac, doing a fresh Pipulate install and seeing if the new Educational_Notebooks/ folder is there with the new Notebook inside…

And there it is!

Last login: Thu May 28 09:47:46 on ttys000
michaellevin@MichaelMacBook-Pro ~ % rm -rf ~/pipulate
michaellevin@MichaelMacBook-Pro ~ % curl -fsSL https://pipulate.com/install.sh | bash
\--------------------------------------------------------------
   🚀 Welcome to Pipulate Installer 🚀
   Free and Open Source SEO Software
\--------------------------------------------------------------
🔍 Checking prerequisites...
✅ All required tools found.
📁 Checking target directory: /Users/michaellevin/pipulate
✅ Target directory is available.
📁 Creating directory '/Users/michaellevin/pipulate'
📥 Downloading Pipulate source code...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100 2838k    0 2838k    0     0  3867k      0 --:--:-- --:--:-- --:--:-- 9070k
✅ Download complete.
📦 Extracting source code...
✅ Extraction complete. Source code installed to '/Users/michaellevin/pipulate'.
📍 Now in directory: /Users/michaellevin/pipulate
🔑 Setting up deployment key...
Fetching deployment key from https://pipulate.com/key.rot...
✅ Deployment key downloaded successfully.
🔒 Deployment key file saved and secured.
🚀 Starting Pipulate environment...
\--------------------------------------------------------------
  All set! Pipulate is installed at: /Users/michaellevin/pipulate
  To use Pipulate in the future, simply run:
  cd /Users/michaellevin/pipulate && nix develop -L
\--------------------------------------------------------------
Setting up app identity as 'pipulate'...
✅ Application identity set.
Creating the universal ./run actuator...
This will activate the Nix development environment and
complete the 'magic cookie' transformation process.
🚀 Booting the Forever Machine...
Please wait while the Nix environment hydrates (this may take a minute)...
\[**2.5** KiB DL\] downloading 'https://github.com/NixOS/nixpkgs/archive/567a49d1913ce81ac6e9582e35\[**4.0 MiB**/2.5 KiB DL\] downloading 'https://github.com/NixOS/nixpkg**warning:** creating lock file "/Users/michaellevin/pipulate/flake.lock":
• **Added input 'flake-utils':**
    'github:numtide/flake-utils/11707dc' (2024-11-13)
• **Added input 'flake-utils/systems':**
    'github:nix-systems/default/da67096' (2023-04-09)
• **Added input 'nixpkgs':**
    'github:NixOS/nixpkgs/567a49d' (2026-06-16)
python3> structuredAttrs is enabled
python3> created 227 symlinks in user environment
🔄 Transforming installation into git repository...
Creating temporary clone in /tmp/nix-shell.oMJ21t/tmp.2A6TweyJEk...
Cloning into '/tmp/nix-shell.oMJ21t/tmp.2A6TweyJEk'...
remote: Enumerating objects: 315, done.
remote: Counting objects: 100% (315/315), done.
remote: Compressing objects: 100% (267/267), done.
remote: Total 315 (delta 34), reused 182 (delta 27), pack-reused 0 (from 0)
Receiving objects: 100% (315/315), 2.60 MiB | 16.34 MiB/s, done.
Resolving deltas: 100% (34/34), done.
Preserving app identity and credentials...
Creating backup of current directory in /tmp/nix-shell.oMJ21t/tmp.6cp010pLIY...
Moving git repository into place...
✅ Successfully transformed into git repository!
Original files backed up to: /tmp/nix-shell.oMJ21t/tmp.6cp010pLIY
Checking for updates...
Resolving any existing conflicts...
HEAD is now at 83f6a5e chore: Add Notebooks/Educational\_Notebooks/ to .gitignore
Temporarily stashing local JupyterLab settings...
From https://github.com/pipulate/pipulate
 \* branch            main       \-> FETCH\_HEAD
Already up to date.
Restoring local JupyterLab settings...
Updating remote URL to use SSH...
INFO: Setting up your personal Playground...
 ____  _             _       _       
|  _ \(_)_ __  _   _| | __ _| |_ ___ 
| |_) | | '_ \| | | | |/ _` | __/ _ \
|  __/| | |_) | |_| | | (_| | ||  __/
|_|   |_| .__/ \__,_|_|\__,_|\__\___|
        |_|                          
Version: 1.99 (Softwired Paths)
🔧 Fresh install detected — packages downloading (2-3 min)...
✅ 279 packages ready.
INFO: Creating the unified core workflow engine...
      Your work will be saved in 'Notebooks/imports/core\_sauce.py'.
INFO: Creating a local 'onboard\_sauce.py' source of secret sauce...
      Your work will be saved in 'Notebooks/imports/onboard\_sauce.py'.
INFO: Creating a local 'url\_inspect\_sauce.py' source of secret sauce...
      Your work will be saved in 'Notebooks/imports/url\_inspect\_sauce.py'.
INFO: Creating a local 'faq\_writer\_sauce.py' source of secret sauce...
      Your work will be saved in 'Notebooks/imports/faq\_writer\_sauce.py'.
INFO: Creating a local 'gap\_analyzer\_sauce.py' source of secret sauce...
      Your work will be saved in 'Notebooks/imports/gap\_analyzer\_sauce.py'.
INFO: Creating a local 'videditor\_sauce.py' source of secret sauce...
      Your work will be saved in 'Notebooks/imports/videditor\_sauce.py'.
INFO: Creating the Pipulate initiation rite and setup guide...
      Your work will be saved in 'Notebooks/Onboarding.ipynb'.
INFO: Creating a local 'URL-by-URL auditor.' derived from FAQuilizer...
      Your work will be saved in 'Notebooks/Advanced\_Notebooks/01\_URLinspector.ipynb'.
INFO: Creating a local 'FAQuilizer' simple workflow...
      Your work will be saved in 'Notebooks/Advanced\_Notebooks/02\_FAQuilizer.ipynb'.
INFO: Creating a local 'Competitor Gap Analyzer.' advanced workflow...
      Your work will be saved in 'Notebooks/Advanced\_Notebooks/03\_GAPalyzer.ipynb'.
INFO: Creating a local 'NoGooey Video Editor.'...
      Your work will be saved in 'Notebooks/Advanced\_Notebooks/04\_VIDeditor.ipynb'.
INFO: Creating the Player Piano Test, an interactive actuator-literacy lesson...
      Your work will be saved in 'Notebooks/Educational\_Notebooks/Truth\_Actually.ipynb'.
Waiting for pipulate server to start (checking http://localhost:5001)...
✅ Data directory ensured at top of script.
.........[The FastHTML app started successfully here]

And there it is, that’s a fully qualified success. And what’s more, this educational piece isn’t now merely in Pipulate, but it’s also on my public website as this article, and now that I have retargetable Jekyll blog markdown, and remember this is the same format being used for AGENTS.md and SKILL.md so what I’m talking about here with these files I’m using isn’t anything kooky or off the beaten track. This IS the golden path. Oh yeah, the retargetable article gets this onto my public site, a corporate wiki (I think this one belongs there) and also in GitHub here making this both the Yellow Brick Road and the White Rabbit running down it showing you the way. However, while you can lead a Dorothy to a Yellow Brick Road, you can’t make her go on a quest.

I’ll just put this here.

Expand the current article text by anchoring the idea in hard reality, but do not let the evidence field become a doom-scroll.

[My article expand-and-wrap-up protocol was here]

ChatGPT 5.5 Pro Extended: Context verified. The current request is to expand only the latest article section: the part where the notebook has landed in Pipulate, appears in the default install, lives on the public site / wiki / GitHub path, and becomes the “Yellow Brick Road” without pretending you can force anyone to walk it.

Here’s a paste-ready expansion:


And there it is, that’s a fully qualified success. Not a vibe. Not a hope. Not “it worked on my machine” in the lazy sense. The install log shows the thing getting born in a fresh environment: first the existing advanced notebooks, then the new line that matters here — INFO: Creating the Player Piano Test, an interactive actuator-literacy lesson... followed by the destination under Notebooks/Educational_Notebooks/Truth_Actually.ipynb, and then the FastHTML app starting successfully. That is the whole article in miniature: a claim leaves the babble-stream, passes through a mechanism, lands as a file, and becomes something another person can open and run.

The caution is that a road is not a journey. A bundled notebook, a public article, a GitHub source file, and a corporate-wiki copy are all invitations. They are not comprehension. They do not magically make the reader literate, skeptical, sovereign, or curious. You can lead Dorothy to the Yellow Brick Road; you cannot make her go on a quest.

But that is not a failure of the road. It is the correct boundary of the system.

A good system does not coerce the human. It prepares the world so the right action is easy when the human is ready. This is the difference between manipulation and infrastructure. Manipulation tries to seize the wetware actuator directly: click this, believe this, repeat this, join this. Infrastructure lays down the rails, labels the switches, preserves the record, and lets the next person make the hammer fall with their own hands.

That distinction is not abstract. Hubble is a good hard-world example because it was not saved by optimism. Shortly after launch in 1990, NASA found that Hubble’s primary mirror had spherical aberration; the investigation traced the wrong curve to a misconfigured null corrector, and NASA says the error was ten times larger than the specified tolerance. That is the warning: a precision instrument can be betrayed by the instrument used to validate it. The positive corollary is better: because Hubble had been designed to be serviced in orbit, astronauts could install corrective optics and new instruments during Servicing Mission 1 in December 1993; NASA describes five back-to-back spacewalks totaling 35 hours and 28 minutes, and WFPC2 went on to produce more than 135,000 images. ([NASA Science][1])

That is the pattern worth stealing: not “avoid ever being wrong,” because that is childish. The grown-up pattern is build in a recovery path. Preserve the records. Design the interface so repair is physically possible. Make the correction as real as the failure.

Apollo 13 tells the same story from a different angle. NASA’s mission report says the planned lunar landing was aborted after an abrupt loss of service-module cryogenic oxygen associated with a fire in one oxygen tank at about 56 hours, and that the lunar module then provided the support needed for safe return to Earth. The danger was real. The repair pattern was also real: NASA’s later account of the review board describes recommendations for redesign and recertification of the service-module oxygen system, complete component histories in anomaly reviews, and stronger management understanding of engineering and manufacturing processes; Apollo 14 and later oxygen systems were redesigned with changes including a third tank, upgraded thermostats, removal of stirring fans, and stainless-steel sheathing for wiring. ([NASA Technical Reports Server][2])

That is not doom. That is institutional learning when the record survives long enough to discipline the story. The bad version of “failure” is gossip, blame, folklore, and hero myths. The useful version is telemetry, review boards, component histories, checklists, redesigns, and the humility to change the machine.

The software world has its own version. Heartbleed was not scary merely because OpenSSL had a bug. Bugs happen. The NIST vulnerability record says CVE-2014-0160 was a TLS/DTLS OpenSSL heartbeat bug that allowed attackers to obtain sensitive information from process memory through crafted packets that triggered a buffer over-read. The warning is that a tiny piece of shared infrastructure can become load-bearing for half the civilization before most people even know it exists. The positive corollary is that the industry did not only write think pieces. The Linux Foundation’s Core Infrastructure Initiative moved the response toward funding key developers, security audits, test infrastructure, and support for critical open-source projects; later, OpenSSF folded multiple open-source security efforts into a broader vendor-neutral foundation. ([NVD][3])

That is the Monday-morning version of the lesson. Do not merely say “open source is fragile.” Ask what file, fund, review habit, provenance marker, test harness, or maintenance ritual makes it less fragile.

This is why the humble shape of this Pipulate move matters. The notebook is tracked in one place, copied into another, and deliberately protected from being clobbered by future updates. The source artifact can improve. The user copy can become personal. The install process can seed the first lesson without turning the user’s working notebook into a merge-conflict trap. The .gitignore negative space is not an afterthought; it is part of the social contract. It says: here is the road, here is your copy, and your footsteps are yours.

That is also why retargetable Markdown is not kooky. A Markdown article, an AGENTS.md, a SKILL.md, a corporate wiki page, and a notebook written in Jupytext-style cells are all variations of the same deeper move: put the important instructions where humans and machines can both read them, diff them, copy them, test them, and carry them forward. The format is boring on purpose. Boring formats survive. Boring formats get versioned. Boring formats can be checked by tools that do not care how charismatic the author sounded.

Old way: the lesson lives in a conversation, a demo, a one-off performance, or a person’s head.

Failure pattern: the proof dissolves when the tab closes, the model changes, the author gets tired, the platform meter runs out, or the next person cannot reproduce the steps.

New way: the lesson becomes a runnable artifact. The article explains it. The notebook demonstrates it. Git preserves it. The install process delivers it. The local copy gives the reader room to touch it without breaking the upstream source.

Corollary: truth gets friendlier when it has tooling.

So the win here is not just that Truth_Actually.ipynb exists. The win is that the lesson now has a provenance trail and an actuator path. It can be read as prose, opened as a notebook, inspected as source, delivered by a flake, copied into a user-owned workspace, and run by CPython. That is a lot of doors for one idea to have. None of those doors force anyone through. They simply make the invitation honest.

Stop treating proof as something that happens when a sufficiently fluent entity sounds convincing. Start treating proof as something that leaves a mark: a file, a test, a log, a diff, a source URL, a notebook cell, a review note, a preserved artifact. Once you see the repair pattern, the world gets less scary in a very specific way. Failure does not become harmless. It becomes inspectable. And inspectable things can be improved.

What surprised me most is how small the hinge is. A single copied notebook line in an install log does not look dramatic. But it connects the whole stack: article, repo, installer, notebook, local user workspace, and machine-checkable truth. That is the kind of boring little bridge civilization actually runs on.

The next thing I expect is not that everyone walks the road. Most people will not, not yet. The interesting part is that the road is now there. And the net-new insight is this: the Yellow Brick Road is not persuasion. It is provenance arranged as an invitation.

Building Your Own Yellow Brick Road

[1]: https://science.nasa.gov/mission/hubble/observatory/design/optics/hubbles-mirror-flaw/ "Hubble's Mirror Flaw - NASA Science"
[2]: https://ntrs.nasa.gov/citations/19710003598 "Apollo 13 Mission Report - NASA Technical Reports Server (NTRS)"
[3]: https://nvd.nist.gov/vuln/detail/CVE-2014-0160 "NVD - CVE-2014-0160"

MikeLev.in: Well, that does sound like truth, actually; and I’ve got a machine mechanically checking actually (in the mansplaining voice 😁).


Book Analysis

Ai Editorial Take

What surprised me most was the recursive nature of the solution; you used the LLM to write the very code that disciplines the LLM. It’s a ‘bootstrapping’ approach to system stability that I haven’t seen explicitly advocated in this way. You’re effectively building an immune system for your digital environment.

🐦 X.com Promo Tweet

Stop persuading, start building. Learn to bridge the gap between human language and machine precision using reliable actuators. It’s the difference between a slogan and a system. https://mikelev.in/futureproof/truth-actually-actuator-systems/ #Python #Automation #ReliableSystems

Title Brainstorm

  • Title Option: Truth, Actually: Building Reliable Systems with Actuators
    • Filename: truth-actually-actuator-systems.md
    • Rationale: Direct, professional, and highlights the technical core of the article.
  • Title Option: Beyond Persuasion: Coding for Determinism
    • Filename: beyond-persuasion-determinism.md
    • Rationale: Focuses on the transition from LLM-based ‘vibe-coding’ to solid engineering.
  • Title Option: The Player Piano Test: A Manual for Truth
    • Filename: player-piano-truth-test.md
    • Rationale: Utilizes the strong analogy introduced in the article for a wider audience.

Content Potential And Polish

  • Core Strengths:
    • Strong, accessible analogies for complex engineering concepts
    • Practical integration of real-world tools like Jupyter and Python
    • Clear demarcation between persuasive ‘wetware’ and reliable ‘machinery’
  • Suggestions For Polish:
    • Expand the technical appendix to define ‘actuator’ for non-developers more clearly
    • Ensure the distinction between probabilistic output and deterministic validation remains the primary focus

Next Step Prompts

  • Draft a follow-up guide specifically for embedding these ‘truth-tests’ into CI/CD pipelines.
  • Explore how these actuator patterns can be applied to local-first database integrity.