A large monolithic glass building splitting apart along clean seams into smaller connected structures.

The Test Suite Is the New Code Review

Some mornings I watch a pull request move through our whole pipeline before I’ve finished my coffee. An agent opens it. A second agent reviews it. By the time I sit down, the only thing still running is the test suite. That gap, the minutes between “reviewed” and “safe to merge,” has become the slowest part of how we ship.

This is the third post in a series about how Chris and I build Vycari. The first was about the issue tracker and what belongs in it once agents do the work. The second was about the pipeline that works through the tracker and never merges its own pull requests. This one is about what those pull requests wait on, and how the answer changed the shape of the code.

It didn’t used to be the tests. Plenty of us optimized CI over the years, but we optimized it by tiering it. A few minutes of unit tests at your desk before you pushed. A longer run in CI once you did. A slow integration suite that went off every few hours, or overnight. When it came back red, you bisected your way to whichever pull request broke it. That held up because a human was going to review the change anyway, and that took an hour or two at best.

Agents changed that arithmetic. One agent opens a pull request. Greptile reviews it in a couple of minutes. Human and machine are both waiting on tests now. If the full suite takes thirteen minutes, that’s thirteen minutes with nothing else in the way. Multiply that by the pull requests moving through Pepper on a given day and you need a merge queue to keep them from stepping on each other. I’ve finished a change by hand, pushed it, and found myself forty minutes behind a queue of agent pull requests, each one blocked on the same thirteen minutes. Before agents I would never have set up a merge queue for a two-person project. The idea would have been funny. It’s now part of my standard repository setup.

The tiering trick can’t save us either. We still run a slow suite overnight, but it can’t stand between a bug and production, because we ship on green, or near enough: every merge builds an image, and production picks up whatever is current on a fifteen-minute cycle. Call it half an hour from merge to running, and that’s the whole window I have to find out something is wrong. A suite reporting at midnight would be telling me about a bug I shipped at nine that morning, with a few hundred agent pull requests stacked on top of it. Twenty minutes of CI used to be unremarkable. It feels like an eternity now, because everything around it got faster and it didn’t.

The Repository Gets Harder to Hold

Making the suite faster buys time without touching the deeper problem: the repository keeps getting harder to reason about as it grows. A human engineer joining a large codebase builds intuition for its boundaries over weeks. They learn which corners are scary and which are safe to touch without asking anyone. An agent doesn’t get that runway. It arrives cold on every task, reading whatever context you’ve given it and inferring the rest from the code next to it. The bigger the repository, the more code there is to infer from, and the easier it is to miss a contract something else depends on.

I tried to buy that intuition back with context files: hierarchical instructions at every level of the tree, skills describing the traps in one corner of the code. All of it helps, and none of it is free. Every context file is one more thing to write and keep current. Even a well-oriented agent has to hold the whole shape of the repository in its head to change anything that crosses a boundary.

None of this means monorepos are a mistake. They’re a fine pattern for people who already know where the walls are. The problem is narrower: a monorepo is a bad pattern for a team where most of the work gets done by something that relearns the walls on every task.

Pulling the Slice Out

The fix I’ve settled on is structural. When a piece of functionality is clean enough to stand on its own, I pull it out into its own repository before it tangles into everything else. It gets its own tests, its own merge queue, its own release cadence. An agent working inside it can hold the whole thing in its head.

We already have a pattern for this, and it’s older than any of the AI tooling. Amazon has run this way since Jeff Bezos mandated that every team expose its data through service interfaces, with no direct linking and no reading another team’s data store. Steve Yegge described that mandate in 2011, and the detail everyone remembers is that non-compliance meant termination. Microservices came out of an organizational problem before they were a technical one: teams that couldn’t see inside each other needed real contracts. I’m solving that problem, except the team members who can’t see inside each other are models.

The first slice I pulled out was groups, the part of Pepper that handles shared context between people. Working on groups was much faster than working on Pepper itself, and the reason was almost embarrassingly simple. The test suite was only testing the group stuff. Nothing about calendars, nothing about the agent loop, nothing about the web client. A pull request in groups clears CI in about two minutes, against thirteen for the same work in the monorepo, and it has held at two minutes as the code grew. The old number survives in one place: a push to main, where the fuller job set builds the image and can take fifteen, after the merge instead of in front of it.

I don’t have a rule yet for when a slice is ready to leave the monorepo. Right now I go by feel: one clear job, one owner, and an API narrow enough to describe in a sentence. Pull it too early and you’ve built a service around something that still needs to change shape. Pull it too late and it’s already tangled into three other things. I’ve been wrong in both directions. And one slice out is not a track record. The first extraction is always the cleanest. Ask me again at six services, after the costs have compounded.

Anthropic ran into the same problem from the other direction and wrote up what agentic coding did to their CI. Their engineers ship eight times as much code per quarter as they used to, and Claude writes eighty percent of it. CI jobs grew twenty-five fold in six months on headcount that barely moved. Their answer is test impact analysis: keep the large codebase, and run only the tests a change could plausibly affect. If I had a team to maintain something like that, I’d build it. I don’t. Test selection is a system somebody has to own and keep correct, and at two people every system we own competes with the product. Splitting the repository buys most of the same speed by construction, and it fixes the context problem as well. At Anthropic’s size I would almost certainly choose the way they went.

There’s a principle underneath this. I want each of our Python codebases to look like every other open source Python codebase that exists. Not because I’m attached to convention for its own sake, but because a model has seen a million repositories shaped that way. It has seen almost none shaped like a sprawling internal monorepo with its own rules. A small, conventional repository is legible to an agent before it reads a single context file.

The Boundary Has to Be Real

Splitting only pays off if the interfaces are real. A boundary with a vague interface behind it is worse than no boundary. The coupling is still there and now you can’t see it. Google got this right early with protocol buffers: one schema everybody compiles against, changes that stay backward compatible, and no ambiguity about what a service accepts or returns. We’re doing a smaller version. Each service has a defined interface and the calls run over a fast internal Docker network. The interface is what we argue about in review, not the implementation behind it. For an agent working in one repository, that contract is the whole surface it has to respect.

So what catches the bugs that live between services, now that no single suite covers all of it? Three things, in descending order of respectability. The contract, which is why folding groups back into Pepper took a morning. A browser suite that runs overnight against the real application. And the fact that two of us live in the product all day, so anything broken between services turns up in our own use within hours. That last one is not a strategy. It’s what you get at two people with no customers, and it’s the first thing to break when either number changes.

Where this ends up, if I follow it through, is a web application in its own repository, the clients grouped with their browser tests, and a Pepper repository that is just the agent and its tools. That last part is the point. The thing that makes Pepper Pepper should be small enough that an agent can hold all of it at once.

The tradeoff shows up in cross-cutting work: features that used to be one pull request in the monorepo are now a change spread across several repositories. Three repositories means three pipelines and three cold starts, so total compute went up even as the wait in front of any one change came down. Most work happens inside a single repository, so I’m happy to pay it. My first answer was to check out every relevant repository into one parent directory and start an agent there, with all of them in view at once. That parent directory has since become a repository of its own.

One Tech Lead, Many Repositories

