A desktop 3D printer printing a small mechanical part in a home workshop, with filament spools blurred in the background.

The Bill That Wants My 3D Printer to Police Itself

There is a printer humming in my workshop right now, laying down a part one 0.2mm layer at a time. It is a slow, almost meditative machine. It does not know what it is making. It does not know if the object taking shape on the bed is a bracket for my garage shelving, a fidget toy for my son, a one-off template, a jig for my guitar building, or something I should not be making at all. It knows one thing: move the nozzle here, push out this much plastic, repeat a few hundred thousand times. That is the entire worldview of a 3D printer.

I bring this up because the California legislature has decided my printer should know more than that. Assembly Bill 2047 would require every 3D printer sold in the state to run a state-certified “detection algorithm” designed to recognize and refuse to print firearm components. I got into this hobby almost a decade ago on a Prusa, I still run one today, and I have spent enough of my career writing software to have a strong and specific reaction to this. It is not a political one. It is an engineering one. The bill asks for a piece of software that cannot reliably exist, and even if it could, it would have nowhere to run. On top of that imaginary foundation it then builds an entire regulatory apparatus.

What AB2047 Actually Asks For

The mechanics are worth understanding before we argue about them. The bill lays out a multi-year timeline. By July 2027, the California Department of Justice studies firearm detection technology. By January 2028, it certifies detection algorithms from vendors. By July 2028, manufacturers must submit an attestation for each printer model they sell. By September 2028, the state publishes a list of approved printers, updated quarterly. And starting in March 2029, selling a non-compliant printer in California becomes illegal. There are new criminal penalties for anyone who disables the detection technology.

Read that sequence again and notice what it assumes. It assumes that by 2028 there will be vendors selling reliable firearm-detection software, that the DOJ will be able to certify it, and that manufacturers can bolt it onto their machines. The entire structure depends on step one being possible. So let’s look at step one.

A Rifled Barrel Is Just a Grooved Cylinder

Here is the core problem, and it is the kind of thing that sounds solvable until you actually try to specify it. A 3D printer receives a model. The model is geometry: a mesh of triangles, sliced into a stack of two-dimensional paths. To detect a firearm part, the software would have to look at that geometry and decide whether it represents something dangerous.

But geometry does not carry intent. A rifled barrel, to a detection algorithm, is a grooved cylinder. So is a section of industrial screw thread. So is part of an optical instrument, a custom gear, a textile bobbin, a printable fidget toy, a thousand legitimate objects. The shape that makes a barrel a barrel is not unique to barrels. You cannot write a function that takes in a mesh and returns “this is a gun part” without either missing real firearm components or flagging an enormous number of innocent ones. In machine learning terms you are choosing between false negatives and false positives, and both failure modes here are catastrophic. Miss the real thing and the law accomplishes nothing. Flag the innocent thing and you have a printer that refuses to make a curtain rod bracket because it looks suspicious.

It gets worse once you remember that the person trying to evade detection gets a vote. Shape-based detection is defeated by the most trivial transformations imaginable. Rotate the model. Scale it slightly. Split a banned object into a handful of innocent-looking pieces, print them across separate jobs, and assemble them on the bench, which is how people print large or complex parts anyway. None of these change the function of the final object, and all of them defeat a classifier looking for a known silhouette. There is an old locksmith’s adage that locks only keep honest people honest. A detection algorithm is a kind of lock, and this one stops the honest while inconveniencing nobody who is actually a threat.

Where Would This Software Even Run

Suppose, against all of this, that someone ships a detection algorithm that actually works. There is a second wall waiting behind the first, and it is the part of this I find almost funny: even a perfect classifier would have nowhere to run.

Here is what most people, and I suspect most legislators, do not realize about how these machines work. By the time a design reaches my printer, it is no longer a 3D model. Software on my computer, called a slicer, has already flattened it into G-code, a dumb list of hundreds of thousands of instructions that say move here, extrude this much, heat to this temperature. The printer’s control board never sees the shape of the object at all. For the firmware on the printer to detect a gun, it would have to reconstruct a three-dimensional model out of raw toolpaths and run shape analysis on it, on a microcontroller with kilobytes of memory. My Prusa cannot do that. No consumer printer can. It is not a hard engineering problem so much as an absurd one.

So the detection has to live somewhere else, and there are really only two places it can go. The first is the slicer. But the popular slicers, PrusaSlicer, Cura, OrcaSlicer, are themselves open source. You fork the one with the block stripped out, or you simply use a different one, and nothing downstream ever checks what it produced. The second place is the only one that actually enforces anything: require every printer to refuse any G-code that is not cryptographically signed by a state-approved cloud slicing service. That version works, in the narrow sense that it would function. It also means the end of slicing your own files on your own computer, the end of open-source firmware, the end of the printer as a general-purpose tool. It turns an open machine into a locked appliance that prints only what a government-approved server allows.

That is the choice the bill quietly forces, even if its authors never say it out loud. Either the detection is real and trivially bypassed, or it is genuinely enforceable and the open ecosystem is dead.

And that open ecosystem is not incidental to how these machines work. The software that actually drives them, the two dominant projects Marlin and Klipper, is open source and free to download, and replacing the firmware on a printer is something the community does routinely, in minutes, to tune machines and add features. A state-mandated block sitting in that firmware is removable by definition. The whole community already holds the key to the proposed lock. The law is betting that criminals either do not know the key exists or will politely decline to use it. The bill’s response is criminal penalties for disabling the technology, which means the actual enforceable result is that an ordinary maker who updates their firmware becomes a potential criminal, while anyone with bad intent was always going to use a non-compliant machine bought out of state or built from parts.

Who Actually Pays

This is where the abstract engineering critique becomes a real-world one. The signatories on the opposition letter are not a fringe group. They include Prusa Research, Make Magazine, and VORON Design, alongside individuals like Josef Průša, Dale Dougherty, and Joel Telling of 3D Printing Nerd. The numbers they cite are the reason. More than 1.5 million California students reach 3D printing through their schools. More than 30,000 businesses depend on it, from dental labs to jewelers to small manufacturers. There is something on the order of $10.5 billion in investment riding on this ecosystem in the state.

The bill’s penalties, reportedly $25,000 per violation, do not land on the people it is nominally aimed at. They land on the school district, the maker space, the library, the small business owner running three printers in a garage. Those are the entities that buy from compliant vendors, register their equipment, and follow the rules. The person determined to manufacture an untraceable weapon was never in that group. They route around the law, because routing around the law is, as we just established, trivial. So you end up with the worst possible outcome: real cost imposed on the law-abiding, near-zero cost imposed on the dangerous, and a chilling effect on exactly the educational and small-business use that makes this technology worth having.

Compelled Speech and the First Amendment

There is a legal dimension I am less qualified to litigate but that I think any engineer should at least register. CAD files and the source code that drives these machines are, under a long line of cases, a form of protected expression. The bill requires manufacturers to attest to the behavior of an algorithm operating on that expression, which the opposition frames as compelled speech on a matter of public concern. I am not a lawyer, and I will not pretend the constitutional question is open and shut. But it should make us uncomfortable that the mechanism here is the state requiring a company to vouch for the output of detection software that, as far as anyone can demonstrate, does not actually work. Picture a law that required every typewriter manufacturer to certify, in writing and renewed every year, that their machines cannot be used to type a threatening letter. That is roughly the position AB2047 puts a printer company in. You are being compelled to attest to a fiction, under penalty.

What This Pattern Should Teach Us

I want to be careful here, because it is easy to read a piece like this as “no regulation, ever.” That is not my position. Untraceable firearms are a genuine problem and a serious one. The objection is not that the goal is illegitimate. The objection is that the proposed mechanism is engineering fan fiction, and that mandating impossible software does not become possible because a statute says so.

It helps to remember what the law already does. California does not ignore homemade firearms. If you want to build your own, you must first apply to the Department of Justice for a serial number and engrave it on the receiver, and doing otherwise is itself a crime. The state already regulates the dangerous act, directly, where it belongs. What AB2047 does instead is reach past the act and conscript the tool. Nobody expects Home Depot to interrogate why I am buying a length of steel pipe or a box of fasteners, even though plenty of dangerous things can be assembled from ordinary hardware. We regulate the act, not the hardware store, because the hardware store cannot read minds. Neither can my printer.

This is a pattern worth recognizing, because 3D printers are not the last general-purpose tool a legislature is going to try to make smart enough to police its own users. Any time a law says “the tool must detect bad use and refuse it,” an engineer should ask the boring, deflating questions. Can the tool actually distinguish intent from form? What does the determined adversary do in response? Who absorbs the cost when the detection is wrong? For AB2047 the answers are no, they trivially route around it, and the schools and makers do. When those are your answers, you do not have a safety feature. You have a tax on the honest and a piece of theater for everyone else.

