A laptop glowing in a dim home office at night, showing a queue of pull requests waiting for review.

The Agent That Never Merges

Most mornings now I open my laptop to a queue instead of an inbox. Overnight, while Chris and I were asleep, an agent read our open GitHub issues, decided which ones were ready to build, picked the oldest one, and opened a pull request. The tests pass. Greptile reviewed it, the agent worked through the review comments, and Greptile now scores the change five out of five. The description explains what changed and why. The commit history looks like a careful engineer wrote it. What’s left for me is to decide whether it’s good enough to ship.

I said last month that I’d keep writing about the engineering as Chris and I build Vycari. This is the second post in that series. The first was about the issue tracker, and what belongs in it now that agents do most of the work. This one is about the pipeline that works through it. It’s the part of our process I trust most, and the part I was most nervous about building, because it’s where we let an agent write code that ends up in front of you.

Two Founders, No Platform Team

There are two of us. There is no platform team, no SRE rotation, no QA function, nobody whose job is keeping the lights on so we can build product. Whatever infrastructure we need, we build ourselves, out of the same hours we need for everything else. I like this stage of a company, but it means every hour Chris or I spend on triage, boilerplate, or a stale dependency is an hour not spent on decisions only the two of us can make.

The obvious answer is to point an agent at the boring parts. The harder part is the discipline that requires. An agent that opens pull requests unsupervised can save you a lot of time or create a lot of problems, and which one you get depends on what you refuse to let it decide on its own. I’ll come back to that. First I want to explain where the tooling came from, because it didn’t start as a product decision. It started when I noticed I had built the same thing twice.

We Had Already Built This Twice

Before the company, I was maintaining Gemini Scribe, a TypeScript plugin for Obsidian I’ve been building for a couple of years, and I had started experimenting with overnight agents to do the maintenance work: keeping the docs current, writing development logs, triaging issues. An architecture audit, a daily changelog generator, a skill that reviewed pull requests against the project’s conventions. When I started on what became Vycari’s codebase, a Python backend, I began writing the same things again under slightly different names. I hadn’t noticed they were the same habits until the second or third time.

In June I pulled all of it out into its own project. Maintainerd is a marketplace of Claude Code plugins for maintainer work: audits for architecture, test quality, security, dependencies, and documentation; a daily changelog; a research radar that scans arXiv for relevant papers; a skill that opens pull requests and one that works through reviewer comments; and the pipeline this post is about. It’s open source under the MIT license and lives in the Vycari org.

The name is a pun on maître d’. A maître d’ runs the front of the house so the kitchen can cook, which is what these skills do for a repository. It also reads as Maintainer Nerd, which I didn’t notice until later and haven’t tried to fix. I wrote when we announced the company that using agents today is still a hobby. Naming my repo maintenance tooling after a restaurant host tells you which side of that line I’m on.

Every skill in maintainerd is repo-agnostic. It reads a small config contract, a JSON file plus a few markdown documents of house rules, that says what language the repo is in, where the tests live, what the lint command is, and which GitHub labels mean what. The same skill works on a Python backend and a TypeScript plugin because the repo-specific knowledge is in the config, not in the skill.

That separation is what made the rest possible. A skill hardcoded to one repo’s conventions is a one-off script. A skill that reads a contract is infrastructure you can point at a new project in an afternoon. It’s the same argument I made when I wrote that prompts are code: an instruction that hardcodes its environment isn’t reusable. Without that separation, two people could not have built the pipeline I’m about to describe in the time we had.

What the Pipeline Actually Does

The piece of maintainerd we lean on hardest is called auto-dev. It’s a state machine. An issue moves from idea to clarification to plan to implementation, one step at a time, while I’m working on something else. It has three parts.

The first is what goes in, and the last post covered most of that. The tracker holds three kinds of work: bugs we found and can’t fix right now but an agent can, follow-ups from review that weren’t worth forking a subagent to fix on the spot, and pointers to planned features whose real content lives in a design document. The pipeline only wants the first two. The design-doc issues are labeled so it leaves them alone, and a person sits down with an agent and builds those end to end.