For anything that spans repositories, I start what I call my UTL agent, after the old Google title Uber Tech Lead. A UTL was senior and broad enough to operate across systems without owning any of them. Mine runs on Fable 5.1, the strongest model I have for this kind of judgment, though the role matters more than the model filling it.

The UTL lives in that parent directory, which is now a meta-repository. It checks out every other repository underneath it, carries the shared skills, and is growing an operations plugin that knows how to talk to production. The pipeline from the last post runs from there too, one pull request per repository per tick. Making that work meant a consistency pass across every repository: same CI, same branch protection, same conventions. If the plan is to keep splitting, creating a new repository has to be easy, or the friction becomes the reason you don’t.

The UTL almost never writes code. It’s instructed to delegate, and I’ve been strict about that, because the moment it starts editing files itself I’ve lost the thing I built it for. It’s my emissary to the agentic army. I talk to the UTL, the UTL talks to the subagents, and the subagents write the code.

What it has instead is a toolbox of specialized subagents covering the stages of our development cycle, and judgment about which to launch when. Opus for design. Sonnet, Luna, or Flash for implementation, depending on how much thinking the work needs. Opus again for responding to code review, because arguing with a reviewer about whether a finding is right takes more judgment than the code did. For work that spans repositories, the design pass produces a plan covering all of them. What changes where, in what order, and which pieces can move in parallel. Then the implementation agents go out, one per repository, each carrying a slice of that plan. What’s left is bookkeeping: tracking every pull request it spawned until each one lands, and knowing which are blocked on another repo’s change merging first.

What This Costs

None of this is free. Splitting a monorepo trades one kind of difficulty for another.

The first cost is in your own head. You have to decide which service a piece of work belongs in before starting it. A monorepo never charges you that tax. The second cost is reliability. Repositories that used to fail together as one deployable now fail independently. You have to know which parts of your system can take another part down with them, and build the ones that can’t afford it differently. We haven’t had that failure yet, so this cost is still theoretical for us. The third cost is coordination that never goes away. Shared libraries need update schedules, API contracts need versioning, and a change that used to be one pull request can turn into three, timed so none of them ships broken.

There’s a fourth cost. This is a two-person company that hasn’t launched yet, and repository architecture is not a feature. Somewhere in the middle of the split I asked Chris whether I was faffing around with infrastructure instead of building the product. His answer was that building on a bad foundation just eats you with low velocity, and the point of doing it now is that we never have to do it while customers are watching. He also pointed at something I hadn’t seen. When we hire, a bounded repository is the thing you can hand a new engineer whole. Here’s the interface, here’s everything it needs to do, go. That’s a much better first week than a tour of a monorepo’s cryptic corners.

I’ve paid all four, and I’d pay them again. The alternative, one repository that keeps absorbing everything, doesn’t remove that complexity. It hides it inside a single directory tree until an agent runs into it blind, which is a worse way to find out.

Where the Gate Sits Now

Code review used to be the thing everyone waited on. It was slow, and it was where a second set of eyes actually caught something, so everything else could afford to be slow too.

Agentic review took that bottleneck away almost entirely. A pull request gets opened and reviewed in the time it takes me to read the agent’s description and the parts of the diff I care about. We’re getting closer to letting the pipeline merge its own work, which would retire the rule I wrote about last time. What’s left waiting after that is the machinery underneath: the test suite, the merge queue, the deploy pipeline. Thirteen minutes doesn’t sound like a long time, until it’s the only thing standing between one agent finishing a task and the next one starting. Repeat that across every pull request a day produces, and thirteen minutes stops being a rounding error.

The other side of that machinery is where the split pays off. In a small repository, a feature can go from an idea to running in the product in about half an hour, and the only thing that half hour depends on is CI. In the monorepo, the same feature waited on tests for code it never touched.

That’s the argument for breaking the monorepo apart. The gating factor moved from human judgment to compute time, which is the one delay in this system that’s engineering-tractable. You can’t make a human review faster without asking less of them. You can make a test suite faster by shrinking what it has to know about, which is what a small repository does. I wrote in the first post about the tracker that decides what agents can touch. The repository boundary matters just as much, and it’s the one I spend most of my architecture time on now.

What’s Next

The next experiment follows from the way the UTL already works. If it can plan and delegate across repositories, it should be able to delegate the tech-lead role itself. The plan is to stand up a second, cheaper tech lead for one migration, run it to parity on its own repository, and have it escalate two things: decisions that need a human, and anything that touches production. If that works, Chris and I spend most of our engineering time on design documents rather than pull requests, and the design document becomes the real unit of work. Chris’s caveat, which I think is right, is that user experience is the part you can’t specify up front. That’s the honest limit on all of this, and probably a later post.

A mostly empty library card catalog with a few cards left in one open drawer, lit by morning light.

The Backlog Was a Coping Mechanism

We gave our coding agents a rule that sounded obviously correct. No follow-up left behind. It went into our agent context files in roughly those words: anything a pull request doesn’t fully address gets filed as an issue before the review closes. No deferred cleanup sitting in a comment nobody reads again, no “we should probably handle this properly at some point” evaporating the moment the branch merges. Every loose thread becomes a tracked one, and the agents were good at following it.

The rule worked exactly as designed. That was the problem. Take the last month, which is the first real stretch where I’ve been full time on the product. In those 30 days the two of us merged 657 pull requests on our main repository, close to 30 on a typical weekday. Every one of them came out the other end of review with its own small pile of follow-ups. Honest ones, well written, entirely legitimate.

Over the same 30 days we opened 449 issues. Fifteen a day, most of them filed by our own agents rather than typed by us. Nothing in the system closed the loop. The list only grew, and it grew faster the more time I gave it.

This is the first post in a series about how the two of us actually build Vycari, which I promised when I wrote about why we started the company. I want to start here, with the issue tracker, because it’s the piece of the toolchain I had thought about the least and had to change the most. Almost everything I believed about what belongs in a bug database turned out to be a belief about human limitations rather than about software.

What We Were Really Doing When We Filed a Bug

Think about why you file an issue. You have an idea for something the project should do, and you write it down so it doesn’t fall out of your head. You’re deep in one file and you spot a bug in another, and you write it down so you don’t lose your place. You’re cleaning up a module and you notice three more that need the same treatment, so you write those down too.

The common element in all of it is that you couldn’t do the thing right then. Filing was the second best option. The tracker was a memory prosthesis, a place to park work your hands were too busy or too few to pick up, and it was built for exactly that. Bugzilla went public on a mozilla.org server in 1998, replacing Netscape’s in-house system. The shape it established is still the one we use. A durable record, an owner, a state, a conversation attached to it. Durable because the gap between noticing and fixing was measured in weeks.

The failure mode is just as old. Avery Pennarun makes the point plainly in his treatise on bug tracking and triage. Bugs pile up when filing outpaces fixing. Declaring bug bankruptcy doesn’t change the slope of that line. Every engineer I know has worked in a tracker where the honest read on most open issues was that nobody would ever look at them again. We tolerated it because the alternative was forgetting, and forgetting felt worse.

