Skip to content

AI agent approval workflows: review the batch, not each call

Approving every tool call turns into rubber-stamping. A better AI agent approval workflow: let the agent work on a branch, review the changes, merge.

Say an agent updates 1,200 customer records overnight. Three of them get the wrong plan. Nothing errors, every value looks plausible, and the morning report is fine. Somebody notices three weeks later, when a customer is billed for a plan they never chose.

An approval step would have stopped it, in theory. In practice the agent would have asked 1,200 times, somebody would have clicked "approve" 1,200 times, and the three would have gone through with the rest. Or, more likely, the team would have turned approval off for "routine" updates, and the three would have gone through anyway.

This guide is about a different place to put the person: not in front of every call, but in front of the result. Let the agent do the whole job where it does not count yet, then review what changed, put back what is wrong, and let the rest go live.

What an AI agent approval workflow is ​

An AI agent approval workflow is a checkpoint between what an agent does and what takes effect. The system, not the prompt, enforces it: the agent cannot get past it by being persuaded or confused. A person sees what the agent wants to change, decides, and the decision is kept with who made it and when.

Most write-ups stop at "a person approves risky actions". The useful questions are where the checkpoint sits, what the person sees there, and what happens when they say no.

Why approving every tool call breaks down ​

The usual design is a gate in front of each tool call: the agent proposes update_record, a person approves or rejects, the call runs. It is easy to build and easy to explain. It fails in three ways.

Review fatigue turns approval into a habit ​

We counted our own work. In eight real Claude Code sessions on this project, the agent made 3,243 tool calls, and people stepped in 175 times, 97 of them to approve something. That is already a lot of clicking for one developer. Scale it to a team running agents on business data every day and the approvals stop being read. If a person would approve 99 of 100 requests without changing anything, the gate is not oversight: it is a ritual.

Agents fail quietly ​

A failing program usually throws an error. An agent that misunderstands a request does not. It writes the wrong value into the right field, or the right value into 500 rows when you meant 5. Each single call looks reasonable. Approving them one by one, a person sees one plausible change at a time.

The mistake is in the pattern, not in one call ​

"Set the plan to Business for every customer who upgraded" is fine. Doing it for three customers who downgraded is the bug, and you only see it when you look at all 1,200 changes together: which ones stand out, which ones do not fit the rule. A per-call gate never shows the whole set.

Where the checkpoint can go ​

There are five places to put a person, and each catches different mistakes:

CheckpointWhat the person seesCatchesCosts
Before every callOne proposed actionA single dangerous actionConstant interruptions, then rubber-stamping
On a draftA proposed text or planWrong intent, before any workOnly works when the work is one artefact
SampledA random few actionsSystematic errors, eventuallyMisses rare mistakes, like three in 1,200
Exceptions onlyWhat a rule flagsWhat you thought of in advanceMisses what you did not
After the work, before it countsEverything that changed, togetherPatterns, outliers, wrong scopeNeeds a place where the work can wait

The last one is how software teams have worked for decades: a developer never edits the live product. They change a copy, somebody reviews the difference, and only then is it merged. The cost is the last column: you need somewhere for the agent's work to wait. For code that is a branch. For business data, it can be too.

Review the change set: agents on a branch ​

Here is how that works in practice, using CodeKasten as the example. The ideas carry over to any system that can keep a copy of your data and compare it with the original.

The agent works on its own branch, never on main ​

Every agent gets a branch of the workspace: a full copy of the records, types, workflows and settings. The live branch, main, is protected: no agent writes to it, whatever key it holds. The agent's changes are real on its branch (it can read them back, build on them, run workflows over them) but nobody else's data moves.

A session's branch is private to the person who started the agent until they ask for a merge, so half-done work is not shown to the whole team.

What the reviewer sees: every change, field by field ​

When the agent is done it commits, with a message saying what it did and why, and asks for a merge. Whoever may merge into main gets the request in their Inbox.

[PICTURE: docs/public/shots/versioning-request-merge.png - Request merge in the branch menu]

Opening it shows the branch's commits. Each commit lists every record that was added, modified or deleted, and for each one, field by field, the value before and after. 1,200 updated customers are 1,200 rows of "plan: Starter → Business", and the three that went "Business → Starter" stand out in a way that three separate approval prompts never would.

[PICTURE: docs/public/shots/versioning-commit.png - A commit: each record, field by field, before and after]

On a large change set, a second agent can do the first read: it goes through the branch and reports, say, "1,197 changes follow the rule; these 3 do not". It approves nothing. It only tells the person where to look.

Put back what is wrong, merge the rest ​