Most of the issues the pipeline works on were written by agents, not by us. The test for whether an issue belongs in the tracker is whether a cheap model could fix it tonight without asking anything, and an agent that just watched a bug happen usually clears that bar. When one of us writes an issue by hand, we describe it to a skill whose job is to get it to that standard. It reads the relevant code first, so the issue points at real files and functions, and it asks us the two or three questions the code can’t answer, so the issue doesn’t have to ask them later.

I don’t always hold to that. Sometimes I type a few paragraphs into an issue from my phone. When an issue isn’t good enough to build from, the pipeline’s triage catches it and asks. That’s the fallback, not the plan. Either way an issue is filed unlabeled, so the pipeline evaluates it fresh rather than assuming someone already decided it was ready.

The second part is the pipeline itself, which runs on a schedule rather than on demand. A scheduled task fires the skill at a regular interval, and each run is one tick of the state machine. It does the single highest-priority piece of work available and exits. Waiting for an answer, an approval, or a review is what happens between ticks.

Each tick looks at every open issue and asks whether it’s ready to build: whether it has enough context, whether it depends on something unfinished, whether it’s in scope. Ready issues get a plan drafted and posted as a comment, and the pipeline stops there. A plan is a proposal. Only after one of us approves it does the issue become buildable, and only then, on a later tick, does the pipeline pick the oldest approved issue, write the code, run the tests, and open the pull request. All of that state lives in a small set of GitHub labels:

"stateLabels": {
  "needsInfo": "auto:needs-info",
  "planned": "auto:planned",
  "ready": "auto:ready",
  "inProgress": "auto:in-progress",
  "parked": "auto:parked",
  "skip": "auto:skip"
}

There is no database to maintain and no dashboard to check. The state is on the issue, visible to anyone who looks at the repo, and the label history is the audit log.

The third part is the human side. It’s a skill called review-queue that I run in my coding agent. It collects everything blocked on a decision that’s waiting for me to weigh in: plans waiting for approval, pull requests where a reviewer left a comment the pipeline has already addressed, issues it flagged as needing more information. I work through the list top to bottom, the way I used to work a code review queue at Google, except most of the queue was generated by something that also drafted its own response to the objections.

I run it on my desktop and on my phone against a cloud-hosted model, and the phone version is the one that changed my habits. I’ll clear three or four items while standing in a line: approve a plan, answer a question, park something that isn’t worth building yet. That time used to go to scrolling.

The pull requests themselves get more than a glance. Most of the time we spend on them goes to running the change in a local environment and testing it. When there’s nothing to see, I read the code and either agree with it or don’t. Every pull request also goes through an adversarial review agent, and it holds code I write to the same bar as code auto-dev writes.

Here is the part that matters most. There are two gates in this pipeline, and a person has to open both. Nothing gets built without an approved plan, and someone has to read the plan to approve it. And at no stage does the pipeline merge anything. It triages, it plans, it builds, it responds to review feedback on its own pull requests. It never decides that a change is good enough to ship. That decision is still the responsibility of a human, on every pull request, with no exception for the ones that look routine. The merge rule isn’t a line in a prompt. The harness the pipeline runs in doesn’t allow it to merge, so the agent couldn’t do it even if it decided to. I’ve written before about where to put the guardrails on an agent that acts on its own. The guardrail I care most about now is not the one that stops the agent from doing damage. It’s the one that stops it from making a decision a person should be accountable for.

Four Pull Requests for One Bug

It doesn’t always go that cleanly. Last night an architecture audit flagged a real problem in one of our coding convention files, and several auto-dev runs picked it up at the same time. By the time I looked there were four pull requests open, each fixing the same thing in a slightly different way, and dozens of runs still going in the runner. I closed the pull requests, went into the runner, and shut down every instance that was working on it.

The pipeline has a limit on how many pull requests it can have open at once and a rule for reclaiming a build that looks abandoned, and both of those held. What they don’t cover is a burst of runs that all start before any of them has claimed the issue. It cost tokens and an evening, and it was the first failure of any size. I’ve been raising the run cadence and the number of open pull requests it’s allowed slowly, on purpose, so that the failures show up one at a time while they’re still cheap.