The second thing we used trackers for was breaking work apart. The vocabulary comes from the agile world. Mike Cohn defines an epic as, simply, a large user story, one you decompose into smaller stories when a team is ready to work on it. Big thing arrives, big thing gets chopped into two-week pieces, pieces get distributed across people, tracker becomes the coordination surface. That’s project management wearing an issue tracker as a costume, and for humans it works well.

Both of those jobs assumed constraints that agents removed. We spent the last few weeks stress testing that, finding out which parts of the practice were load bearing, and came out the other side with a different idea of what a tracker is for.

The Follow-Up That Doesn’t Need to Wait

Start with the follow-ups, because that’s where the pain showed up first.

Our code reviews are agentic. We run Greptile on every pull request and I’ve been genuinely happy with what it catches. An agent reads the diff against the project’s own conventions and writes up what it finds. The good ones find real things. A missing test case, an error path that swallows a failure, a helper that should have been extracted. Under the old rule, each of those became an issue. Faithful record keeping, and a queue that grew every single day.

What we’re experimenting with now is skipping the record entirely. When the review turns up a follow-up, the reviewing agent forks its own context into a subagent and hands it the problem immediately. The fork inherits the conversation that produced the finding, so it already knows the file, the convention that was violated, and why the reviewer cared. It goes off and opens its own pull request while the original review continues uninterrupted. If the pull request that spawned it hasn’t merged yet, the fork stacks its work on top of that branch.

The economics are almost embarrassing. Writing a good issue means describing context that is already loaded in the window right in front of you. Then, later, some other agent pays to rebuild that context from scratch by re-reading the same files and re-deriving the same reasoning, assuming anyone ever picks the issue up at all. Forking the context costs a fraction of that and produces a pull request instead of a promise. It reuses the cached context from the original thread, which saves real money and skips the whole bootstrapping conversation. The cache is already warm. The reasoning is already done.

I want to be careful not to oversell it. This doesn’t work for everything. Some findings send me off to do research first. Some need me to sit with the consequences, or go take a walk and figure out how I actually want to handle the situation. Some need a conversation between me and Chris about whether a feature is the right fit for the product at all. Those still get filed, and I’ll come back to what that queue looks like now. But the default flipped. Filing an issue used to be what you did with a follow-up. Now it’s what you do when the follow-up needs a person to think first.

Epics Assume a Constraint That Left

The second habit took longer to give up, because breaking work into pieces feels like professionalism itself.

An agent can produce a five thousand line pull request in ten minutes. When that’s true, the reason for chopping a large feature into eleven tracked sub-issues mostly disappears. You weren’t decomposing because the work was inherently separable. You were decomposing because a person can only hold so much in their head at once, because a sprint is two weeks long, and because four people needed to work in parallel without colliding. Those are real constraints. None of them are the agent’s.

This is not an argument against testing, or against reviewing carefully, or against building a large feature in stages you can actually validate. Every one of those matters more now, not less. Simon Willison put it well recently. Using agents well takes two skills, instructing them clearly and verifying that what came back is right. He also points out that eyeballing every line was never the best way to do the second one. The chunking that survives is the chunking that helps you verify. The chunking that dies is the chunking that existed to schedule humans.

The testing instincts I picked up early in my career are paying dividends now more than ever. I can load our project into a browser inside the coding agent and start poking at it. When something behaves wrong, the agent is sitting right there with the context already loaded, so I can just ask about it. I can drive the thing from the terminal and then go verify what actually happened in my local database or against my local copy of the service. None of that is new. It’s the same work I’ve been doing for my whole career, and agents didn’t change it.

So we stopped using issues as project management. The agent doesn’t need a burndown chart, and neither do the two of us. We did try. We briefly put the work into a GitHub project built on our issues, kanban columns and all. It fell out of date faster than either of us was willing to maintain it.

Aspirational Issues Are a Trap

The third category is the one I’d defend the longest and was most wrong about. “Someday we should support real-time collaboration.” “It’d be nice if this thing had a plugin system.”

An agent cannot reliably act on an issue like that. It doesn’t have the product context, the customer conversations, or the constraints that would make one implementation right and another one a waste of a week. What it has is enough capability to build something confidently, quickly, and wrong. Aspirational issues are the ideal input for producing exactly that.

Worse, they poison the queue around them. If a tracker is a work list something else pulls from overnight, every unbuildable item in it is a small trap laid for a system that can’t tell the difference. Those ideas are still worth keeping. They just belong somewhere that isn’t the tracker: a document, a spreadsheet, a future-features markdown file checked into the repo. Somewhere a person browses on purpose, rather than somewhere an agent shops.

What Actually Belongs in the Tracker Now

Here’s the test we use. Could a cheap, fast model with the repository’s context in hand fix this tonight, correctly, without asking me anything?

I mean cheap literally. Sonnet 5, Gemini Flash, GPT Terra. If the issue is well defined and the project’s conventions are written down where an agent can read them, the frontier model isn’t buying you much. That’s a strong forcing function on how you write the issue. It’s also the same forcing function that made issues good for humans.

Three kinds of work pass the test. Bugs I found while using the product away from my desk, which I can describe well because I just watched it happen. Most of those reach the tracker through a feedback path rather than through my hands, and the nightly reflection that turns them into issues is a post of its own. Follow-ups from review that resisted being forked. Small features that are well scoped, well understood, and simply not a priority this week. That’s it. Everything in the tracker is something a low-cost agent could pick up and drive to a pull request, prioritized so it knows which one to pick.

We have a pipeline that does exactly that. It doesn’t wait for night. It runs continuously, picking up whatever is ready whenever it becomes ready. That’s the next post in this series, and it’s the reason the discipline in this one matters so much. A pipeline that works through an unfiltered backlog is a machine for generating plausible garbage at scale.

I’m also experimenting with a task that sweeps every issue opened during the week and spawns an agent to fix the ones that qualify. That habit is new enough that I don’t know yet whether the tracker ever actually reaches empty.

Where the Big Work Goes Instead

None of this means we stopped doing big things. It means the big things stopped living in the tracker.

For a substantial feature, I work with an agent to write a product document first. That might be a short document, a mock, or a piece of a design system, whatever makes the idea concrete enough to argue about. Then Chris and I talk it through until we agree it’s actually right for the product. Only then does it become the input to a technical design document, which gets reviewed the way any other design gets reviewed and then checked into the repository alongside the code. After the design doc lands we create an issue, and the issue says one thing. Implement this design.

That issue is not food for the overnight pipeline, and it’s labeled so the pipeline leaves it alone. It’s a marker. It exists so that a person sitting down with an agent has a place to start, and so both of us can see what’s planned but not yet built. When someone picks it up, the first move is to point the agent at the design doc for context, and then build the feature end to end. Not in phases the issue dictated in advance, because an issue written before the work started is a bad predictor of how the work will actually decompose. In reasonably sized chunks of working functionality, with a lot of manual testing along the way, laddering up to the whole thing. That doesn’t mean one enormous pull request either. I’m about fifteen pull requests into a feature right now, several days of work, all of it pointed at a single issue.

The design document does the job the epic used to do, and does it better. It holds the reasoning, not just the task list, which is what an agent needs to build something faithfully. And it’s version controlled next to the code it describes, so it goes stale visibly instead of quietly. We have an agent that runs nightly to compare the claims in those documents against the code and update the ones that have drifted, which is a story for another day.