If you build things, and especially if you build things in California, this one is worth paying attention to. The opposition letter has a breakdown of where the bill stands and who to contact on the Senate Judiciary and Public Safety committees before it advances. I am writing to those committee members, and I am going to call my own state senator, Dave Cortese, because the gap between what the bill asks for and what software can actually do is not a close call, and that gap is precisely the kind of thing the people who build these machines are in a position to explain.

My printer is still running as I finish this. It still has no idea what it is making. And no statute, however well-intentioned, is going to change that.

A spotlight shines on a pianist intensely playing a small, worn piano on a large, dark stage.

The Koln Concert and Creative Constraints

This week I was reminded of a story I like to tell, and the value of constraints on creative work. When I’m working, I often set my constraints before I begin. For example, on an old agentic coding project, I set a few constraints: “The orchestration model must be Gemini Flash,” “All tool calls are through sub-agents,” and “Permissions and configurability are at the core of the agentic loop.” From that, I ended up with adh-cli, a policy-aware TUI for working with Gemini that inspired many of the features I worked on in Gemini CLI last year. The project itself is defunct now and not maintained, but the constraints gave me a great way to think about the project and forced creativity in other areas.

We run into constraints in many different ways. Maybe it’s time pressure: How many of you felt like you wrote your best papers 24 hours before they were due? Maybe it’s the environment, like you must integrate with a certain piece of software, or you have to design your system in a certain way. Maybe it’s self-imposed like my example with adh-cli.

Or maybe the constraint is philosophical. Take Mario Zechner’s Pi Agent, for example. In a blog post, Zechner expressed frustration with the bloat of modern AI coding assistants that try to do everything, describing them as “spaceships with 80% unused functionality.” In response, he built Pi around an “anti-framework” philosophy of radical minimalism. He intentionally constrained his default coding agent to just four fundamental tools: read, write, edit, and bash. By stripping away the hidden system prompts and unpredictable context injections, the tool forces developers to be intentional. It proves that you don’t need a massive, opaque framework to build highly capable AI workflows—sometimes, fewer tools create a sharper focus.

Whether it’s a self-imposed architectural rule or an anti-framework philosophy, these software constraints force us out of our default habits and into a space of deliberate, intentional design. Yet, in our day-to-day work, constraints are rarely celebrated. In fact, that is actually how I end up in constraint conversations the most often: people don’t like their constraints because the constraint has been imposed on them externally. They see it as a restriction instead of a way to channel their creativity. To me, a constraint means that we shut down a huge portion of the exploration space. I don’t have to worry about a million different architectural choices because the constraint has made the decision for me. It is incredibly freeing. Whenever I try to help someone turn around their mindset—from fearing or being frustrated by constraints to being excited by them—I inevitably end up telling them the story of Keith Jarrett and the 1975 Köln Concert.

In 1975, a 17-year-old jazz fan named Vera Brandes organized a late-night concert at the Cologne Opera House. She managed to book Keith Jarrett, one of the most notoriously perfectionist jazz pianists of his generation. It was an ambitious undertaking, and almost immediately, it turned into a disaster.

Due to a backstage mix-up, the venue provided the wrong piano. Instead of the premier concert grand Jarrett requested, he was presented with a small rehearsal model. It was horribly out of tune, the pedals stuck, the high notes sounded tinny and harsh, and the bass lacked any resonance. Jarrett, exhausted and suffering from back pain, flat-out refused to play. It was only when Brandes followed him out into the pouring rain and begged him that he relented, taking pity on the teenager. “Never forget,” he told her. “Only for you.”

What happened next is legendary. Forced to play an unplayable instrument, Jarrett had to completely abandon his usual style. Because the high and low registers were awful, he confined his playing strictly to the middle of the keyboard. Because the piano was too quiet to fill the 1,400-seat opera house, he stood up and hammered the keys with immense physical force. To make up for the lack of resonance, he relied on rolling, repetitive, hypnotic rhythmic patterns in his left hand.

He embraced the limitations, and in doing so, he produced absolute magic. The recording, The Köln Concert, went on to become the best-selling solo jazz album in history.

I think about the Köln Concert all the time, especially lately as we navigate the current landscape of Artificial Intelligence and software architecture.

The Bloat of Infinite Resources

In modern software engineering, we are rarely handed a broken piano. We operate in an era of perceived infinite resources. Cloud computing gives us endless horizontal scaling. Context windows for Large Language Models have ballooned from a meager 4K tokens to 1 million or more. If an application is slow or an agent isn’t performing well, the default instinct is to throw more compute, more memory, or a larger model at the problem.

But infinite resources often breed intellectual laziness. When you have a 1-million token context window, you don’t have to think critically about what information actually matters. You just dump the entire codebase or the entire library of documents into the prompt and hope the model figures it out. It’s the equivalent of having a perfect Bösendorfer grand piano and just mashing all the keys at once.

A pragmatic engineering manager might push back here: Developer time is expensive. If I can solve a problem today by dumping an entire codebase into a 1-million token context window, isn’t throwing compute at it just good business?

It’s a fair question, and engineering is always about tradeoffs. But the tools have evolved—building a RAG pipeline doesn’t take a week anymore; with the right utilities, it takes minutes. More importantly, relying on infinite resources often hides long-term costs. When I built adh-cli, I made an explicit tradeoff: by routing everything through tightly scoped sub-agents, I was actually consuming more total tokens than a single massive prompt would use. But because my constraint forced me to use a much cheaper model (Gemini Flash), my bet was that the overall system would be far more cost-effective and resilient. AI doesn’t remove the need for architectural judgment; it exponentially increases it. You have to exercise good judgment to know when throwing compute at a problem is a calculated business decision, and when it’s just masking a fragile design.

The Innovation of Constraints

The most interesting work in AI right now isn’t happening where resources are unlimited. It’s happening at the edges, where constraints are severe.

Take local models, for example. When you’re trying to run an LLM on a consumer laptop or a Raspberry Pi, you don’t have the luxury of a 70-billion parameter model. You are forced to use a smaller, quantized model. This constraint forces you to build better architectures. You can’t rely on the model to “know” everything, so you have to optimize at the edge. Maybe you build robust Retrieval-Augmented Generation (RAG) pipelines. Maybe you implement sophisticated memory retrieval systems to surface exactly the right historical context just-in-time. Or maybe you break complex workflows down into tiny, focused sub-agents, each operating with its own tightly constrained context window. You have to craft highly specific, deterministic prompts.

# Instead of one massive prompt, constraints force modularity
def evaluate_code_chunk(chunk: str, context: dict) -> EvaluationResult:
    """
    A tightly scoped function that uses a small, fast local model
    to evaluate a specific piece of code, rather than dumping
    the whole repo into a massive API call.
    """
    prompt = build_focused_prompt(chunk, context)
    response = local_model.generate(prompt, max_tokens=256)
    return parse_evaluation(response)

Just like Jarrett avoiding the tinny upper register, we learn to avoid the weak points of our tools. We build guardrails. We write cleaner code. We design systems that are elegant because they have to be.

Finding Your Broken Piano

Of course, there is a survivorship bias to the Köln Concert. For every broken piano that produces a masterpiece, there are a hundred broken laptops that just result in missed deadlines. Not all constraints are good constraints. You can’t change the laws of physics, and if a structural limitation is genuinely preventing the work from happening, you have to reevaluate. The goal isn’t to suffer for the sake of suffering. But by starting with strict constraints, you force yourself to explore the boundaries. If you prove a task is impossible under those conditions, you can always loosen the constraints and expand your resources. But if you start with infinite resources, you never learn where those boundaries actually are.

Constraints are not the enemy of creativity; they are its prerequisite. Yes, accepting a severe constraint—especially an external one you didn’t choose—can be incredibly painful in the moment. Keith Jarrett hated his broken piano. He didn’t feel freed; he fought against it until he was forced to adapt. But like exercise or eating your vegetables, the value isn’t in the immediate comfort. It’s about the mindset shift. You accept the constraint to build a muscle, to stay fit, to force yourself to find a new path when the easy one is blocked. When we are stripped of our ideal tools and infinite runways, we are forced to abandon our default habits. Whether it’s the self-imposed design rules of adh-cli, the radical minimalism of Mario Zechner’s Pi Agent, or the physical limitations of a broken rehearsal piano in Cologne, constraints force us into a space of deliberate, intentional action.

If you want to build a truly resilient, innovative system, don’t start with the biggest, most expensive tools available. Start with a broken piano. Artificially constrain your resources. Limit your context window. See what you can achieve with a 7B parameter model instead of a flagship API, or see what happens when you strip your agent’s toolkit down to the bare essentials.