The Part That Happens in Public

Gemini Scribe is a public repository with real users, and they file issues. The pipeline treats their issues the way it treats ours: it reads the relevant code, decides whether there’s enough to plan against, and if not, posts a comment asking for what’s missing.

Sometimes that’s better than what I would have done. Someone files a short bug report, and before I’ve seen it there’s a specific question underneath it, pointing at the relevant file and asking which of two behaviors they meant. That’s a faster and more useful first response than a busy maintainer usually gives, and it arrives while the problem is still fresh for the reporter.

Every comment it posts is attributed to Claude Code in the comment metadata, so anyone reading the thread can see it wasn’t me. And each run starts in a fresh virtual machine with access to the repository and nothing else: no connectors, no configured tools, none of my data. The most it can leak is what’s already public in the repo.

Sometimes it asks the reporter what the plugin should do about the bug. That’s the wrong person to ask. They wanted their bug fixed; deciding how the software should behave is my job. The pipeline isn’t broken when it does this. It’s running the same readiness triage that works fine on a private backlog, where the only people it can ask are Chris and me. In a public tracker, that same question lands on a stranger.

I haven’t fixed this yet. What makes it tolerable is the same rule as before: the worst thing the pipeline can do in public is ask a question it shouldn’t. It can’t ship a decision. Nothing it writes reaches a user’s vault unless I put it there. The never-merge rule was meant to protect code quality, and it also limits how much damage an awkward comment can do.

I still prefer this to the alternative. An issue tracker that responds within hours instead of whenever I have a free evening is better for the person who filed the issue. Getting the questions right is a tuning problem. Getting anyone answered at all, with two people, is not something I could otherwise do.

The Same Discipline We’re Selling

I said when we announced the company that an agent has to earn the trust the access requires, and that we’d hold ourselves to that before asking anyone to trust us with their calendar and mail. This pipeline is the first place I get to practice that on myself.

The rule that it never merges is enforced by the harness, but the harness is ours. We built it that way and we could build it differently. It’s a decision, applied to our own code before it’s applied to a customer’s. If I’m going to ask someone to trust an assistant with the private parts of their day, I should at least refuse to let our own agent decide what ships. Everything else is work we’re happy to hand off: triage, drafting, boilerplate, the first pass at a reviewer’s comment. That’s the same division we want the product to make for you. Simon Willison made the argument in a post about proving your code works: a computer can’t be held accountable, so the human in the loop has to be. The decision with real consequences stays with a person who can answer for it. The product is built the same way. What Pepper is allowed to do on its own is set by policies and hooks in the agent framework, not by the model’s judgment in the moment. The harness decides where the line is, and a person decides what the harness says.

There’s a second connection I didn’t expect. Part of what we believe is that nobody should have to learn our vocabulary to get value from an agent. The review-queue skill is that idea applied to me. It doesn’t ask me to understand auto-dev’s state machine or check six label filters across a dozen pull requests. It gives me one ordered list of the decisions that are mine to make. Once you decide an assistant’s job is to meet people where they are, you notice every other place a tool is asking you to come to it.

What’s Next

One more thing I’ve noticed. A pipeline like this works because someone wrote the config contract, the conventions, the review rules, and the gates, and that someone had built and shipped software for a long time before agents existed. That’s where experienced engineers add value now. You build the frameworks that let the agents succeed, and you review what comes back. It isn’t very different from being a tech lead with a team of new grads.

None of this is finished. The pipeline still gets things wrong in ways I only find by watching it, and I’ll write about the ones worth telling. Next in this series I want to cover how we grade the agent’s work when nobody is watching. It’s closer to writing a rubric than writing code. I built a first version of that for my Obsidian plugin, and doing it for a product two people are betting on is a different problem. For now, two people are building a company with a colleague that never stops, never merges, and still needs its work checked every morning. That’s the balance I wanted.

Leave a Reply