Taking Inventory

Last week I stopped adding and took inventory instead. 300 open issues on that same repository, every one of them read against the actual code rather than against its own title, then sorted by what it would really take to close.

15 were already dead. Fixed by some other pull request, obsoleted by a decision we’d since made, duplicated, or never actionable in the first place. 54 were real work an agent could simply do, so I let it. The sweep spawned subagents in waves and merged 54 pull requests over the following days. That’s 69 issues, most of a quarter of the backlog, that a machine could resolve without me in the room.

The rest is the part that stuck with me. 101 needed a decision from me. 54 needed one from Chris. 67 were epics or deliberately parked, and I left them alone on purpose. Three quarters of what we had filed wasn’t work at all. It was a queue of decisions wearing work’s clothing, and it had been sitting there looking like a backlog the whole time.

That number moved me further than any argument in this post did.

Three Jobs Instead of One

What we’ve landed on is a tracker with three purposes. It holds things we found and can’t fix right now but an agent can fix later. It holds pointers to planned work whose real content lives in a design document. It holds the follow-ups from review that weren’t worth forking a subagent to solve on the spot.

Look at what fell out. The tracker stopped being a filing cabinet for human memory. Everything in it is now addressed to a machine that will read it, act on it, and open a pull request. That makes an issue much closer to a function call than to a note to self. I’ve argued before that prompts are code and deserve the same rigor. An issue an agent will execute is the same claim, arriving from a direction I didn’t expect.

The part I’m still sitting with is how much of my old practice was compensation. Filing a bug so I wouldn’t forget it. Breaking an epic into pieces so a team could carry them. Keeping a wishlist because writing it down felt like progress. Those were all coping mechanisms for being a human with one pair of hands and a bad memory. I still have both of those things. I just have fewer reasons to build my tools around them.

Next time, the pipeline that works through this queue on its own, and what happens when it wanders into a public repository and starts talking to strangers.

A hand-drawn map on a workbench with a half-built mechanical instrument being assembled directly on top of it.

Agents as Building Blocks

There’s a thread running through the last year of my writing and my work, and I didn’t fully see it until now.

Last September, I wrote Full Circle, about going back to building after years of leading teams. I wanted to be in the driver’s seat for what I called the agentic shift. I wanted to feel the code under my fingers again, to be close enough to the technology that I could form my own opinions about where it was going.

Then I spent six months drawing the map. The Agentic Shift was twelve essays on what agents are, how they work, and what it means to build them well: anatomy, memory, tools, guardrails, multi-agent coordination, production readiness. It was a theoretical framework, written while I was getting my hands dirty on the Gemini CLI team.

And then, in January, I wrote Everything Becomes an Agent, the practitioner’s version. Not theory anymore. I’d watched Gemini Scribe grow from a chat window into a full agent. I’d seen the CLI team go from talking about code to writing and executing it. I’d noticed a pattern repeating across every AI project I touched: given enough time, they all converged on the same architecture. Tools. Loops. Policies. Judgment.

The Antigravity SDK is the second agent product I’ve worked on at Google. Gemini CLI was the first, and it’s where I learned what an agent runtime actually needs: a policy engine, a tool pipeline, lifecycle hooks, a trust model that scales from “let me approve every file write” to “here are the guardrails, go handle it.” The SDK is the next step. Taking everything I learned building one agent and making it possible for everyone to build their own.

Today we’re launching the Antigravity SDK in Preview. The official announcement covers the features (what the SDK does, how to install it, what you can build). This post is about the why. Why this SDK, why this design, and why it matters to me.

What Is an Agent SDK, Really

Here’s something I find fascinating: people have wildly different ideas about what “agent SDK” means.

For some, it’s a way to automate the coding agent. You take the AI that already lives inside your IDE (Antigravity, Cursor, Copilot), and you script it. Pipe in a task, get back a diff. The SDK is an extension of your development environment. That’s a legitimate philosophy, and there are good products built on it.

But that’s not what I wanted to build.

To me, an agent SDK gives you an agent that you can incorporate into your software. Not an extension of your IDE. A building block. Something you import into your Python project the same way you’d import a database client or an HTTP library, and then you use it to solve a problem. The agent is a component in your system, not a wrapper around your workflow.

I’ve watched this pattern play out across Gemini Scribe, the Podcast RAG prototype, and a dozen smaller projects. Software that starts as a script, grows a tools array and a while loop, and eventually looks an awful lot like an agent. I wouldn’t claim that every AI project becomes an agent. But the pattern is durable for a huge class of software problems. And if that convergence is real, if a meaningful number of AI applications end up needing tools, memory, judgment, and guardrails, then the SDK should make that convergence frictionless.

The key distinction is this: the agents you build with the Antigravity SDK aren’t extensions of your developer tools, although they can do development work. They’re independent pieces of software that happen to be implemented as agents. They live in your codebase, run on their own, and do real work.

Let me show you what I mean.

Three Agents That Prove the Point

Two of my favorite examples ship with the SDK, and we use both of them on the SDK project itself on a regular basis. They live in the examples directory on GitHub.

The first is the docstring maintenance agent. You point it at a directory, and it audits every Python file for missing or incomplete docstrings, then fixes them, all following the Google Python Style Guide. It knows which tools it’s allowed to use (read files, list directories, edit .py files in the target directory, and nothing else). It has a policy engine that enforces those boundaries. It runs, does its job, and exits.

The second is the documentation maintenance agent. Same idea, different problem: it scans your project’s documentation for staleness, checks it against the current state of the code, and updates what needs updating.

Here’s what I love about these two examples. They’re coding-related tasks, but they aren’t extensions of my IDE. They’re standalone programs. I don’t run them inside my editor. I run them from the command line, or from a CI job, or from a cron schedule. They happen to be implemented as agents because an agent is the right abstraction for “read a bunch of files, reason about their quality, and make targeted edits.” If I’d built these as scripts, I would have ended up writing a brittle classifier full of if/else branches to decide what to fix and how. The agent architecture deletes that complexity.

We use both of these on the SDK project itself. The SDK maintains its own documentation with its own agents. There’s a satisfying recursion to that.

But I want to push the point further, because the SDK isn’t just for coding tasks. Here’s a completely different kind of agent, a personal knowledge graph I wrote that connects to my Workspace MCP server and answers questions about my Drive, Docs, Gmail, and Calendar:

import asyncio

from google.antigravity import Agent, LocalAgentConfig, types
from google.antigravity.utils import interactive


async def main():
    workspace_mcp = types.McpStdioServer(
        command="node",
        args=["/Users/adh/src/workspace/workspace-server/dist/index.js"],
    )
    system_instructions = (
        "You are a Personal Knowledge Graph Agent. Your goal is to help the user "
        "navigate and synthesize information from their Google Workspace "
        "(Drive, Docs, Gmail, Calendar). You can search for documents, "
        "read emails, and check calendar events to answer questions "
        "and help the user connect the dots."
    )
    config = LocalAgentConfig(
        system_instructions=system_instructions,
        mcp_servers=[workspace_mcp],
        capabilities=types.CapabilitiesConfig(
            enabled_tools=types.BuiltinTools.read_only(),
        ),
    )
    async with Agent(config) as agent:
        print("Knowledge Graph Agent ready. Ask me anything about your Workspace.")
        await interactive.run_interactive_loop(agent)