You might just find that the limitations force you to build something far better than you would have otherwise—a system that is elegant not in spite of its constraints, but because of them.

So, look around your current projects. Where are you relying on infinite resources to mask lazy architecture? And more importantly: what constraints have you come across in your own work that felt like a frustrating restriction at first, but turned out to be a blessing in disguise? I’d love to hear your stories.

A mixed-media illustration of a double-necked electric guitar with glowing microtonal frets, set against sound waves that shift from orderly blue patterns to chaotic warm-colored interference patterns.

Temperature 1.0

I subscribe to a lot of music channels on YouTube, and last week every single one of them started talking about the same band at the same time. Not a slow build, not one creator picking up on another’s video. It was like a switch flipped. Two masked figures in black-and-white polka-dot costumes, performing on KEXP, playing music that sounded like it had arrived from a parallel universe where Western tuning never became the standard. The band is called Angine de Poitrine, and their live session has racked up over six million views. I sat there watching, grinning like an idiot, thinking: this is what temperature 1.0 sounds like.

If you work with large language models, you know what that means. The temperature parameter controls randomness in a model’s output. At temperature 0, you get the safest, most predictable token every time. The writing is competent and completely forgettable. Crank it to 1.0 and you get something wilder, less expected, sometimes brilliant, sometimes incoherent, but never boring. Most production systems run somewhere around 0.7, which is the sweet spot for “creative but not too creative.” It’s also, increasingly, the setting our entire culture seems to be tuned to.

The Median is Everywhere

A few weeks ago, I wrote about how our culture has been drifting toward monoculture for a long time, and AI is accelerating the trend. Language models are prediction engines. Left to their defaults, they produce the statistically most likely next token, the most average, most expected version of whatever you ask for. I argued that the writer’s job is to pull the output toward the edges, to resist the gravitational pull of the median.

But it’s not just writing. The flattening is everywhere. Algorithmic recommendation systems optimize for engagement, which in practice means optimizing for familiarity. Spotify’s Discover Weekly is tuned to give you something new that sounds enough like what you already listen to that you won’t skip it. Netflix thumbnails are A/B tested into oblivion. Even the indie coffee shop down the street has the same reclaimed wood and Edison bulbs as every other indie coffee shop in every other city, because the same Pinterest boards and Instagram algorithms surfaced the same aesthetic to the same demographic worldwide.

We are living at temperature 0.7, and I think people can feel it.

Something Between the Notes

Which is why Angine de Poitrine hit so hard. Everything about them resists categorization.

Start with the instrument. Khn de Poitrine plays a custom double-necked hybrid, a guitar and bass separately wired, each fitted with additional microtonal frets. It was custom-built by a local luthier in Saguenay, Quebec. Western music divides the octave into 12 equal steps. Microtonal music lives in the spaces between those steps, the quarter tones and third tones that are standard in Indian, Arabic, Turkish, and Indonesian traditions but almost unheard of in Western rock. When you listen to Angine de Poitrine, the notes themselves are literally between the notes your ear expects. The music is operating outside the grid.

Then there’s the visual identity. In an era of algorithmic personal branding, where artists are coached to show their faces, share their stories, and build parasocial relationships with their audiences, these two perform behind oversized papier-mâché masks. They go by pseudonyms. Their website states plainly that “any speculation regarding the identity of its members is unverified, not endorsed by the group, and could constitute an invasion of privacy.” They’ve stripped away every signal the recommendation engine uses to sort and categorize. No faces, no backstory, no brand. Just the music and the performance.

And the performance is unhinged. Their KEXP session is part math-rock concert, part absurdist theater, part fever dream. It shouldn’t work. It’s too weird, too dense, too far from anything the algorithm would select as “likely to engage.” And yet six million people watched it and couldn’t look away.

The Joke That Wasn’t Random

Here’s the part that matters most to me. Angine de Poitrine formed in 2019 as a practical joke. The two musicians were booked to perform twice in one week at the same local venue in Saguenay, so they put on masks and polka-dot costumes for the second show as a gag. But the people behind those masks have been musical collaborators for two decades, performing together in various projects since they were 13 years old. By the time the KEXP camera pointed at them, the craft underneath the joke was deep and undeniable.

This is the part that connects to something I’ve been thinking about a lot lately. Virality looks like luck from the outside. But what looks like a random spike is almost always preparation meeting a moment. These two spent twenty years building a musical vocabulary together, absorbing microtonal traditions from around the world, developing the kind of telepathic interplay that only comes from thousands of hours of shared performance. The band name was a joke. The musicianship was not.

Temperature 1.0 output only works when the model has been well-trained. Randomness without depth is just noise. What makes Angine de Poitrine compelling isn’t that they’re weird. It’s that they’re weird and masterful. The strangeness is intentional, controlled, the product of two people who know exactly what they’re doing and have chosen to do something no algorithm would have predicted.

The Hunger

I think the six million views aren’t an accident, and they aren’t just the novelty of funny masks. I think people are hungry. We scroll through feeds that have been optimized to show us what we’ll tolerate, and we’ve started to notice the sameness. When something comes along that is genuinely, irreducibly different, something that can’t be reduced to a Spotify genre tag or an engagement metric, it cuts through the noise like a signal from another frequency entirely.

Angine de Poitrine didn’t optimize for the algorithm. They built a custom instrument, put on masks, and played music that exists in the spaces between the notes the Western world agreed on centuries ago. And the world responded not in spite of how strange they are, but because of it.

Maybe the lesson is simple. In a culture tuned to 0.7, the thing that breaks through is the thing running at 1.0. But only if the model behind it has been trained for twenty years.

A bird's-eye view of a winding river of glowing green GitHub contribution tiles flowing across a dark landscape, with bright yellow-green flames rising from clusters of the brightest tiles, while a lone figure sits at a laptop at the edge of the mosaic under a distant skyline of code-filled windows.

4255 Contributions – A Year of Building in the Open

I was staring at my GitHub profile the other day when a number caught my eye. 4,255. That’s how many contributions GitHub has recorded for me over the past year. I sat with it for a moment, doing the quick mental math: that’s close to twelve contributions every single day, weekends included. The shape of the year looked just as striking. I showed up on 332 of the 366 days in the window, 91% of them, and at one point put together a 113-day streak without a gap. It felt like a lot. It felt like proof of something I hadn’t been able to articulate until I saw it rendered as a green heatmap on a screen.

About a year ago, I wrote about my decision to move back to individual contributor work after years in leadership roles. I talked about missing the flow state, the direct feedback loop of writing code and watching it work. What I didn’t know at the time was just how dramatically that shift would show up in the data. 4,255 contributions is the quantitative answer to the question I was trying to answer qualitatively in that post: what happens when you give a builder back the time to build?

The Shape of a Year

Numbers by themselves are just numbers. What makes them interesting is the shape they take when you zoom in. My year wasn’t a single monolithic effort on one project. It was a constellation of interconnected work, each project feeding into the next, each one teaching me something that made the others better.

The largest body of work was on Gemini CLI, Google’s open-source AI agent for the terminal. This project alone accounts for a significant chunk of those contributions, spanning everything from core feature development to building the Policy Engine that governs how the agent interacts with your system. But the contributions weren’t just code. A huge portion of my time went into code reviews, issue triage, and community engagement. Working on a repository with over 100,000 stars means that every merged PR has real impact, and every review is a conversation with developers around the world.

Then there was Gemini Scribe, my Obsidian plugin that started as a weekend experiment and grew into a tool with 302 stars and a community of writers who depend on it. Over the past year, I shipped a major 3.0 release, built agent mode, and iterated constantly on the rewrite features that make it useful for daily writing. In fact, this very blog post was drafted in the tool I built, which is a strange and satisfying loop.

Alongside these larger efforts, I shipped a handful of small, sharp tools that I needed for my own workflows. The GitHub Activity Reporter is one I’ve written about before, a utility that uses AI to transform raw GitHub data into narrative summaries for performance reviews and personal reflection. More recently, I built the Workspace extension for Gemini CLI and a deep research extension that lets you conduct multi-step research from the terminal. Each of these tools was born from a specific itch, and each turned out to be useful to more people than I expected. The Workspace extension alone has gathered 510 stars.

The Rhythm of Building

One thing the contribution graph doesn’t capture is the rhythm behind the numbers. My weeks developed a cadence over the year that I didn’t plan but that emerged naturally. Mornings were for deep work on Gemini CLI, the kind of focused system design and implementation that benefits from a fresh mind. Afternoons were for reviews and community work, responding to issues, providing feedback on PRs, and engaging with the developers building on top of our tools. Evenings and weekends were where the personal projects lived: Gemini Scribe, the extensions, and whatever new idea was rattling around in my head.