The reviewer now has three choices, and none of them loses work:

  • Merge: the changes go live.
  • Fix, then merge: change the three records back on the branch, or revert the commit that made them, and merge the rest.
  • Send it back: tell the agent what was wrong; it tries again on the same branch.

[PICTURE: docs/public/shots/versioning-merge.png - Merging a branch into main]

After a merge every change stays in the history. Reverting a commit later makes a new commit that undoes it, deleted records included. "Undo" is not a feature somebody has to remember to build: it is how the data is kept.

Effects outside your data wait for the merge ​

Records can be put back. An e-mail cannot be unsent. So on an agent's branch, a workflow can change records at once, but anything it would send out (a connector's call to another service, a message to a customer) is held. The merge sends it, once. The merge screen lists what is waiting, and a branch closed without merging sends nothing.

This is the part per-call gates usually get backwards: they ask about every harmless write and let a webhook through because it "is not a database change".

When the agent needs a decision in the middle ​

Some work cannot wait for the end. The agent hits a question only a person can answer: which of two customers is the real one, whether a price change applies to existing contracts, whether to go ahead with a release.

For that, the agent asks. The question lands in the person's Inbox with the choices the agent offers ("ship it", "not now"), on the web or on their phone, and the answer is kept with the question: who decided what, and when.

[PICTURE: docs/public/shots/mobile-inbox-ask.png - An agent's question in the Inbox, on the phone]

The release that put this blog's pages in better shape for search went out that way. The agent asked "Ship Easier to find in search?", the answer "ship it" was given in the Inbox, and the question's ticket is part of the release's record. One honest limit, as we write this: the agent waits up to 50 seconds for the answer and then holds a ticket. An answer given later does not wake it by itself, so somebody tells it to look. We are fixing that.

The difference from a per-call gate is that the agent asks about a decision, once, in words a person can answer. It does not ask permission for every step of carrying it out.

Know which agent did what ​

An audit trail that says "changed by Maria" when Maria's agent made the change is worse than none: it is wrong. Approval workflows need to know who acted.

So in CodeKasten an agent is a principal of its own. The first time an agent changes something in a workspace, an Agent is made for it, and from then on its id is on every record it writes, every commit and every merge request. The app shows it as "Claude, for Maria": whose agent, and that it was an agent.

An agent also does not hold its person's key. It works with a grant narrowed from that key: which types it may write, which branches, who it may notify, whether it may merge, define types or run workflows at all. A request the grant does not cover is refused before it reaches the data. A person can see what each of their agents holds, revoke it, or pause or stop the agent.

Which actions still need a gate before they run ​

A branch does not remove the need for every gate. It moves most of them to one place. A reasonable split:

ActionCheckpoint
ReadingNone, within what the person may read and the grant allows
Changing records, types, documents on its own branchNone while working; everything reviewed at the merge
Merging into mainA person who may merge
Calls to other services, messages outHeld until the merge, sent once
A decision only a person can makeask: one question in the Inbox
What the grant does not allowRefused outright

A checklist for your first approval workflow ​

Whatever tools you use:

  1. Give the agent a copy, not the original. A branch, a staging table, a sandbox: somewhere its work is real but not live.
  2. Protect the original in the system, not in the prompt.
  3. Review the difference, not the requests. Show every changed record with its before and after, together, so outliers stand out.
  4. Make putting back cheap: revert a change, fix a few rows, merge the rest.
  5. Hold external effects (mail, webhooks, payments) until the merge.
  6. Ask for decisions, not permissions. One question in words beats a hundred approve buttons.
  7. Record the agent as the actor, with the person it works for.
  8. Narrow what the agent holds to what the job needs, and make it revocable.
  9. Let a second agent do the first read of a big change set, and a person decide.
  10. Keep the history. Every merged change should be traceable and reversible later.

FAQ ​

Does human-in-the-loop slow agents down? ​

Only if the person is in front of every step. With the review at the end, the agent works at full speed on its branch, and the person spends their time once, on the result, looking first at what a review agent flagged.

Human in the loop or human on the loop? ​

"In the loop" means a person approves before something happens; "on the loop" means they monitor and step in after. A branch gives you both: the agent works unwatched (on the loop), and nothing reaches live data until a person merges (in the loop).

How do you undo an AI agent's changes? ​

If the changes are still on the agent's branch, change them back there or drop the branch: the live data was never touched. If they were merged, revert the commit that made them; the revert is a new commit, so the history shows both the mistake and its fix.

Try it ​

CodeKasten gives your team and your agents one workspace, with every agent change on a branch a person reviews. Open the app, or connect an agent you already use through MCP. For how a team works with agents day to day, read How your team and AI agents work together in CodeKasten; for where it is going, CodeKasten as an agentic OS. The details are in the docs: Branches, Merging and Commits and history.