if __name__ == "__main__":
    asyncio.run(main())

This agent has nothing to do with coding. It’s a personal productivity tool that connects to my Google Workspace via MCP and lets me query my own data in natural language. It’s about 20 lines. It’s read-only by design. And it uses the same SDK, the same patterns, the same trust model as the docstring agent.

Three examples, three completely different domains: autonomous code maintenance, documentation upkeep, personal knowledge synthesis. All built with the same building blocks. That’s the vision.

Batteries Included, Layers When You Need Them

When designing this SDK, I kept coming back to one principle: batteries included. I wanted it to be really easy to put together an agent that worked for you. Easy to grow your application when you needed more sophistication. Easy to dive into the internals when the situation required it.

Here’s what a functional agent looks like:

import asyncio

from google.antigravity import Agent, LocalAgentConfig


async def main():
    config = LocalAgentConfig()
    async with Agent(config) as agent:
        response = await agent.chat("What files are in the current directory?")
        print(await response.text())


if __name__ == "__main__":
    asyncio.run(main())

That’s it. About 10 lines of real code. That agent can read files, edit code, run shell commands, search directories, all out of the box. You didn’t have to configure tools, set up a model connection, or wire up a conversation loop. The batteries are included.

But batteries included doesn’t mean batteries only. I designed the API in three layers, and knowing which layer to reach for is part of the design.

Layer 1: Agent. The highest level. Create an agent, give it a prompt, get results. This is where most people start, and many people stay. It manages the full lifecycle (connection, conversation, tools, hooks, policies) in a single async with block. If you just need an agent that does a job, this is your entire API surface.

Layer 2: Conversation. This is the implementation layer. Conversations, hooks, policies, MCP servers, custom tools, structured output. Conversation wraps a Connection with step history, turn tracking, and convenience methods. This is where you shape behavior. You add guardrails through the declarative policy engine. You inject lifecycle hooks, and the SDK gives you three distinct types: Inspect hooks for read-only observability, Decide hooks for policy decisions (allow/deny), and Transform hooks that can modify data in flight. You wire up MCP servers and your own Python functions as tools.

Layer 3: Connection. The lowest level. Connection is the abstract interface for talking to an agent backend. ConnectionStrategy knows how to establish one for a specific runtime. Today, we ship a local connection strategy that runs the agent on your machine. On the roadmap: remote connection strategies that let the same agent code deploy to the cloud without a rewrite.

Here’s the neat thing about this layer. Because Connection is an abstraction, you could conceivably wire up other agent runtimes behind it. We do this internally. We have several different ways of talking to our agent harness, and they all work through the same Connection interface. Your agent code doesn’t know or care which one is running underneath.

The philosophy is: easy to start, easy to grow, easy to go deep. You shouldn’t need to understand the Connection layer to write your first agent. But when you need it, when you’re building something that requires custom streaming, session resumption, or a novel deployment target, it’s there, and it’s a clean abstraction, not a hack.

One detail I’m particularly proud of: the trust model adapts to the deployment context. The base AgentConfig is deny-by-default. It defaults to read-only tools, and if you try to enable write tools or MCP servers without a safety policy, the Agent refuses to start. Enforced at the framework level. LocalAgentConfig takes a different posture. Since it runs on your own machine, it enables every tool, scopes file operations to the workspaces you’ve configured, and gates shell commands behind a user confirmation prompt by default. You’re developing locally; you probably want your agent to actually do things, but you also probably want a chance to look before it runs rm -rf. The trust gradient is baked into the architecture.

Lessons Encoded

If you’ve been following along with my writing, the SDK might feel familiar. That’s intentional.

The twelve-part Agentic Shift wasn’t just an intellectual exercise. It was the blueprint. Every essay mapped a concept that eventually became a feature.

In Everything Becomes an Agent, I wrote: “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.” The SDK takes that literally. You don’t build classifiers, you define tools and let the model decide which ones to use. The complexity moves from branching logic to capability definition.

I wrote about building a “sudoers file for AI”, a permission system for agents. That became the policy engine. policy.allow("view_file"). policy.deny("*"). Declarative, composable, deny-by-default. You express what’s allowed, and the framework enforces it.

I wrote: “The real complexity isn’t in the code; it’s in the trust.” That conviction shaped the hook system. Hooks give you visibility into every tool call, before and after. Policies give you control. Together, they manage the trust relationship between you and the agent. The SDK doesn’t ask you to trust blindly; it gives you the instruments to verify.

And I wrote: “A hammer does nothing unless you swing it. But an agent? An agent can work while you sleep.” That’s the promise. The SDK is the handle.

These aren’t abstract design principles that I reverse-engineered to sound good in a blog post. They’re lessons learned from building Gemini Scribe, from contributing to Gemini CLI, from watching every project I touched converge on the same agentic patterns. I drew the map, I lived the map, and then I got to build the territory.

The Team

I want to be clear about something. I didn’t build this alone.

I did most of the design for the Python SDK (the API surface, the three-layer architecture, the philosophy behind “batteries included”), and a lot of that design came from the writing I’ve been doing this past year. But design is the easy part. The hard part is building something real, and that was a team effort.

A talented group of engineers worked with me on this. On the SDK implementation, on the test infrastructure, on the Go harness underneath that actually runs the agent, on the internal connection strategies, on the MCP bridge, on a hundred decisions that don’t show up in a blog post but absolutely show up in the quality of the software. The SDK exists because of their work, and it’s better than anything I could have built on my own.

Preview, and an Invitation

We’re shipping this as a Preview. Not “1.0.” That’s deliberate.

The API surface will change. We know that. We’ll evolve it based on feedback from you and from our own continued use of the SDK, because we use it too, every day, on the project itself. There are things we haven’t figured out yet. There are patterns we haven’t discovered. That’s the point of a preview: to learn in the open.

So here’s the invitation: build something. Build a documentation bot, a knowledge graph, a CI pipeline agent, a personal assistant. Build something I haven’t imagined. Break something. Tell us what’s missing, what’s awkward, what delights you. File an issue. Open a PR. Argue with us about the API.

Last September, I wrote that I was going back to building because “for a builder, there’s no more exciting place to be.” The Agentic Shift was the map. The SDK is the territory.

Come explore it.

The Antigravity SDK is available now as a Preview. Install it with pip install google-antigravity, read the official announcement for feature details, and find the source on GitHub.

A futuristic glowing notebook on a wooden desk with a cup of coffee and floating geometric shapes.

Reading List 6

This week’s reading list is a mix of high-level theory and low-level pragmatism. I found myself bouncing between the philosophical implications of how we build AI and the immediate satisfaction of writing a good Go component.

[article] The Century-Long Pause in Fundamental Physics. The author argues that physics has stagnated by swapping “ontology-first” theory for mathematical models that merely fit data. This debate perfectly mirrors current machine learning disputes about whether LLMs build internal world models or just pattern-match at scale, which is the open empirical front currently being adjudicated in mechanistic interpretability.