This rhythm is something I couldn’t have had in my previous role. When your calendar is stacked with meetings from nine to five, the creative work gets squeezed into the margins. Now, the creative work is the whole page. That’s the real story behind 4,255 contributions. It’s not about productivity metrics or GitHub gamification. It’s about what happens when you align your time with the work that energizes you.

What Surprised Me

A few things caught me off guard when I looked back at the year.

First, the ratio of code to “everything else” wasn’t what I expected. I assumed the majority of my contributions would be commits. In reality, a massive portion was reviews, comments, and issue management. On Gemini CLI alone I logged 205 reviews over the year. This was especially true as my role on that project evolved from pure contributor to something closer to a technical steward. Reviewing a complex PR, asking the right questions, and helping someone refine their approach takes just as much skill as writing the code yourself. Sometimes more.

Second, the personal projects had more reach than I anticipated. When I wrote about building personal software, I was mostly thinking about tools I built for myself. But Gemini Scribe has real users who file real bugs and request real features. The Workspace extension took off because it solved a problem that a lot of Gemini CLI users were hitting. Building in the open means you discover an audience you didn’t know was there.

Third, and this is the one I keep coming back to, the year felt shorter than 4,255 contributions would suggest. Flow state compresses time. When you’re deep in a problem, hours feel like minutes. I remember entire weekends spent in the codebase that felt like an afternoon. That compression is, for me, the clearest signal that I made the right call in going back to IC work.

Fourth, and this is the one I never would have predicted until I charted it out: the weekend, not the weekday, turned out to be my most productive window by a wide margin. Saturdays averaged 14.7 contributions, Sundays 14.5, and Thursday, the day I’d have guessed was safest, came in last at 8.3. The busiest single day of the entire year was a Saturday, December 20, when I shipped 89 contributions into podcast-rag, rebuilding the web upload flow, adding episode management to the admin dashboard, and migrating email delivery over to Resend, all in one afternoon. I didn’t plan for the weekends to become the engine. They just did, because that’s where the personal projects live, and the personal projects are where the work is loudest, most direct, and most free of interruption. A day with no meetings on it, I’ve come to realize, is worth more than I ever gave it credit for.

Looking Forward

I don’t know what next year’s number will be, and I’m not particularly interested in making it bigger. The number is a side effect, not a goal. What I care about is continuing to work on problems that matter, in the open, with people who push me to think more clearly. The AI-first developer model I wrote about over a year ago is now just how I work every day. The agents I’m building are the collaborators I’m building with, and both keep getting better.

If you’re someone who’s been thinking about a similar shift, whether it’s moving back to IC work, contributing to open source, or just carving out more time for the work that lights you up, I’d encourage you to try it. You might be surprised by what a year of focused building can produce. I certainly was.

A woodworker's workbench viewed from above, with traditional hand tools like chisels, planes, and saws arranged alongside a glowing tablet and a translucent holographic AI interface. In the center, a beautifully carved wooden book lies open, its pages showing handwritten text and illustrations. Wood shavings curl around the edges, and warm golden afternoon light streams through a workshop window.

It’s a Poor Craftsman Who Blames His Tools

A craftsman’s workbench where traditional tools meet digital ones, a carved wooden book at its center. The old and new sit side by side, each essential to the work.

I was around eight years old, standing in front of my third grade class, holding a neatly printed report on Scotland. We had an IBM PC Jr. in the house, one of the few families in the neighborhood who did, and I’d used its word processor to write my family heritage project. When a classmate asked how I’d made it, I told him the truth. I did it on the computer. His response was immediate: “That’s cheating. All you had to do was tell the computer to write everything about Scotland.”

That moment has stuck with me for over forty years. Not because my classmate was right, but because his assumption felt so deeply unfair. He looked at the output, saw something cleaner than what he could produce by hand, and concluded that the tool had done the thinking. The work I’d put in, the reading, the organizing, the rewriting, became invisible the moment the medium changed.

I thought about that kid this week when Hachette pulled Mia Ballard’s horror novel Shy Girl after AI detection tools flagged up to 78% of the text as machine-generated. The story has all the ingredients of a proper scandal: a buzzy debut, a viral three-hour YouTube analysis that racked up over a million views, an author claiming an editor used AI without her knowledge, and a publisher left scrambling to explain how it slipped through. I don’t know whether Ballard used AI to write her book, and this post isn’t about relitigating her specific case. What interests me is the reaction. The assumptions that surfaced, the lines people drew, and what those lines tell us about what we actually value in creative work.

We’ve Been Here Before

The outrage around AI-generated books carries an implicit premise: that the value of a book is inseparable from the human effort of writing it word by word. But we’ve never really believed that, have we?

Ghostwriting is one of the oldest traditions in publishing. The term itself was coined in 1921 by Christy Walsh, but the practice predates the modern publishing industry entirely. Ancient Greek and Roman scribes wrote speeches for public figures. In the early 1900s, Edward Stratemeyer built a literary empire by creating plot outlines for children’s book series and hiring writers to turn them into finished novels. The Nancy Drew books, beloved by generations of readers, were written by a rotating cast of ghostwriters under the pseudonym Carolyn Keene.

Today, estimates suggest that 60 to 80 percent of business and self-help nonfiction is ghostwritten or co-written. More than 80 percent of celebrity memoirs are ghostwritten. Andre Agassi’s autobiography, widely praised as one of the best sports memoirs ever written, was ghostwritten by Pulitzer Prize winner J.R. Moehringer. JFK’s Profiles in Courage won a Pulitzer Prize despite strong evidence that Ted Sorensen wrote most of it.

We know all of this, and we’ve made our peace with it. When you pick up a celebrity memoir, you don’t assume the celebrity sat alone in a cabin writing every sentence. You assume there was a collaborator, and you’re fine with it, because the ideas, the stories, the perspective still belong to the person whose name is on the cover.

So what exactly changes when the collaborator is a machine?

The obvious objection is that a ghostwriter is a human being. Moehringer spent months interviewing Agassi, made narrative choices rooted in empathy and decades of craft, brought the full weight of his own life experience to the project. Today’s language models do none of that. They have no understanding of their subject, no lived experience to draw on, no creative judgment in any meaningful sense. That’s a real difference today, and I don’t want to pretend otherwise. Whether it remains a permanent difference is a question none of us can answer yet.

But here’s what I keep coming back to: the reader’s relationship to the work is the same either way. You don’t read Agassi’s memoir for Moehringer’s prose style. You read it for Agassi’s story, told well. The question for the reader was always “is this book worth my time,” not “who exactly arranged these sentences, and what was their inner life like while they did it.” The collaborator changed. The contract between author and reader didn’t.

The Real Question

The Guardian piece on the Shy Girl controversy quotes Mor Naaman, a professor of information science at Cornell Tech, asking a question I think cuts to the heart of this: “We all work in an AI-hybrid world now. When does something become an AI-generated book, rather than just using AI like I use a spellchecker, to fix my grammar or maybe spark ideas?”

This is the right question, and I don’t think it has a clean answer. The spectrum of AI assistance in writing is wide and continuous. At one end, your word processor underlines a misspelled word and you accept the correction. Nobody calls that cheating. At the other end, someone pastes “write me a horror novel” into ChatGPT and submits whatever comes back. That feels like fraud. But between those two poles is a vast grey area where most of us who write with AI actually live.

I write extensively with AI, and in the interest of practicing what I preach, let me tell you exactly how this post was made. I read the Guardian article in my feed reader. I opened up an AI conversation, dropped in a link to the piece along with several hundred words of reaction, half-formed arguments, and personal anecdotes. Then I went back and forth with the AI to build out the structure and prose. I read and edited the draft several times. I then used the AI to find weaknesses in my own arguments, and methodically worked through each one, discussing my views and refining the piece as we went.

Every idea in this post is mine. The IBM PC Jr. story is mine, pulled from a memory no language model has access to. The ghostwriting parallel, the monoculture pushback, the phonograph analogy, all mine. But the process of turning that raw material into the thing you’re reading right now involved a collaboration that didn’t exist five years ago. So, is this an AI-generated blog post? I’d say no. But I couldn’t have written it this way without AI, and I don’t see any reason to pretend otherwise.

Here’s the thing that doesn’t get said enough: I would not have written this piece at all five years ago. Not because I didn’t have opinions, but because I didn’t have the time, and the tools available to me didn’t match the way I think. I’m a conversational thinker. I work through ideas by talking them out, testing them, pushing back on them. Five years ago, I would have read the Guardian story, thought “I disagree,” and moved on with my day. AI gave me a way to turn that disagreement into something I could share. It didn’t lower the quality of my writing. It made the writing possible in the first place.

