Build vs buy: the real cost of a human-in-the-loop approval loop
What it actually takes to build agent approvals in-house: delivery, enrollment, pending state, fail-closed timeouts, recovery, and audit, itemized honestly.
The approval loop demo is a weekend project: a Slack message with two buttons, a callback, an if statement. The approval loop you can trust with refunds, deletions, and production deploys is a different artifact, and the distance between the two is where build-versus-buy actually gets decided. Here is the honest itemization.
Key takeaways
- Seven components separate the demo from the system: delivery, enrollment, pending state, correlation, fail-closed timeout, recovery, and audit. Each has a silent failure mode.
- The recurring cost dominates the build cost: push credentials rot, device tokens churn, timeout edge cases surface in incidents, and the audit story gets audited.
- Building is right in specific cases (compliance boundaries, deep workflow-engine coupling, approvals as the product). Ambient approvals for an agent feature are usually a buy.
The seven components, itemized
1. Delivery that reaches a phone. The person who should decide is rarely at a desk. Real phone delivery means native push credentials and token lifecycles, or PWA push notifications with subscription management, or both, plus a fallback chain when a device goes quiet. This alone is a standing operational commitment.
2. Enrollment. Every approver needs a route from "user in your database" to "device that rings." If your approvers are your own customers, the flow has to cost them almost nothing, because every extra step loses users who then silently never receive approvals. Getting this to one tap, with no account and no key on their side, is a product problem as much as an engineering one.
3. Pending state. The open question has to live in storage, keyed and queryable, or a crash erases the fact that anyone was asked.
4. Correlation. Answers arrive out of band, possibly twice, possibly late. Matching them to the right waiting agent, idempotently, is classic distributed-systems work.
5. Fail-closed timeout. Silence has to become a definite no, enforced centrally. The teams that skip this discover it during the incident where a missed Slack message counted as consent.
6. Recovery. Deploys and restarts happen mid-wait. Either the wait polls durable state, or a webhook resumes parked work, and both paths need building and testing.
7. The record. Who approved what, when, with the question shown. Append-only, exportable, and trusted enough to settle an argument.
Each component's prototype is genuinely easy, which is what makes the estimate go wrong. The production version of each is where the quarter goes.
The cost that does not show up in the estimate
The build cost is one number; the ownership cost is the one that compounds. Push certificates expire. Token churn produces quiet delivery failures that look like users ignoring approvals. The timeout policy meets daylight saving time. The audit table meets a compliance review. None of this is hard on its own, and all of it is now your team's permanent side quest, competing with the product you actually set out to ship.
When building is the right call
An honest version of this post has to include the cases where in-house wins.
Build when approvals cannot leave your infrastructure for regulatory reasons, and a managed decide page is off the table regardless of vendor. Build when your approvals are deeply fused to a workflow engine you already operate, where an external wait would fight the engine's own suspend-and-resume. And build when human approval is your product's core mechanic rather than a safety feature, because then the loop deserves your roadmap.
Outside those cases, an approval loop is undifferentiated heavy lifting: your users cannot tell your bespoke timeout policy from anyone else's, but they can tell when it silently fails.
What the buy side looks like
For comparison, this is the entire integration with Pushary:
import { createPusharyServer } from "@pushary/server"
const pushary = createPusharyServer({ apiKey: process.env.PUSHARY_API_KEY! })
// Once per user: keyless, one-tap phone connect.
await pushary.enroll(user.id)
// At the step that needs a person. Blocks. Fails closed.
const decision = await pushary.decisions.ask({
question: "Approve $480 refund to customer #4471?",
externalId: user.id,
type: "confirm",
})
if (decision.approved) await issueRefund()Delivery, enrollment, pending state, correlation, fail-closed timeout, recovery, and the ledger are the service. The framework adapters wrap the same two calls for the Vercel AI SDK, LangGraph, CrewAI, Mastra, the OpenAI Agents SDK, Eve, Hermes, OpenClaw, and MCP clients.
The decision is not really build versus buy on features, because you can build all of it. It is a question of whose roadmap the approval loop deserves to be on. If it is not yours, the two calls are waiting.
Frequently asked questions
What do I have to build for an in-house agent approval loop?
Seven pieces: push delivery to real devices, an enrollment flow for each approver, a pending-decision store, question-to-answer correlation, a fail-closed timeout policy, recovery so waits survive restarts, and an audit record. The prototype version of each is easy. The version you can trust with refunds is not.
When does building in-house make sense?
When approvals must stay inside your infrastructure for compliance, when they are deeply coupled to a workflow engine you already operate, or when approvals are your product rather than a feature of it. Those are real cases, and they come with an ongoing ownership cost, not a one-time build.
What does the buy side look like with Pushary?
Two calls. enroll(externalId) connects an approver's phone with a keyless one-tap link, and decisions.ask() blocks on a real answer, fails closed on silence, and writes to a durable ledger. Framework adapters exist for the Vercel AI SDK, LangGraph, CrewAI, Mastra, the OpenAI Agents SDK, Eve, Hermes, OpenClaw, and MCP.