[release] Onyx Has Released a New Remote Page Turner Called Tappy. I wish Amazon would support page turners for their Kindle line. It would be great if they supported a device as delightful as this one.

[blog] The agent principal-agent problem. This is a great look at one of the biggest problems with agentic development: code review. In my open source work, I now use a pattern where I work with an agent to make a change, test it locally, and create a pull request before having another agent review the code. This back-and-forth works well and keeps a good balance of mental state for the codebase and efficiency.

[article] ReMarkable Paper Pure wants to be the only notebook you’ll ever need. I have always liked the reMarkable tablets, but every time I try one I miss having my Kindle library alongside it. Reading and writing are deeply linked for me, which is why I recently got a Kindle Scribe Colorsoft and found it really hits the mark for what I want.

[blog] Just Fucking Use Go. I have been working on a project that has a Go component to it recently. This is the first time I have really started to look at the language, and it inspires me to spend more time with it.

I built my 7MB Full AI Terminal in Rust & Tauri. This is a neat open source AI terminal. It feels similar to Warp but is a lot smaller.

[article] Computer Use Is 45x More Expensive Than Structured APIs. I am not surprised at all by these findings. I think computer use will remain a last resort, and a lot of apps will expose some kind of API for an agent to use instead. My guess is that this eventually becomes the way we automate unmaintained applications that need to fit into an agentic workflow.

GitHub issues transforming into glowing skill cards floating above a laptop screen.

Bundled Skills in Gemini Scribe

The feature that became Bundled Skills started with a GitHub issues page.

I wrote and maintain Gemini Scribe, an Obsidian plugin that puts a Gemini-powered agent inside your vault. Thousands of people use it, and they have questions. People would open discussions and issues asking how to configure completions, how to set up projects, what settings were available. I was answering the same questions over and over, and it hit me: the agent itself should be able to answer these. It has access to the vault. It can read files. Why am I the bottleneck for questions about my own plugin?

So I built a skill. I took the same documentation source that powers the plugin’s website, packaged it up as a set of instructions the agent could load on demand, and suddenly users could just ask the agent directly. “How do I set up completions?” “What settings are available?” The agent would pull in the right slice of documentation and give a grounded answer. The docs on the web and the docs the agent reads are built from the same source. There is no separate knowledge base to keep in sync.

That first skill opened a door. I was already using custom skills in my own vault to improve how the agent worked with Bases and frontmatter properties. Once I had the bundled skills mechanism in place, I started looking at those personal skills differently. The ones I had built for myself around Obsidian-specific tasks were not just useful to me. They would be useful to anyone running Gemini Scribe. So I started migrating them from my vault into the plugin as built-in skills.

With the latest version of Gemini Scribe, the plugin now ships with four built-in skills. In a future post I will walk through how to create your own custom skills, but first I want to explain what ships out of the box and why this approach works.

Four Skills Out of the Box

That first skill became gemini-scribe-help, and it is still the one I am most proud of conceptually. The plugin’s own documentation lives inside the same skill system as everything else. No special case, no separate knowledge base. The agent answers questions about itself using the same mechanism it uses for any other task.

The second skill I built was obsidian-bases. I wanted the agent to be good at creating Bases (Obsidian’s take on structured data views), but it kept getting the configuration wrong. Filters, formulas, views, grouping: there is a lot of surface area and the syntax is particular. So I wrote a skill that guides the agent through creating and configuring Bases from scratch, including common patterns like task trackers and project dashboards. Instead of me correcting the agent’s output every time, I describe what I want and the agent builds it right the first time.

Next came audio-transcription. This one has a fun backstory. Audio transcription was one of the oldest outstanding bugs in the repo. People wanted to use it with Obsidian’s native audio recording, but the results were poor. In this release, fixes around binary file uploads meant the model could finally receive audio files properly. Once that was working, I realized I did not need to write any more code to get good transcriptions. I just needed to give the agent good instructions. The skill guides it through producing structured notes with timestamps, speaker labels, and summaries. It turns a messy audio file into a clean, searchable note, and the fix was not code but context.

The fourth is obsidian-properties. Working with note properties (the YAML frontmatter at the top of every Obsidian note) sounds trivial until you are doing it across hundreds of notes. The agent would make inconsistent choices about property types, forget to use existing property names, or create duplicates. This skill makes it reliable at creating, editing, and querying properties consistently, which matters enormously if you are using Obsidian as a serious knowledge management system.

The pattern behind all four is the same. I watched the agent struggle with something specific to Obsidian, and instead of accepting that as a limitation of the model, I wrote a skill to fix it.

Why Not Just Use the System Prompt

You might be wondering why I did not just shove all of this into the system prompt. I wrote about this problem in detail in Managing the Agent’s Attention, but the short version is that system prompts are a “just-in-case” strategy. You load up the agent with everything it might need at the start of the conversation, and as you add more instructions, they start competing with each other for the model’s attention. Researchers call this the “Lost in the Middle” problem: models pay disproportionate attention to the beginning and end of their context, and everything in between gets diluted. If I packed all four skills worth of instructions into the system prompt, each one would make the others less effective. Every new skill I add would degrade the ones already there.

Skills avoid this entirely. The agent always knows which skills are available (it gets a short name and description for each one), but only loads the full instructions when it actually needs them. When a skill activates, its instructions land in the most recent part of the conversation, right before the model starts reasoning. Only one skill’s instructions are competing for attention at a time, and they are sitting in the highest-attention position in the context window.

There is a second benefit that surprised me. Because skills activate through the activate_skill tool call, you can watch the agent load them. In the agent session, you see exactly when a skill is activated and which one it chose. This gives you something that system prompts never do: observability. If the agent is not following your instructions, you can check whether it actually activated the skill. If it activated the skill but still got something wrong, you know the problem is in the skill’s instructions, not in the agent’s attention. That feedback loop is what lets you iterate and improve your skills over time. You are no longer guessing whether the agent read your instructions. You can see it happen.

Skills follow the open agentskills.io specification, and this matters more than it might seem. We have seen significant standardization around this spec across the industry in 2026. That means skills are portable. If you have been using skills with another agent, you can bring them into Gemini Scribe and they will work. If you build skills in Gemini Scribe, you can take them with you. They are not a proprietary format tied to one tool. They are Markdown files with a bit of YAML frontmatter, designed to be human-readable, version-controllable, and portable across any agent that supports the spec.

What Comes Next

The four built-in skills are just the beginning. When I decide what to build next, I think about skills in four categories. First, there are skills that give the agent domain knowledge about Obsidian itself, things like Bases and properties where the model’s general training is not specific enough. Second, there are skills that help the agent use Gemini Scribe’s own tools effectively. The plugin has capabilities like deep research, image generation, semantic search, and session recall, and each of those benefits from a skill that teaches the agent when and how to use them well. Third, there are skills that bring entirely new capabilities to the agent, like audio transcription. And fourth, there is user support: the help skill that started this whole process, making sure people can get answers without leaving their vault.

The next version of Gemini Scribe will add built-in skills for semantic search, deep research, image generation, and session recall. The skills system is also designed to be extended by users. In a future post I will walk through creating your own custom skills, both by hand and by asking the agent to build them for you.