I’ve written before about how AI is changing the craft of software engineering, and I see the same dynamics playing out in writing. In my open source projects, I have no problem with AI-generated code. What I care about is whether the author has tested it, understands it, and can vouch for its quality. The tool doesn’t matter. The accountability does.

Authorship in other creative work should be no different. The judgment should be reserved for the end product. We should hold authors accountable for delivering something we want to read and that we enjoy reading. There were plenty of formulaic, poorly written books on the shelves before AI, and there will be more after authors embrace it. It’s not the tools. It’s the people and how they use them.

The Monoculture Was Already Here

The other argument that comes up in these conversations is that AI will drive us toward a cultural monoculture, a flattening of creative output into algorithmically averaged blandness. Naaman makes this case in the Guardian piece, warning that “AI nudges users into a bland monoculture” and that it “could never generate the truly diverse creativity of the human mind.”

I have two responses to that.

First, we’ve been driving toward monoculture for a long time, well before AI had anything to do with it. Walk down the high street of any major city in the world and tell me what brands you see. Are they really that different from London to Tokyo to São Paulo? How about the movies in the theater or the books on the international bestseller lists? Globalization and the internet have been blending and merging cultures for decades. AI isn’t the cause of our growing monoculture. It’s a reflection of it. We are all more connected today than we have ever been, and that connectivity, for all its benefits, inevitably smooths out some of the edges. Can AI accelerate that flattening? Absolutely. If millions of writers lean on the same models trained on the same corpus, the pull toward sameness is real. I don’t want to minimize that risk. But the answer is better use of the tool, not rejection of it.

And this is the more important point for writers: AI is a prediction engine. Given a sequence of text, it generates the statistically most likely next token. Left to its own devices, it will absolutely produce the most average, most expected, most median version of whatever you’re writing. That’s not a flaw in the technology. It’s a feature you have to work against. It’s up to the author to bring their own voice, their own weird obsessions, their own hard-won perspective into that conversation and make it interesting. The AI will always pull you toward the center. Your job as a writer is to pull it toward the edges.

This is no different from any other creative tool. A guitar doesn’t make you a musician. A camera doesn’t make you a photographer. And a language model doesn’t make you a writer. But in the hands of someone who knows what they want to say and has the skill to shape the output, all of these tools can produce extraordinary work.

I keep thinking about the early days of digital photography. When digital cameras started displacing film, there was a vocal community of film purists who insisted that you weren’t a real photographer unless you were shooting on film. Digital made it too easy, they argued. You could fire off hundreds of frames and increase your odds of getting a lucky shot, rather than developing the discipline to compose and expose a single frame correctly. The process was the point, they said. The limitation was what made it art.

Sound familiar? We’re hearing the same argument about AI and writing now. The tool lowers the barrier, so the gatekeepers question whether the output counts. But digital cameras didn’t kill photography. They democratized it. They opened the craft to millions of people who would never have had access to a darkroom, and the best photographers in the world today shoot digital without anyone questioning their artistry. What matters is the image, not the medium it was captured on.

Judge the Work

There is a legitimate concern buried in this debate that I don’t want to dismiss. If AI takes over the entry-level writing work (the copywriting gigs, the formula genre fiction, the content mill assignments), where do emerging authors get their reps? If the lower rungs of the ladder disappear, do we end up with fewer masters at the top?

It’s a fair question, but it’s not a new one. When the phonograph and the player piano arrived in the early 1900s, they devastated the livelihoods of working musicians. Every restaurant and pub used to have live music because there was simply no other way to fill a room with sound. Those technologies thinned the ranks dramatically, and what followed was a greater concentration of attention on the most talented performers. They weren’t playing the pubs anymore. They were playing the concert halls. Then radio and recorded music concentrated things further still. John Philip Sousa warned in 1906 that mechanical music would be the death of the art form.

He was wrong, of course. The tipping point, in my opinion, came with the democratization of music-making and music-publishing tools. GarageBand, SoundCloud, Bandcamp, Spotify for independent artists. The pub gigs never came back, but the ability to create and distribute music became more accessible than it had ever been. The pipeline didn’t disappear. It changed shape entirely.

I think writing is headed somewhere similar. The old entry points may shrink, but AI is simultaneously creating new ones. Editing AI output, directing it, curating it, knowing how to coax something genuinely good out of a collaboration with a machine. These are new skills, and they’re the early rungs of a different ladder.

There’s a saying in woodworking: it’s a poor craftsman who blames his tools. I think we need to keep that wisdom close as this debate continues. If Shy Girl turns out to be a case of someone submitting raw machine output as their own creative work, then the problem isn’t that AI was involved. The problem is that the author didn’t do the work. But if it turns out to be something messier, a human writer who leaned on AI more than the industry is currently comfortable with, then we need to ask ourselves what exactly we’re punishing and why.

But let’s not make the same mistake my third grade classmate made. Let’s not look at the tool and assume it did all the thinking. The question isn’t whether an author used AI. The question is whether they wrote something worth reading.

A beam of white light enters a translucent geometric crystal and refracts into three distinct colored beams — red, green, and blue — each passing through a different abstract geometric shape against a dark navy background.

MCP Isn’t Dead You Just Aren’t the Target Audience

I was debugging a connection issue between Gemini Scribe and the Google Calendar integration in my Workspace MCP server last month when a friend sent me a link. “Have you seen this? MCP is dead apparently.” It was Eric Holmes’ post, MCP is dead. Long live the CLI, which had just hit the top of Hacker News. I read it while waiting for a server restart, which felt appropriate.

His argument is clean and persuasive: CLI tools are simpler, more reliable, and battle-tested. LLMs are trained on millions of man pages and Stack Overflow answers, so they already know how to use gh and kubectl and aws. MCP introduces flaky server processes, opinionated authentication, and an all-or-nothing permissions model. His conclusion is that companies should ship a good API, then a good CLI, and skip MCP entirely.

I agree with about half of that. And the half I agree with is the part that doesn’t matter.

The Shell is a Privilege

Holmes is writing from the perspective of a developer sitting in a terminal. From that vantage point, everything he says is correct. If your agent is Claude Code or Gemini CLI, running in a shell session on your laptop with your credentials loaded, then yes, gh pr view is faster and more capable than any MCP wrapper around the GitHub API. I made exactly this observation in my own post on the Internet of Agents. Simon Willison said as much in his year-end review, noting that for coding agents, “the best possible tool for any situation is Bash.”

But here’s the thing: not every agent has a shell. And not every agent is an interactive coding assistant.

I wrote in Everything Becomes an Agent that the agentic pattern is showing up everywhere: classifiers that need to call tools, data pipelines that need to make decisions, background processes that orchestrate workflows without a human watching. The “MCP is dead” argument treats agents as though they are all developer tools running in a terminal session. That’s one pattern, and it’s the pattern that gets the most attention because developers are writing the blog posts. But the agentic shift is much broader than that.

I’ve been building Gemini Scribe for nearly a year and a half now. It’s an AI agent that lives inside Obsidian, a note-taking application built on Electron. On desktop, Gemini Scribe runs in the renderer process of a sandboxed app. It has no terminal. It has no $PATH. It cannot reliably shell out to gh or kubectl or anything else. Its entire world is the Obsidian plugin API, the vault on disk, and whatever external capabilities I wire up for it. And on mobile, the constraints are even tighter. Obsidian runs on iOS and Android, where there is no shell at all, no subprocess spawning, no local binary execution. The app sandbox on mobile is absolute. If your answer to “how does an agent use tools?” begins with “just call the CLI,” you’ve already lost half your user base.

When I wanted Gemini Scribe to be able to read my Google Calendar, search my email, or pull context from Google Drive, I didn’t have the option of “just use the CLI.” There is no gcal CLI that runs inside a browser runtime. There is no gmail binary I can spawn from an Electron sandbox, let alone from an iPhone. MCP gave me a way to expose those capabilities through a protocol that works over stdio or HTTP, regardless of where my agent happens to be running.

The same is true of my Podcast RAG system. The query agent runs on the server, orchestrating retrieval, re-ranking, and synthesis in a Python process that has no interactive shell session. I could wire up every capability as a bespoke function call, and in some cases I do. But when I want that same retrieval pipeline to be accessible from Gemini CLI on my laptop, from Gemini Scribe in Obsidian, and from the web frontend, MCP gives me one implementation that serves all three. The alternative is writing and maintaining three separate integration layers.

