---
title: "13 PRs. One Day. No Manual Merges."
description: "I merged 13 PRs in one day with AI agents running in parallel. 5 iterations to a 3-button approval system that keeps humans in control."
date: 2026-06-23
tags: ["Multi-Agent Systems", "DevOps", "Automation", "Developer Experience", "GitHub Copilot"]
canonical: https://htek.dev/articles/agent-merge-speed-without-losing-control
---
Iteration 1 looked like this: my agent opened a PR, immediately called `gh pr merge --squash --delete-branch` in PowerShell, and merged itself into `main`. Raw merge access, no guardrails. The Vercel deployment broke. I spent 40 minutes fixing it.

Iteration 5 looked like this: Sofia got a Telegram notification — a new feature PR for the shop management app — with three buttons: **✅ Merge Now**, **🤖 Agent Merge**, **❌ Deny**. She tapped 🤖 Agent Merge and went back to what she was doing. The merge-agent rebased the PR onto `main`, waited 3 minutes for [GitHub Actions](https://docs.github.com/en/actions) and the Vercel preview deployment to go green, and squash-merged it. Zero diff read by a human. The right outcome shipped.

The gap between those two states is a five-iteration story about building the merge layer that agentic development actually requires.

## Why the Merge Layer Is the Bottleneck

When you have 10 agent-authored PRs queued and every merge requires a developer to checkout the branch, read the diff, run tests locally, and click approve on GitHub, you've recreated the bottleneck you were trying to break. The merge layer — not the code generation layer — is the throughput ceiling for agentic teams. Fix it with deterministic tooling and approval that doesn't require humans to read code.

Most teams focus their agentic investment on faster code generation. The real leverage is in what happens *after* the code is written. An agent that ships a PR 3x faster than a developer means nothing if that PR waits two days in review. Fix the merge layer first.

## Five Iterations to One System

Each iteration of this system fixed one specific failure mode from the previous version. Iteration 1 had agents merging directly. Iteration 5 has a 3-button Telegram approval, a durable local ledger, and a patient merge-agent that handles rebases and CI waits automatically. The path between them is the lesson.

### Iteration 1 — Naive Auto-Merge

Agents had raw PowerShell access and could call `gh pr merge` directly. The failure mode wasn't that agents were malicious — it was that they couldn't distinguish "CI hasn't run yet" from "CI is green." The first naive merge broke the `main` branch of `htekdev/htek-dev-site` within 72 hours. **Lesson:** agents need a deterministic merge tool, not raw shell access.

### Iteration 2 — Deterministic Merge Tool

I introduced `dev_merge_pr` — a tool that checks PR state (open, non-draft, no merge conflicts), verifies all required [GitHub Actions status checks](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-required-status-checks) are passing, then calls the [GitHub squash merge API](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-commits). Agents call `dev_merge_pr`; raw `gh pr merge` is blocked. This was safer but the approval model was still the GitHub web UI — Hector reads diff, clicks approve, waits.

**Lesson:** the merge tool needs an approval mechanism that's physically closer to the agent.

### Iteration 3 — Hookflow Lockdown

I added `enforce-merge-pr-tool-only.yml`, a [hookflow that fires on every PowerShell tool call](/articles/hookflows-governed-git-for-ai-agents) and blocks any command matching `gh pr merge`, direct GitHub API merge endpoints (`/pulls/{N}/merge`), and `gh api` merge combos. Agents literally cannot route around the approval requirement — there's no shell escape hatch.

**Lesson:** safety without throughput is just a different kind of frustration.

### Iteration 4 — The Conflict Wall

Sofia started operating AI agents as a non-technical user — describing features, approving previews, driving parallel workstreams without writing code. She'd launch 5–6 agent sessions, each producing a PR using [isolated git worktrees](/articles/git-worktree-unlocks-agentic-development). Then the PRs conflicted with each other. PR #1 merges, PR #2 is now behind `main` and needs a rebase. PR #3 is behind and conflicting. I spent 20 minutes per session on manual rebases.

The deeper problem: [GitHub dismisses PR approvals when new commits are pushed](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches) — specifically when "Dismiss stale pull request approvals when new commits are pushed" is enabled in branch protection. Every rebase wiped the approval and triggered another review cycle.

**Lesson:** tie approvals to the PR, not the commit SHA.

### Iteration 5 — The Current System

When an agent finishes a PR, it calls `merge_pr(repo, pr_number, description)` via the telegram-bridge extension. This triggers a [Telegram inline keyboard message](https://core.telegram.org/bots/features#inline-keyboards) to the configured approver:

```
htekdev/taller-mecanico#47 — "Add appointment sorting by urgency"

✅ Merge Now  |  🤖 Agent Merge  |  ❌ Deny
```

**Merge Now** triggers an immediate squash merge if CI is already green. **Agent Merge** records the approval in `data/merge-queue.json` and dispatches the merge-agent. **Deny** ends the request. The approval takes 3 seconds. The rest is automated.

![Diagram of the three-button Telegram approval flow: an agent calls merge_pr(), Telegram presents Merge Now, Agent Merge, and Deny buttons, and each branch shows the steps the system takes after a human taps once.](/images/articles/agent-merge-speed-without-losing-control/agent-merge-flow.webp)
*Iteration 5: one tap routes the PR into immediate merge, queued merge-agent processing, or denial — the human spends three seconds, the system handles the rest.*

## The Approval Ledger That Survives Rebases

A local JSON ledger keyed on PR number — not commit SHA — is the design decision that makes everything else work. The merge-agent can rebase a branch three times and push three new commits, and Sofia's approval still holds. She approved the *intent* (this PR should ship), not a specific diff state. No external API, no approval state to reset, no rate limits.

When Sofia taps **🤖 Agent Merge**, the system writes an entry to `data/merge-queue.json`:

```json
{
  "repo": "htekdev/taller-mecanico",
  "pr_number": 47,
  "approved_by": "sofia",
  "approved_at": "2026-06-22T14:31:00Z",
  "sha_at_approval": "abc123f",
  "status": "approved"
}
```

![Side-by-side comparison: GitHub's native review ties approval to a commit SHA and gets dismissed on every rebase, while the local merge-queue.json ledger ties approval to a PR number and survives any number of rebases until CI is green.](/images/articles/agent-merge-speed-without-losing-control/agent-merge-ledger.webp)
*GitHub dismisses approvals on every rebase because they're bound to a SHA. The local ledger binds approval to a PR number — intent, not bytes — so rebases are free.*

The `sha_at_approval` field is there for audit trail — but the merge gate checks the ledger entry, not the SHA. [GitHub's native merge queue](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue) handles sequencing but resets GitHub's own review state when branches are rebased. The local ledger sidesteps this entirely. This is the pattern behind [approval gates that survive code churn](/articles/cryptographic-approval-gates-ai-agents): tie the authorization to the intent, not the implementation.

The hookflow `require-merge-approval.yml` enforces this at dispatch time — every merge-agent invocation must include a `PRs: [...]` array, and every entry must exist in the ledger. If it's not in `data/merge-queue.json`, the dispatch is blocked before the agent even runs.

## Review the Deliverable, Not the Code

Non-technical approval of AI-agent PRs is viable only with per-PR preview environments. With Vercel preview deployments attached to every PR and Supabase branch deployments for database-backed features, a non-technical approver reviews a live running version of the change — not a diff. They answer "does this outcome look right?" — a question anyone can answer.

The reason Sofia can approve 11 PRs in 8 minutes isn't because she's fast at reading code. It's because she's not reading code at all. Every PR gets a [Vercel preview deployment](https://vercel.com/docs/deployments/preview-deployments) — a full working version of the app with that change applied — linked directly in the Telegram notification.

For database-backed features, [Supabase branching](https://supabase.com/docs/guides/platform/branching) gives each PR its own isolated Supabase branch with the migrated schema. Sofia can test the actual feature behavior, not review a migration SQL diff. The question shifts from "does this code look right?" to "does this outcome look right?" That's a question anyone can answer.

This is a principle I want to be explicit about: **full preview environments per PR are not optional in an agentic workflow**. They're the mechanism that makes non-technical approval viable. Without them, every PR review is a code review, and you're back to the old throughput ceiling.

## The Sofia Multiplier

One non-technical operator. Thirteen PRs merged in a single day, across multiple agent sessions, with reviews — but no diffs read, no GitHub UI opened, no code checked out. If a developer merges 2–3 PRs on a busy day, Sofia's output that day was running at 5-6x that rate — entirely through Telegram taps on Vercel previews.

The day we hit 13 merged PRs, Sofia ran multiple agent sessions. She approved 11 via **🤖 Agent Merge**, one via **✅ Merge Now** (CI was already green), and denied one — a visual regression on mobile she caught from the Vercel preview.

Her total time on merge decisions: roughly 8 minutes across the full day. Every approval was a Telegram tap on a live preview. No GitHub UI. No diff reading. No checkout-and-run-locally.

That's the output of a 5-6x multiplier on normal developer throughput — with reviews included — driven by one person who has never written a line of production code.

[The agent infrastructure layer](/articles/the-agent-infrastructure-layer-is-here) isn't about replacing developers. It's about shifting who can participate in the shipping loop. Sofia's multiplier isn't a productivity stat — it's proof that the bottleneck was never the code. It was the merge ceremony around it.

## What Your Team Needs First

Before agent-merge can work reliably, your CI/CD stack needs to be at a specific maturity level: CI under 5 minutes, branch protection enforcing status checks, per-PR preview deployments, and agents locked to a deterministic merge tool. Without these, the merge-agent will escalate constantly instead of shipping. The [agentic development maturity curve](/articles/agentic-development-maturity-curve) covers the full picture.

- **CI under 5 minutes** — the merge-agent polls every 30 seconds with a 10-minute timeout. Slow CI means your queue backs up and PRs time out instead of merging.
- **Branch protection with required status checks** — [GitHub must enforce CI before any merge](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-status-checks-before-merging) is possible. If your repo allows merging with failing checks, the merge-agent will occasionally ship broken builds.
- **Per-PR preview environments** — [Vercel preview deployments](https://vercel.com/docs/deployments/preview-deployments), Netlify deploy previews, or equivalent. Non-technical approval isn't viable without them.
- **Hookflows blocking raw git** — agents must not be able to call `gh pr merge` directly. The hookflow is the enforcement layer, not a convention. Conventions drift; hookflows don't.
- **Sequential merge agent** — parallel PRs always conflict. The merge-agent processes one at a time, rebase → CI wait → merge. Do not parallelize this.

The failure mode when you skip these prerequisites is a merge-agent that escalates constantly, which trains your team to ignore it. The system only works if it merges reliably — that reliability comes from the infrastructure layer under it, not from better prompts.

## The Bottom Line

The approval never disappeared. Sofia still approves every PR. What changed is when she approves it — a 3-second Telegram tap on a live running preview — versus a 15-minute code review session she wasn't qualified to do anyway.

Your AI agents can already write the code. The question is whether your infrastructure can keep pace with them. Build the merge layer first. Then figure out what to automate next.

---

*The full implementation — `data/merge-queue.json`, the `merge_pr` Telegram tool, `enforce-merge-pr-tool-only.yml`, `require-merge-approval.yml`, and the merge-agent — lives in the rocha-family platform repo (private). The patterns here are the extractable parts.*