For now, the takeaway is simple. A general-purpose model knows a lot, but it does not know your tools. When I watched the agent struggle with Obsidian Bases or produce flat transcripts or make a mess of note properties, I could have accepted those as limitations. Instead, I wrote skills to close the gap. The model’s knowledge is broad. Skills make it deep.

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 focused workspace at a desk in a vast library, with nearby shelves illuminated and distant shelves visible but softened, a pair of sunglasses resting on the desk

Scoping AI Context with Projects in Gemini Scribe

My son has a friend who likes to say, “born to dilly dally, forced to lock in.” I’ve started to think that describes AI agents in a large Obsidian vault perfectly.

My vault is a massive, sprawling entity. It holds nearly two decades of thoughts, ranging from deep dives into LLM architecture to my kids’ school syllabi and the exact dimensions needed for an upcoming home remodelling project. When I first introduced Gemini Scribe, the agent’s ability to explore all of that was a feature. I could ask it to surface surprising connections across topics, and it would. But as I’ve leaned harder into Scribe as a daily partner, both at home and at work, the dilly dallying became a real problem. My work vault has thousands of files with highly overlapping topics. It’s not a surprise that the agent might jump from one topic to another, or get confused about what we’re working on at any given time. When I asked the agent to help me structure a paragraph about agentic workflows, I didn’t want it pulling in notes from my jazz guitar practice.

I could have created a new, isolated vault just for my blog writing. I tried that briefly, but I immediately found myself copying data back and forth. I was duplicating Readwise syncs, moving research papers, and fracturing my knowledge base. That wasn’t efficient, and it certainly wasn’t fun. The problem wasn’t that the agent could see too much. The problem was glare. I needed sunglasses, not blinders. I needed to force the agent to lock in.

So, I built Projects in Gemini Scribe.

A project defines scope without acting as a gatekeeper

Fundamentally, a project in Gemini Scribe is a way to focus the agent’s attention without locking it out of anything. It defines a primary area of work, but the rest of the vault is still there. Think of it like sitting at a desk in the engineering section of a library. Those are the shelves you browse by default, the ones within arm’s reach. But if you know the call number for a book in the history section, nobody stops you from walking over and grabbing it. You can even leave a stack of books from other sections on your desk ahead of time if you know you’ll need them. If you’ve followed along with the evolution of Scribe from plugin to platform, you’ll recognize this as a natural extension of the agent’s growing capabilities.

The core mechanism is remarkably simple. Any Markdown file in your vault can become a project by adding a specific tag to its YAML frontmatter.

---
tags:
  - gemini-scribe/project
name: Letters From Silicon Valley
skills:
  - writing-coach
permissions:
  delete_file: deny
---

Once tagged, that file’s parent directory becomes the project root. From that point on, when an agent session is linked to the project, its discovery tools are automatically scoped to that directory and its subfolders. Under the hood, the plugin intercepts API calls to tools like list_files and find_files_by_content, transparently prepending the project root to the search paths. The practical difference is immediate. Before projects, I could be working on a blog post about agent memory systems and the agent would surface notes from a completely unrelated project that happened to use similar terminology. Now I can load up a project and work with the agent hand in hand, confident it won’t get distracted by similar ideas or overlapping vocabulary from other corners of the vault.

The project file serves as both configuration and context

The project file itself serves a dual purpose. It acts as both configuration and context. The frontmatter handles the configuration, allowing me to explicitly limit which skills the agent can use or override global permission settings. For example, denying file deletions for a critical writing project is a simple but effective safety net. But the real power is in customizing the agent’s behavior per project. For my creative writing, I actually don’t want the agent to write at all. I want it to read, critique, and discuss, but the words on the page need to be mine. Projects let me turn off the writing skill entirely for that context while leaving it fully enabled for my blog work. The same agent, shaped differently depending on what I’m working on.

Everything below the frontmatter is treated as context. Whatever I write in the body of the project note is injected directly into the agent’s system prompt, acting much like an additional, localized set of instructions. The global agent instructions are still respected, but the project instructions provide the specific context needed for that particular workspace. This is similar in spirit to how I’ve previously discussed treating prompts as code, where the instructions you give an agent deserve the same rigor and iteration as any other piece of software.

This is where the sunglasses metaphor really holds. The agent’s discovery tools, things like list_files and find_files_by_content, are scoped to the project folder. That’s the glare reduction. But the agent’s ability to read files is completely unrestricted. If I am working on a technical post and need to reference a specific architectural note stored in my main Notes folder, I have two options. I can ask the agent to go grab it, or I can add a wikilink or embed to the project file’s body and the agent will have it available from the start. One is like walking to the history section yourself. The other is like leaving that book on your desk before you sit down. Either way, the knowledge is accessible. The project just keeps the agent from rummaging through every shelf on its own. This builds directly on the concepts of agent attention I explored in Managing AI Agent Attention.

Session continuity keeps the agent focused across your vault

One of the more powerful aspects of this system is how it interacts with session memory. When I start a new chat, Gemini Scribe looks at the active file. If that file lives within a project folder, the session is automatically linked to that project. This is a direct benefit of the supercharged chat history work that landed earlier in the plugin’s life.

This linkage is stable for the lifetime of the session. I can navigate around my vault, opening files completely unrelated to the project, and the agent will remain focused on the project’s context and instructions. This means I don’t have to constantly remind the agent of the rules of the road. The project configuration persists across the entire conversation.

Furthermore, session recall allows the agent to look back at past conversations. When I ask about prior work or decisions related to a specific project, the agent can search its history, utilizing the project linkage to find the most relevant past interactions. This creates a persistent working environment that feels much more like a collaboration than a simple transaction.

Structuring projects effectively requires a few simple practices

To get the most out of projects, I’ve found a few practices to be particularly effective.

First, lean into the folder-based structure. Place the project file at the root of the folder containing the relevant work. Everything underneath it is automatically in scope. This feels natural if you already organize your vault by topic or project, which many Obsidian users do.

Second, start from the defaults and adjust as the project demands. Out of the box, a new project inherits the agent’s standard skills and permissions, which is a sensible baseline for most work. From there, you tune. If you find the agent reaching for tools that don’t make sense in a given context, narrow the allowed skills in the frontmatter. If a project needs extra safety, tighten the permissions. The creative writing example I mentioned earlier came about exactly this way. I started with the defaults, realized I wanted the agent as a reader and critic rather than a co-writer, and adjusted accordingly. This aligns with the broader principle I’ve written about when discussing building responsible agents: the right guardrails are the ones shaped by the actual work.

Finally, treat the project body as a living document. As the project evolves, update the instructions and external links to ensure the agent always has the most current and relevant context. It’s a simple mechanism, but it fundamentally changes how I interact with an AI embedded in a large knowledge base. It allows me to keep my single, massive vault intact, while giving the agent the precise focus it needs to be genuinely helpful.

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 laptop sits on a dark wooden desk under the warm glow of an Edison bulb; above the screen, a stream of glowing, holographic research papers and data visualizations cascades downward like a waterfall, physically dissolving into lines of green and white markdown text as they enter the open terminal window.

Bringing Deep Research to the Terminal