Or consider a less obvious case: a background agent that monitors a codebase for security vulnerabilities and files tickets when it finds them. This agent runs on a schedule, not in response to a human typing a command. It needs to read files from a repository, query a vulnerability database, and create issues in a project tracker. You could give it a shell, but you shouldn’t. An autonomous agent running unattended with shell access is a privilege escalation vector. A crafted comment in a pull request, a malicious string in a dependency manifest, any of these could become a prompt injection that turns bash into an attack surface. Structured tool protocols are the natural interface for this kind of autonomous workflow precisely because they constrain what the agent can do. The agent gets read_file and create_issue, not bash -c. The narrower the interface, the smaller the blast radius.

The N-by-M Problem Doesn’t Go Away

Holmes frames MCP as solving a problem that doesn’t exist. CLIs already work, so why add a protocol?

But CLIs work for a very specific topology: one human (or one human-like agent) driving one tool at a time through a shell. The moment you step outside that topology, CLIs stop being the answer.

Even if every service had a CLI (and Holmes is right that more should), you still have the consumer problem. A CLI is consumable by exactly one kind of agent: one with shell access. The moment you need that same capability accessible from an Electron plugin, a mobile app, a server-side orchestrator, and a terminal agent, you’re back to writing integration code for each consumer. MCP lets you write the server once and expose it to all of them through a common protocol.

This is the same insight behind LSP, which I wrote about in the context of ACP. Before LSP, every editor had to implement its own Python linter, its own Go formatter, its own TypeScript type-checker. The N-by-M integration problem was a nightmare. LSP didn’t replace the underlying tools. It standardized the interface between the tools and the editors. MCP does the same thing for the interface between capabilities and agents.

Holmes might respond that the N-by-M problem is overstated, that most developers just need one agent talking to a handful of tools. Fair enough for a personal workflow. But the industry isn’t building personal workflows. It’s building platforms where agents need to discover and compose capabilities dynamically, where the set of available tools changes based on the user’s permissions, their organization’s policies, and the context of the current task. That’s the world MCP is designed for.

Authentication is the Feature, Not the Bug

One of Holmes’ sharpest critiques is that MCP is “unnecessarily opinionated about auth.” CLI tools, he notes, use battle-tested flows like gh auth login and AWS SSO that work the same whether a human or an agent is driving.

This is true when the agent is acting as you. But the moment the agent stops acting as you and starts acting on behalf of other people, everything changes.

Imagine you’re building a product where an AI assistant helps your customers manage their calendars. Each customer has their own Google account. You cannot ask each of them to run gcloud auth login in a terminal. You need per-user OAuth tokens, tenant isolation, and an auditable record of every action the agent takes on each user’s behalf. This is not a niche enterprise concern. This is the basic architecture of any multi-tenant agent system.

Or think about something simpler: a shared documentation service protected by OAuth. Your team’s internal knowledge base, your company’s Confluence, your organization’s Google Drive. An agent that needs to search those resources on behalf of a user has to present that user’s credentials, not the developer’s, not a shared service account. This is a solved problem in the web world (every SaaS app does it), but it requires a protocol that understands identity delegation. curl with a hardcoded token doesn’t cut it.

MCP’s authentication specification isn’t trying to replace gh auth login for developers who already have credentials loaded. It’s trying to solve the problem of how an agent running in a hosted environment acquires and manages credentials for users who will never see a terminal. Dismissing this as unnecessary complexity is like dismissing HTTPS because curl works fine over HTTP on your local network.

Where I Actually Agree

I want to be clear that Holmes isn’t wrong about the pain points. MCP server initialization is genuinely flaky. I’ve lost hours to servers that didn’t start, connections that dropped, and state that got corrupted between restarts. The tooling is immature. The debugging experience is terrible. As I wrote in my post on the observability gap, the moment you rely on an agent for something that matters, you realize you’re flying blind. MCP’s opacity makes that worse.

And the context window overhead is real. Benchmarks from ScaleKit show that an MCP agent injecting 43 tool definitions consumed 44,026 tokens before doing any work, while a CLI agent doing the same task needed 1,365. When you’re paying per token, that’s not an abstraction tax you can ignore.

But these are maturity problems, not architecture problems. The early days of LSP were rough too. Language servers crashed, features were spotty, and half the community said “just use the built-in tooling.” The protocol won anyway, because the abstraction was right even when the implementation wasn’t.

The Bridge Pattern

Here’s what I think the mature answer looks like, and it’s neither “use MCP for everything” nor “use CLIs for everything.” It’s building your core capability as a shared library, then exposing it through multiple transports.

Think about how you’d design a tool that queries your internal knowledge base. The business logic (authentication, retrieval, re-ranking) lives in a Python module or a Go package. From that shared core, you generate three thin wrappers. A streaming HTTP MCP server for agents running in web runtimes and hosted environments. A local stdio MCP server for desktop agents like Gemini Scribe or Claude Desktop that communicate over standard input/output. And a CLI binary for developers who want to pipe results through jq or use it from Gemini CLI’s bash tool.

All three share the same code paths. A bug fix in the retrieval logic propagates everywhere. The auth layer adapts to context: the CLI reads your local credentials, the HTTP server handles OAuth tokens, and the stdio server inherits the host process’s permissions. You get the CLI’s simplicity where a shell exists, and MCP’s universality where it doesn’t.

This isn’t hypothetical. It’s what I’m already doing. My gemini-utils library is the shared core: it handles file uploads, deep research, audio transcription, and querying against Gemini’s APIs. It exposes all of that as a set of CLI commands (research, transcribe, query, upload) that I use directly from the terminal every day. But when I wanted those same research capabilities available to Gemini CLI as an agent tool, I built gemini-cli-deep-research, an extension that wraps the same underlying library as an MCP service. The core logic is shared. The CLI is for me at a terminal. The MCP server is for agents that need to invoke deep research as a tool in a larger workflow. Same capability, different transports, each suited to its context.

I think this is the pattern that tool developers should be building toward. The best agent tools of the next few years won’t be “MCP servers” or “CLI tools.” They’ll be capability libraries with multiple faces.

The Real Question

The CLI-vs-MCP debate, as Tobias Pfuetze argued, is the wrong fight. The question isn’t “which is better?” It’s “where does each one belong?”

For a developer in a terminal with their own credentials, driving a coding agent? Use the CLI. It’s faster, cheaper, and the agent already knows how. Holmes is right about that.

For an agent embedded in an application runtime without shell access? For a multi-tenant platform where the agent acts on behalf of users who will never open a terminal? For a system where you need one capability implementation discoverable by multiple heterogeneous agent hosts? That’s where MCP earns its complexity.

And for the tool developer who wants to serve all of these audiences? Build the core once, expose it three ways: CLI, stdio MCP, and streaming HTTP MCP. Let the runtime decide.

The mistake is assuming that because your agent has a shell, every agent has a shell. The terminal is one runtime among many. And as agents move from developer tools into products that serve non-technical users, the fraction of agents that can rely on a $PATH and a .bashrc is going to shrink rapidly.

MCP isn’t dead. It’s just not for you yet. But it might be soon.

A conceptual illustration showing sound waves passing through a prism and refracting into a 3D scatter plot of colored clusters, representing different speaker identities in vector space.

The Fingerprint of Sound

Hero Image Suggestion:

Last year, I spent a lot of time obsessed with the concept of embeddings. I wrote about how they act as a bridge, transforming the messy, unstructured world of human language into a clean, numerical landscape that computers can understand. In my series on the topic, I explored how text embeddings allow us to map concepts in space—how they let us mathematically prove that “king” is close to “queen,” or find a podcast episode about “economic growth” even if the specific keywords never appear in the transcript.

For me, grasping text embeddings was a watershed moment. It turned AI from a black box into a geometry problem I could solve. But recently, my friend Pete Warden released a post that clicked the another piece of the puzzle into place for me, moving that geometry from the page to the ear.

In his post, Speech Embeddings for Engineers, Pete tackles the problem of diarization—the technical term for figuring out “who spoke when” in an audio recording. If you’ve followed my podcast archive project, you know this has been a thorn in my side. I have thousands of transcripts, but they are largely monolithic blocks of text. I know what was said, but often I lose the context of who said it.

Pete’s explanation is brilliant because it leverages the exact same intuition we developed for text. Just as a text embedding captures the semantic “fingerprint” of a sentence, a speech embedding captures the vocal fingerprint of a speaker.

The mental shift is fascinating. When we embed text, we are mapping meaning. We want the vector for “dog” to be close to “puppy” and far from “motorcycle.” But when we embed speech for diarization, we don’t care about the meaning of the words at all. A speaker could be whispering a love sonnet or screaming a grocery list; semantically, those are worlds apart. But acoustically—in terms of timbre, pitch, and cadence—they share an undeniable identity.

