HITL for autonomous agents: Where does the human go? Human-in-the-loop oversight for autonomous agents splits across four distinct layers — identity, protocol, infrastructure, and runtime — according to an analysis citing OpenID Connect's CIBA, MCP's elicitation specification, gateway holds, and durable-execution interrupts in LangGraph and Temporal. The core difficulty is timing: a person takes minutes to respond while a held request must survive client timeouts, proxy idle timeouts, and load balancer limits, with two minutes described as generous by network standards but insulting by human ones. MCP's tools specification recommends keeping a person able to deny a tool call, and CIBA is the most mature of the four approaches, with Auth0 and Okta having productized it. Human approval is easy when you are sitting in front of the agent. For an agent running by itself in a cluster, almost none of that holds. You’re in a meeting and your agent is running in a cluster. It has a service account, it has been asked to keep a service healthy, and it has just worked out that the right fix is to roll back a database migration. Nobody is watching it. That was rather the point of deploying it. You want to get notified to approve such an important action. This is a different problem from the one most people picture when they hear human in the loop . If you use a coding assistant, you already have human oversight and it costs almost nothing: the agent shows you a diff, you read it, you approve. That works because you are already there — at a keyboard, with the context in front of you, in the same second the agent needs an answer. An autonomous agent has none of that. There is no session to interrupt. The person who should decide is asleep, or in a meeting, or on a plane. The approval has to travel out of the cluster, reach a phone, and come back. And the request has to survive however long that takes. Almost everything that is hard about human oversight for autonomous agents follows from those two sentences. So the useful question is not whether to have a human – MCP’s tools specification recommends keeping a person able to deny a tool call, and every agent security note I have read says some version of the same thing. The useful question is where that human sits, and what happens to the request while it waits. Four places to put the human Four different layers of the stack have each grown an answer, and they are not the same answer. At the identity layer , the agent asks an identity provider for permission. The provider reaches you out of band, and your approval mints a short-lived token scoped to that one action. This is OpenID Connect’s CIBA https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1 0.html , and it is the most mature of the four — Auth0, Okta and others have productized it, and Christian Posta has made the case https://blog.christianposta.com/ai-agents-and-oidc-ciba/ for it as the natural home for agent oversight. At the protocol layer , the tool server pauses and asks a question back through the client. MCP calls this elicitation and specifies it properly. It also assumes there is a client with a person attached to it, which puts us back at two in the morning. At the infrastructure layer , a gateway in the request path holds the call, notifies someone, and then releases or refuses it. Nothing in the agent changes, because the agent never learns it was held. At the runtime layer , the agent framework pauses itself and checkpoints. LangGraph’s interrupt and Temporal’s durable execution both work this way: the pause is saved state rather than a blocked thread, so it survives a crash or a deploy. These are not four implementations of one feature. CIBA answers what authority does this action now carry . Elicitation answers what does the user want to happen . A gateway hold answers should this request proceed . A durable interrupt answers how does the work survive the wait . We started out treating them as alternatives and had to stop. The wait is the hard part A person takes minutes. A request does not. Hold the call open at a gateway and you get approval with no change to the agent at all — which is the whole reason to do it there — but you are now racing every timeout between the caller and you. Client timeouts, proxy idle timeouts, load balancer limits. You can raise them, but you raise them for all traffic, not just the held kind. Two minutes is generous by network standards and insulting by human ones. Return immediately instead, mark the call pending, and let the caller poll. TrueFoundry describe this shape https://www.truefoundry.com/blog/mcp-tool-approval-human-gate-call-path well and are honest about the cost: the client has to understand the convention and decide when to retry. That is a change to every agent you were trying to leave alone. Or push the pause into the runtime, where LangGraph and Temporal have already solved it properly. The wait is checkpointed, it survives a restart, and holding for an hour costs nothing. It also requires that you own the agent’s runtime, which rules out the third-party agent and the one you did not write. Each design pays one of those three prices. We pay the first. What does an approval prove? When the log says the action was approved, what has actually been established? In the strong version: this named person, authenticated at this strength, saw this specific request with these specific arguments, and decided at this time. In the weak version: a push went somewhere and something came back green. In our case it was the notification channel that opened the gap between the two. We integrated Cisco Duo as an approval backend, because it is already deployed at the kind of enterprise we work with and the push-to-phone experience is exactly right for the two-in-the-morning case. But Duo’s status response tells us the transaction was approved. It does not tell us who approved it, or on what device. So the approver we can record is the one we were configured to ask, not the one who actually tapped. Our first-party path, where someone answers through an authenticated session, can name the individual. The Duo path cannot. That is not a complaint about Duo, which is doing exactly what a second-factor product is designed to do. It is a warning about reading approval logs — and it is the strongest argument for the CIBA model, where the approver’s identity travels inside the token rather than sitting in a log line beside it. If you are choosing a design, ask what it can prove, not what it can block. Blocking is the easy half. Approval should not create permission There is a design fork here, and we came down firmly on one side of it. When the human says yes, does that create permission, or does it satisfy a condition in permission that already existed? CIBA takes the first path by construction: the approval mints a token, and the token is new authority. That is clean and portable, and it also means a leaked token is authority in someone else’s hands until its window closes. We took the second path. In Lynx a gate is an ordinary Cedar forbid rule with an annotation and a condition attached: @approvalRequired "Approve transfer funds on treasury" forbid principal, action, resource unless { context.approval.granted }; The annotation marks the rule as worth holding and carries the prompt the person reads. The unless clause is what an approval satisfies. Both halves are required, and our linter rejects one without the other. The consequence is the part I care about: the gateway never turns a deny into an allow. If the agent was not permitted to reach that server in the first place, no amount of approval gets it there, and the request is refused immediately rather than paging someone at 2am to rubber-stamp something that was never going to work. Approval is one input to a decision still made by policy. It is not a manual override, and I would not be comfortable shipping one that was. The person holding the phone Once a human is in the path, that human is a target, and the attacks are all about what the notification says. Most of what appears on an approval screen is influenced by someone else: the tool name comes from a server that may be compromised, the arguments come from a model that may have been steered by a retrieved document, and MCP annotations are explicitly hints rather than facts. Render all of that as one undifferentiated block and you are asking a half-awake person to spot a lie on a small screen. So we treat the rendering as a security control — authenticated caller identity first, written from what the gateway verified; caller-supplied arguments namespaced under an arg. prefix so nothing can pose as provenance; text flattened and truncated. None of it is clever. The alternative is a phishing surface with a corporate logo on it. The quieter failure is fatigue. If the answer is always yes, the gate has stopped being judgment and become latency with an audit trail. Every change that reduces the number of prompts — batching, scoping, remembering a decision for a window — is also a change that puts fewer requests in front of a person. I do not think there is a way out of that tension, only a way to be deliberate about where you sit in it. Where we have got to We build on agentgateway https://agentgateway.dev as our data plane, and Solo’s enterprise build already ships human approval with notifications out to Slack, email and mobile. This is not new ground, so I would rather say what we learned than claim novelty. We hold at the gateway, gated by Cedar, so that an agent nobody wants to modify can still be governed. We drive Duo’s asynchronous push for the out-of-band decision. That is deliberately not CIBA — Duo has no backchannel authorization endpoint and no CIBA grant — but the shape is the same: client-initiated, decoupled device, poll for a decision. We built the seam to that shape so a real CIBA backend can sit behind it later without the policy surface changing. Since MCP’s specification declines to standardize an approval workflow, borrowing the shape of the standard next door seemed better than inventing a fifth one. What we have not solved: the hold is synchronous, so it carries the timeout cost above. There is a single global approver, where the problem wants routing by resource and by policy. It covers MCP tool calls and not yet the other paths an agent can take. And the trail does not yet surface the way it needs to for someone reconstructing an incident months later. I am writing this before those are fixed rather than after, because the feature is not the interesting part. The interesting part is that an identity specification, a protocol specification, a set of gateways and a set of agent frameworks all produce something different when a human says yes, and none of them drops into the place of another. That is worth working out now, while the designs are still soft enough to change. Agents will keep asking for permission to do consequential things while nobody is looking. We should be more precise about what we mean when we say a human approved it. Further reading: the OpenID CIBA specification https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1 0.html ; Christian Posta on agent autonomy and CIBA; Auth0’s asynchronous authorization https://blog.christianposta.com/ai-agents-and-oidc-ciba/ for agents; TrueFoundry on tool approvals at the gateway boundary https://www.truefoundry.com/blog/mcp-tool-approval-human-gate-call-path ; LangGraph durable execution https://docs.langchain.com/oss/python/langgraph/durable-execution and Temporal’s LangGraph plugin https://temporal.io/blog/temporal-langgraph-plugin-durable-execution .