I lost the report somewhere between browser tabs. One moment it was there in the Gemini app, a detailed deep research analysis on how AI agents communicate with each other, complete with citations and a synthesis I’d spent an hour reviewing. The next moment, gone. Along with the draft blog post I’d been weaving it into.

I was working on part nine of my Agentic Shift series, trying to answer the question of what happens when agents start talking to each other instead of just talking to us. The research was sprawling—academic papers on multi-agent systems, documentation from LangGraph and AutoGen, blog posts from researchers at DeepMind and OpenAI. I’d been using Gemini’s deep research feature in the app to help synthesize all of this, and it was genuinely useful. The AI would spend minutes thinking through the question, querying sources, building a structured report. But then I had to move that report into my text-based workflow. Copy, paste, reformat, lose formatting, copy again. Somewhere in that dance between the browser and my terminal, I lost everything.

I stared at the empty browser tab for a moment. I could start over, rerun the research in the Gemini app, be more careful about saving this time. But this wasn’t the first time I’d hit this friction. Every time I used deep research in the browser, I had to bridge two worlds: the app where the AI did its thinking, and the terminal where I actually write and build.

What looked like yak shaving was actually a prerequisite. I needed deep research capabilities in my terminal workflow, not just wanted them. I couldn’t keep jumping between environments. And I was in luck. Just a few weeks earlier, Google had announced that deep research was now available through the Gemini API. The capability I’d been using in the browser could be accessed programmatically.

When Features Live in the Wrong Place

I’m not going to pretend this was built based on demand from the community. I needed this. Specifically, I needed to stop context-switching between the Gemini app and my terminal, because every time I did, I was introducing friction and risk. The lost report was just the most recent symptom of a workflow that was fundamentally broken for how I work.

I live in the terminal. My notes are markdown files. My drafts are plain text. My build process, my git workflow, my entire development environment assumes I’m working with files and command-line tools. When I have to move work from a browser back into that environment, I’m not just inconvenienced—I’m fighting against the grain of everything else I do.

Deep research is powerful. It works. But living in a web app meant it was disconnected from the places where I actually needed it. Sure, other people might benefit from having this integrated into MCP-compatible tools, but that’s a nice side effect. The real reason I built this was simpler: I had to finish part nine of the Agentic Shift series, and I couldn’t do that without fixing my workflow first.

The Model Context Protocol made this possible. It’s a standard for exposing AI capabilities as tools that can plug into different environments. Google’s API gave me the primitives. I just needed to connect them to where I actually work.

Building the Missing Piece

The extension wraps Gemini’s deep research capabilities into the Model Context Protocol, which means it integrates seamlessly with Gemini CLI and any other MCP-compatible client. The architecture is deliberately simple, but it supports two distinct workflows depending on what you need.

The first workflow is straightforward: you have a research question, and you want a deep investigation. You can kick off research with a simple command, but if you use the bundled /deep-research:start slash command, the model actually guides you through a step to optimize your question to get the most out of deep research. The agent then spends tens of minutes—or as much time as it needs—planning the investigation, querying sources, and synthesizing findings into a detailed report with citations you can follow up on.

The second workflow is for when you want to ground the research in your own documents. You use /deep-research:store-create to set up a file search store, then /deep-research:store-upload to index your files. Once they’re uploaded, you have two options: you can include that dataset in the deep research process so the agent grounds its investigation in your specific sources, or you can query against it directly for a simpler RAG experience. This is the same File Search capability I wrote about in November when I rebuilt my Podcast RAG system, but now it’s accessible from the terminal as part of my normal workflow.

The extension maintains local state in a workspace cache, so you don’t have to remember arcane resource identifiers or lose track of running research jobs. The whole thing is designed to feel as natural as running a grep command or kicking off a build—it’s just another tool in the environment where I already work.

So did it actually work?

The first time I ran it, I asked for a deep dive into Stonehenge construction. I’d been reading Ken Follett’s novel Circle of Days and found myself curious about the scientific evidence behind the story, what do we actually know about how it was built and who built it. I kicked off the query and watched something fascinating happen. The model understood that deep research takes time. Instead of just waiting silently, it kept checking in to see if the research was done, almost like checking the oven to see if dinner was ready. Twenty minutes later, a markdown file appeared in my filesystem with a comprehensive research report, complete with citations to academic sources, isotope analysis, and archaeological evidence. I didn’t have to copy anything from a browser. I didn’t lose any formatting. It was just there, ready to reference. The report mentioned the Bell Beaker culture and what happened to the Neolithic builders around 2500 BCE, which sent me down another rabbit hole. I immediately ran a second research query on that transition. Same seamless experience. That’s when I knew this was exactly what I needed.

What This Actually Means

I think extensions like this represent something important about where AI development is heading. We’re past the proof-of-concept phase where every AI interaction is a magic trick. Now we’re in the phase where AI capabilities need to integrate into actual workflows—not replace them, but augment them in ways that feel natural.

This is what I wrote about in November when I talked about the era of Personal Software. We’ve crossed a threshold where building a bespoke tool is often faster—and certainly less frustrating—than trying to adapt your workflow to someone else’s software. I didn’t build this extension for the community. I built it because I needed it. I had lost work, and I needed to stop context-switching between environments. If other people find it useful, that’s a nice side effect, but it’s fundamentally software for an audience of one.

The key insight for me was that the Model Context Protocol isn’t just a technical standard; it’s a design pattern for making AI tools composable. Instead of building a monolithic research application with its own UI and workflow, I built a small, focused extension that does one thing well and plugs into the environment where I already work. That composability matters because it means the tool can evolve with my workflow rather than forcing my workflow to evolve around the tool.

There’s also something interesting happening with how we think about AI capabilities. Deep research isn’t about making the model smarter—it’s about giving it time and structure. The same model that gives you a superficial answer in three seconds can give you a genuinely insightful report if you let it think for tens of minutes and provide it with the right sources. We’re learning that intelligence isn’t just about raw capability; it’s about how you orchestrate that capability over time.

What Comes Next

The extension is live on GitHub now, and I’m using it daily for my own research workflows. The immediate next step is adding better control over the research format—right now you can specify broad categories like “Technical Deep Dive” or “Executive Brief,” but I want more granular control over structure and depth. I’m also curious about chaining multiple research tasks together, where the output of one investigation becomes the input for the next.

But the bigger question I’m sitting with is what other AI capabilities are hiding in plain sight, waiting for someone to make them accessible. Deep research was always there in the Gemini API; it just needed a wrapper that made it feel like a natural part of the development workflow. What else is out there?

If you want to try it yourself, you’ll need a Gemini API key (get one at ai.dev) and set the GEMINI_DEEP_RESEARCH_API_KEY environment variable. Deep research runs on Gemini 3.0 Pro, and you can find the current pricing here. It’s charged based on token consumption for the research process plus any tool usage fees.

Install the extension with:

gemini extensions install https://github.com/allenhutchison/gemini-cli-deep-research --auto-update

The full source is on github.

As for me, I still need to finish part nine of the Agentic Shift series. But now I can get back to it with the confidence that I’m working in my preferred environment, with the tools I need accessible right from the terminal. Fair warning: once you start using AI for actual deep research, it’s hard to go back to the shallow stuff.

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.