Pete includes a Colab notebook that demonstrates this beautifully. It’s a joy to run through because it demystifies the process entirely. He walks you through taking short clips of audio, running them through a model, and visualizing the output.

Suddenly, you aren’t looking at waveforms anymore. You’re looking at clusters. You can see, visually, where one voice ends and another begins. It turns the murky problem of distinguishing speakers in a crowded room into a clean clustering algorithm, something any engineer can wrap their head around.

This reinforces a recurring theme for me: the power of small, composable tools. We often look for massive, end-to-end APIs to solve our problems—a “magic box” that takes audio and returns a perfect script. But understanding the primitives is where the real power lies. By understanding speech embeddings, we aren’t just consumers of a transcription service; we are architects who can build systems that listen, identify, and understand the nuance of conversation.

If you’ve ever wrestled with audio data, or if you just want to see how the concept of embeddings extends beyond text, I highly recommend finding a quiet hour to work through Pete’s notebook. It might just change how you hear the data.

A photorealistic image shows an old wooden-handled hammer on a cluttered workbench transforming into a small, multi-armed mechanical robot with glowing blue eyes, holding various miniature tools.

Everything Becomes an Agent

I’ve noticed a pattern in my coding life. It starts innocently enough. I sit down to write a simple Python script, maybe something to tidy up my Obsidian vault or a quick CLI tool to query an API. “Keep it simple,” I tell myself. “Just input, processing, output.”

But then, the inevitable thought creeps in: It would be cool if the model could decide which file to read based on the user’s question.

Two hours later, I’m not writing a script anymore. I’m writing a while loop. I’m defining a tools array. I’m parsing JSON outputs and handing them back to the model. I’m building memory context windows.

I’m building an agent. Again.

(For those keeping track: my working definition of an “agent” is simple: a model running in a loop with access to tools. I explored this in depth in my Agentic Shift series, but that’s the core of it.)

As I sit here writing this in January of 2026, I realize that almost every AI project I worked on last year ultimately became an agent. It feels like a law of nature: Every AI project, given enough time, converges on becoming an agent. In this post, I want to share some of what I’ve learned, and the cases where you might skip the intermediate steps and jump straight to building an agent.

The Gravitational Pull of Autonomy

This isn’t just feature creep. It’s a fundamental shift in how we interact with software. We are moving past the era of “smart typewriters” and into the era of “digital interns.”

Take Gemini Scribe, my plugin for Obsidian. When I started, it was a glorified chat window. You typed a prompt, it gave you text. Simple. But as I used it, the friction became obvious. If I wanted Scribe to use another note as context for a task, I had to take a specific action, usually creating a link to that note from the one I was working on, to make sure it was considered. I was managing the model’s context manually.

I was the “glue” code. I was the context manager.

The moment I gave Scribe access to the read_file tool, the dynamic changed. Suddenly, I wasn’t micromanaging context; I was giving instructions. “Read the last three meeting notes and draft a summary.” That’s not a chat interaction; that’s a delegation. And to support delegation, the software had to become an agent, capable of planning, executing, and iterating.

From Scripts to Sudoers

The Gemini CLI followed a similar arc. There were many of us on the team experimenting with Gemini on the command line. I was working on iterative refinement, where the model would ask clarifying questions to create deeper artifacts. Others were building the first agentic loops, giving the model the ability to run shell commands.

Once we saw how much the model could do with even basic tools, we were hooked. Suddenly, it wasn’t just talking about code; it was writing and executing it. It could run tests, see the failure, edit the file, and run the tests again. It was eye-opening how much we could get done as a small team.

But with great power comes great anxiety. As I explored in my Agentic Shift post on building guardrails and later in my post about the Policy Engine, I found myself staring at a blinking cursor, terrified that my helpful assistant might accidentally rm -rf my project.

This is the hallmark of the agentic shift: you stop worrying about syntax errors and start worrying about judgment errors. We had to build a “sudoers” file for our AI, a permission system that distinguishes between “read-only exploration” and “destructive action.” You don’t build policy engines for scripts; you build them for agents.

The Classifier That Wanted to Be an Agent

Last year, I learned to recognize a specific code smell: the AI classifier.

In my Podcast RAG project, I wanted users to search across both podcast descriptions and episode transcripts. Different databases, different queries. So I did what felt natural: I built a small classifier using Gemini Flash Lite. It would analyze the user’s question and decide: “Is this a description search or a transcript search?” Then it would call the appropriate function.

It worked. But something nagged at me. I had written a classifier to make a decision that a model is already good at making. Worse, the classifier was brittle. What if the user wanted both? What if their intent was ambiguous? I was encoding my assumptions about user behavior into branching logic, and those assumptions were going to be wrong eventually.

The fix was almost embarrassingly simple. I deleted the classifier and gave the agent two tools: search_descriptions and search_episodes. Now, when a user asks a question, the agent decides which tool (or tools) to use. It can search descriptions first, realize it needs more detail, and then dive into transcripts. It can do both in parallel. It makes the call in context, not based on my pre-programmed heuristics. (You can try it yourself at podcasts.hutchison.org.)

I saw the same pattern in Gemini Scribe. Early versions had elaborate logic for context harvesting, code that tried to predict which notes the user would need based on their current document and conversation history. I was building a decision tree for context, and it was getting unwieldy.

When I moved Scribe to a proper agentic architecture, most of that logic evaporated. The agent didn’t need me to pre-fetch context; it could use a read_file tool to grab what it needed, when it needed it. The complex anticipation logic was replaced by simple, reactive tool calls. The application got simpler and more capable at the same time.

Here’s the heuristic I’ve landed on: If you’re writing if/else logic to decide what the AI should do, you might be building a classifier that wants to be an agent. Deconstruct those branches into tools, give the agent really good descriptions of what those tools can do, and then let the model choose its own adventure.

You might be thinking: “What about routing queries to different models? Surely a classifier makes sense there.” I’m not so sure anymore. Even model routing starts to look like an orchestration problem, and a lightweight orchestrator with tools for accessing different models gives you the same flexibility without the brittleness. The question isn’t whether an agent can make the decision better than your code. It’s whether the agent, with access to the actual data in the moment, can make a decision at least as good as what you’re trying to predict when you’re writing the code. The agent has context you don’t have at development time.

The “Human-on-the-Loop”

We are transitioning from Human-in-the-Loop (where we manually approve every step) to Human-on-the-Loop (where we set the goals and guardrails, but let the system drive).

This shift is driven by a simple desire: we want partners, not just tools. As I wrote back in April about waiting for a true AI coding partner, a tool requires your constant attention. A hammer does nothing unless you swing it. But an agent? An agent can work while you sleep.

This freedom comes with a new responsibility: clarity. If your agent is going to work overnight, you need to make sure it’s working on something productive. You need to be precise about the goal, explicit about the boundaries, and thoughtful about what happens when things go wrong. Without the right guardrails, an agent can get stuck waiting for your input, and you’ll lose that time. Or worse, it can get sidetracked and spend hours on something that wasn’t what you intended.

The goal isn’t to remove the human entirely. It’s to move us from the execution layer to the supervision layer. We set the destination and the boundaries; the agent figures out the route. But we have to set those boundaries well.

Embracing the Complexity (Or Lack Thereof)

Here’s the counterintuitive thing: building an agent isn’t always harder than building a script. Yes, you have to think about loops, tool definitions, and context window management. But as my classifier example showed, an agentic architecture can actually delete complexity. All that brittle branching logic, all those edge cases I was trying to anticipate: gone. Replaced by a model that can reason about what it needs in the moment.

The real complexity isn’t in the code; it’s in the trust. You have to get comfortable with a system that makes decisions you didn’t explicitly program. That’s a different kind of engineering challenge, less about syntax, more about guardrails and judgment.

But the payoff is a system that grows with you. A script does exactly what you wrote it to do, forever. An agent does what you ask it to do, and sometimes finds better ways to do it than you’d considered.

So, if you find yourself staring at your “simple script” and wondering if you should give it a tools definition… just give in. You’re building an agent. It’s inevitable. You might as well enjoy the company.

A close-up photograph on a wooden workbench shows a hand-carved wooden tool handle resting on a MacBook Pro keyboard. The handle transitions into a glowing blue and orange digital wireframe where it extends over the laptop's screen, which displays lines of green code. Wood shavings, chisels, and other traditional tools are scattered around the laptop. A warm desk lamp illuminates the scene from the right.

The Era of Personal Software

I was sitting in a coffee shop this afternoon, nursing a cappuccino and doing a quick triage of the GitHub repositories I maintain. It was supposed to be a quick check-in, but I was surprised to find a pile of issues I hadn’t seen before. They had slipped through the cracks of my notifications.

My immediate reaction wasn’t just annoyance; it was an itch to fix the process. I needed a way to monitor a configurable set of repos and get a consolidated report of new activity—something bespoke. For my smaller projects, I want to see everything. For the big, noisy ones, I only care if I’m assigned or mentioned.

So, I opened up my terminal. I fired up gemini cli and started describing what I needed.

Twenty minutes later, I had a working command-line tool. It did exactly what I described, filtering the noise exactly how I wanted. I ran it, verified the output, and added it to my daily workflow. I closed my laptop and went on with my day.

But on the walk home, I realized something strange had happened. Or rather, something hadn’t happened.

I never opened Google. I never searched GitHub for “activity monitor CLI.” I didn’t spend an hour trawling through “Top 10 GitHub Tools” blog posts, or installing three different utilities only to find out one was deprecated and the other required a subscription.

I just built the thing I needed and moved on.

We are entering the era of Personal Software. This is software written for an audience of one. It’s an application or a script built to solve a specific problem for a specific person, with no immediate intention of scaling, monetizing, or even sharing.

Looking back at my recent work, I realize I’ve been living in this category for a while. In many ways, this is the active evolution of the “Small Tools, Big Ideas” concept I explored earlier this year. Instead of just finding these sharp, focused tools, I’m now building them. Gemini Scribe started because I wanted a better way to write in Obsidian. Podcast Rag exists solely because I wanted to search my own podcast history. My github-activity-reporter from this afternoon? Pure personal necessity. Even adh-cli was just a sandbox for me to test ideas for the Gemini CLI.

We have crossed a threshold where building a bespoke application is often faster—and certainly less frustrating—than finding an off-the-shelf solution that mostly works. The friction of creation has dropped so low that it is now competing with the friction of discovery.

There is a profound freedom in this approach. When you build for an audience of one, the software does exactly what you want and nothing more. There is no feature bloat, no upsell, no UI clutter. You are the product manager, the engineer, and the customer. If your workflow changes next week, you don’t have to file a feature request and hope it gets upvoted; you just change the code. You don’t have to convince anyone else that your problem is worth solving.

But this freedom comes with a new kind of responsibility. When you step outside the walled garden of managed software, you are on your own. If you get stuck, there is no support ticket to file. If an API changes and breaks your tool, you are on the hook to fix it.

There is also the “trap of success.” Sometimes, your personal software is so useful that it accidentally becomes non-personal. Friends ask for it. Colleagues want to fork it. Suddenly, you aren’t just a user anymore; you’re a maintainer. You have to decide if you’re willing to take on the burden of supporting others, or if you’re comfortable saying, “This works for me, good luck to you.”

Not every problem is a nail for this particular hammer, of course. Over time, I’ve started to develop a rubric for what makes for good Personal Software.

The sweet spot is usually glue and logic. If you need to connect two APIs that don’t talk to each other, or parse a messy data export into a clean report, AI can write that script in seconds. My GitHub activity reporter is a perfect example: it’s just fetching data, filtering it against my specific rules, and printing text.

It’s also great for ephemeral workflows. If you have a task you need to do fifty times today but might never do again—like renaming a batch of files based on their content or scraping a specific webpage for research—building a throwaway tool is vastly superior to doing it manually.

Another fantastic category is quick web applications. We used to think of web apps as heavy projects requiring frameworks and hosting headaches. But modern platforms like Google Cloud Run or Vercel have made deployment trivial. Tools like Google AI Studio take this even further—offering a free “vibe coding” platform that can take you from a rough idea to a hosted application in minutes. My boxing workout app is a prime example: I didn’t write a line of infrastructure code; I just described the workout timer I needed, and it was live before I even put on my gloves.

Where Personal Software falls short is in infrastructure and security. I wouldn’t build my own password manager or roll my own encryption tools, no matter how good the model is. The stakes are too high, and the “audience of one” means there are no other eyes on the code to catch critical vulnerabilities. Similarly, if a problem requires a complex, interactive GUI or high-availability hosting, the maintenance burden usually outweighs the benefits of customization.

Despite the downsides, I find this shift fascinating. For decades, software development was an industrial process—building generic tools for mass consumption. Now, it’s becoming a craft again. We are returning to a time where we build our own tools, fitting the handle perfectly to our own grip.

So, I want to turn the question over to you. What are you building just for yourself? Are there small, nagging problems you’ve solved with a script only you will ever see? I’d love to hear about the kinds of personal software you’re creating in this new era. Let me know in the comments or reach out—I’m genuinely curious to see what handles you’re crafting.

A manager, dressed in a suit, stands in the center conducting a "jazz band" composed of three glowing, transparent blue AI agents playing a piano, trumpet, and holding sheet music, with a human bass player in the background. The background is dark blue with subtle lines of code and glowing dots.

The Manager’s Edge in the Age of AI

I was catching up with an old friend last week when I shared a hypothesis that’s been on my mind: people who have experience managing, coaching, or directing others might have a surprising advantage in the age of AI. He encouraged me to write about it, and here we are. The core of the idea is this: there’s a subtle art to getting the best out of people, and I’m beginning to believe the same is true for getting the best out of our AI partners.

It’s a strange disconnect I’m seeing everywhere. For all the buzz, a surprising number of companies are struggling to turn their AI experiments into real, lasting value. The latest McKinsey Global Survey on AI found that while about three-quarters of organizations are using generative AI, many pilots are stalling out. And it turns out, the problem usually isn’t the tech. The Boston Consulting Group (BCG) puts it bluntly with their “10-20-70 rule”: AI success is about 10% algorithms, 20% technology, and a massive 70% people and processes. It’s a leadership challenge, not a technical one. A people problem, not a silicon one.

This brings me back to my hypothesis. My gut tells me that successful managers have a head start in this new world because we’ve been trained—formally or through the hard school of experience—to be ruthlessly clear in our communication. We learn to define what success looks like, provide guardrails for the work, and then guide it with a steady hand through gentle correction and continuous feedback.

I’ve seen this play out as talented developers try to adopt these new tools. Many struggle, but I’ve noticed a recurring pattern: they fail to give the model enough context, get a generic or wrong answer, and walk away thinking the AI isn’t smart enough.

When this generation of LLMs first arrived, it was natural to treat them like a search engine—ask a simple question and expect a perfect answer. But that’s like walking up to a new junior engineer on your team, saying, “Build me a login system,” and expecting a production-ready feature a week later. You’d never do that.

You’d give them architectural documents, point them to existing libraries, explain the security requirements, and set up regular check-ins. You’d provide the context, the constraints, and the success criteria.

This is the heart of the matter. The very same principles apply when working with an AI. A vague prompt like, “Write a blog post on prompt engineering,” will produce a generic, soulless article. But a more “managerial” prompt changes the game entirely: “Synthesize a 2,000-word blog post on prompt engineering using the research and references provided in this document. Here is an outline to follow. Ensure the tone and style match these three writing samples.”

Suddenly, you’re not just a user asking a question. You are a manager setting a clear direction. What we call “prompt engineering” is, in many ways, a new form of management. The research community is even starting to use a more fitting term: “context engineering“—the strategic curation of the entire information environment in which the AI operates.

When you see it this way, the parallels between managing people and directing AI are impossible to ignore. A manager’s ability to articulate a clear vision becomes the skill of crafting a precise prompt. The strategic delegation of tasks becomes the art of defining the AI’s role, deciding which work is best for the model and which needs a human’s creative or empathetic judgment. And the rhythm of performance management—monitoring progress and giving feedback—is a perfect mirror of the iterative AI workflow, where we critically evaluate an output and refine our prompts to get closer to the goal.

This isn’t to say that every developer needs to become a people manager. Of course, many ICs are brilliant communicators, but the daily work of management is a constant exercise in clarity and context-setting. It strongly suggests that the “soft skills” of management—clarity, context-setting, and iterative feedback—are becoming the new essential “hard skills” for the AI-first era. Our job is no longer just to write the code, but to effectively guide the intelligence that will help us write it. We are all becoming managers of a different kind of mind.

This is just the beginning, and it points to a powerful new workflow. As Simon Willison has observed, models are getting remarkably good at writing prompts themselves. This indicates that prompt creation itself is a task perfectly suited for AI. So here is the call to action: before you dive into solving a complex problem, make your first step a collaboration with an AI to build the perfect prompt for the job. This is the next layer of abstraction. We are moving beyond simply giving instructions to strategizing with our AI partners about what the best instructions should be. The core managerial skill of setting a clear, high-level mission remains—only now, we’re applying it to the meta-task of designing the conversation itself.