{"slug": "human-in-the-loop-authorization-patterns-for-agents", "title": "Human-in-the-loop Authorization Patterns for Agents", "summary": "Solo.io's research on human-in-the-loop authorization for AI agents identifies two types of approval flows—harness checks and enterprise policy-driven approvals—and evaluates five protocols (CHEQ, TAC, AAuth, MCP MRTR, MCP Tasks) against criteria including accountable authority, exact binding, revalidation, bounded execution, and durable evidence. The analysis emphasizes that enterprise HitL requires enforcement-point-mandated approvals, not agent self-policing, to support compliance with regulations like Sarbanes-Oxley.", "body_md": "# Human-in-the-loop Authorization Patterns for Agents\n\nIn my work with customers adopting and securing AI agents at scale in enterprise environments, we are running into “human in the loop” usecases. For example, usecases where a sensitive operation MUST be approved by a human; sometimes even a different human than the one on who’s behalf the agent is acting. As part of my work on Agent Identity and Access Management here at Solo.io, I research various patterns and options. I want to distill some of the patterns/spec drafts that exist to address these types of usecases.\n\n## Two kinds of human in the loop\n\nThere are two types of “Human in the Loop (HitL) we’ll call out. The first is a **harness check** based HitL. The harness classifies some action as “looks risky” and asks its user, “Is this okay?” This is useful because it gives the user to review risky behavior as determined by the agent/harness.\n\nIn the organizations we’ve been working with, this is not the only type of HitL needed.\n\nIf the model, prompt, or tool description decides when to ask, the agent is largely policing itself. Another execution path may call an API directly. A plugin or sub-agent may bypass the harness. Prompt injection may convince the agent that approval is unnecessary or already happened. Even after the click, the agent may execute something different from what it displayed.\n\nEnterprise HitL has a stronger meaning:\n\nPolicy requires an authorized principal to approve this exact action before an enforcement point will permit it to execute.\n\nHere, asking is not optional. The agent does not decide when a human decision is required, the authorization boundary does that.\n\nIn this blog we’ll look at the following options to solve this problem:\n\n- Confirmation with Human Exchange of Qutoations (CHEQ)\n- Transaction Authorization Challenge tokens (TAC)\n- Agent Auth (AAuth)\n- MCP Multi Round-Trip Requests (MRTR)\n- MCP Tasks\n\n## What Enterprise HitL must provide\n\nBefore comparing protocols, we need criteria. A serious HitL authorization system should provide:\n\n**Accountable authority.** Identify the agent, requester, approver, workload, and any delegation chain. Policy determines who may approve and whether duties must be separated.**Exact binding.** Bind the decision to the operation, parameters, destination, requester, policy, expiry, and the semantic display reviewed by the approver.**Revalidation at enforcement.** Before execution, re-check identity, policy, schema, credentials, destination, and the exact operation. Drift requires a new decision. Stale credentials require re-acquisition.**Bounded execution.** Define whether approval covers one invocation, a reusable scope, a session, or a mission.**Durable evidence.** Record what was requested, what the approver saw, who decided, which policy applied, what executed, and what happened.\n\nThese properties are what make HitL critical for compliance. No protocol is inherently “SOX compliant” for example, and the [Sarbanes-Oxley Act](https://www.govinfo.gov/content/pkg/COMPS-1883/pdf/COMPS-1883.pdf) does not prescribe an agent approval flow. But when agents can affect systems involved in financial reporting, HitL can support internal controls.\n\nWith those requirements in hand, we can evaluate the leading patterns.\n\n## CHEQ: memorialize what the human confirmed\n\n[CHEQ](https://datatracker.ietf.org/doc/draft-rosenberg-aiproto-cheq/) was an early draft which brough the idea of a “confirmation server” to familiar entities like clients and resource servers. When an operation needs confirmation, the resource returns a set of URIs. The confirmation service obtains a signed CHEQ object describing the operation, renders it to an authenticated human, adds the confirmation, and returns it to the resource. The agent polls a result URI.\n\nThe flow looks roughly like this:\n\n**The agent calls the resource.** For example, booking a flight with some of its details:\n\n```\n1\n2\n3\n4\n5\nPOST /api/v1/book-flight\nAuthorization: Bearer <agent-access-token>\nContent-Type: application/json\n\n{\"flight_number\":\"UA23\",\"date\":\"2026-09-08\",\"cost_usd\":1200}\n```\n\n**The resource’s policy requires confirmation.** It returns`202 Accepted`\n\nwith a URI pack. The agent remembers the result URI and passes the pack to its user-facing application.\n\n```\n1\n2\n3\n4\n5\n6\n7\n8\nHTTP/1.1 202 Accepted\nContent-Type: application/json\n\n{\n  \"confirmation uri\":\"https://confirm.example\",\n  \"resource uri\":\"https://api.airline.example/confirmations/8asdjd8g9g0as\",\n  \"result uri\":\"https://api.airline.example/results/nn88kak0s0d8jj39sla\"\n}\n```\n\n**The user application starts the browser interaction.** It sends the resource URI to the confirmation service. The confirmation service authenticates the human, then uses its own service credential to retrieve the signed CHEQ object from the resource server. The draft does not finish the exact parameter or retrieval URI syntax.\n\n```\n1\n2\nGET {resource-server}/cheq\nAuthorization: Bearer <confirmation-service-token>\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n{\n  \"version\": 1.0,\n  \"operation\": \"https://airline.example/api/v1/book-flight\",\n  \"operation name\": \"Book airline\",\n  \"inputs\": {\n    \"parameters\": [\n      {\"parameter name\":\"flight number\",\"parameter value\":\"UA23\"},\n      {\"parameter name\":\"flight date\",\"parameter value\":\"8 September 2026\"},\n      {\"parameter name\":\"flight cost\",\"parameter value\":\"$1200 USD\"}\n    ]\n  },\n  \"date\": \"18 August 2026, 15:00 UTC\"\n}\n```\n\n**The human accepts or rejects.** On acceptance, the confirmation service adds its signature and posts the signed CHEQ object to the transaction-specific resource URI.\n\n```\n1\n2\n3\n4\nPOST https://api.airline.example/confirmations/8asdjd8g9g0as?accept\nAuthorization: Bearer <confirmation-service-token>\n\n<resource-and-confirmation-server-signed CHEQ object>\n```\n\nThe draft leaves the signed-object body, signature, and multi-signature formats undefined.\n\n**The agent polls the result URI.** Once the resource has recorded the decision, it returns the final application result. CHEQ specifies polling but does not define the HTTP method, pending response, cadence, or replay behavior; this`GET`\n\nis illustrative.\n\n```\n1\n2\nGET /results/nn88kak0s0d8jj39sla\nAuthorization: Bearer <agent-access-token>\n1\n{\"booking_ref\":\"F7K2Q\",\"status\":\"confirmed\"}\n```\n\nNotice what does—and does not—cross the agent. The agent receives the URI pack and final result. The signed CHEQ object and human decision travel between the confirmation service and resource server, outside the agent’s control.\n\nCHEQ’s strongest idea is that the human should review a representation protected from manipulation by the agent or confirmation UI. It also explores privacy patterns where sensitive values need not pass through the agent.\n\nThe evaluated draft is incomplete, however, and at time of writing expired. CHEQ gives us a valuable trusted-confirmation model, but not a complete enterprise authorization architecture.\n\n## TAC: authorize a challenged transaction\n\nThe [OAuth Transaction Authorization Challenge](https://datatracker.ietf.org/doc/draft-rosomakho-oauth-txn-challenge/) is a recent draft (more thought through than CHEQ) which also starts at the protected resource. When a client requests a sensitive operation, the resource returns a signed challenge describing that transaction and its authorization details. The client submits the challenge to an authorization server, which applies policy and obtains any required approval out of band. Once approved, the server issues an access token bound to the transaction. The client retries the operation with that token.\n\nThe flow looks roughly like this:\n\n**The agent calls the protected resource and opts into TAC.**\n\n```\n1\n2\n3\n4\n5\n6\nPOST /payments\nAuthorization: Bearer <initial-access-token>\nAccept-Txn-Challenge: ?1\nContent-Type: application/json\n\n{\"amount\":\"5000.00\",\"currency\":\"GBP\",\"recipient\":\"Example Ltd\"}\n```\n\n**The resource challenges the transaction.** It returns an OAuth error containing a signed JWT. The JWT identifies the resource (`iss`\n\n), trusted authorization server (`aud`\n\n), transaction (`txn`\n\n), expiry, requested authorization details, reason, and optional actor/delegation context.\n\n```\n1\n2\n3\nHTTP/1.1 401 Unauthorized\nWWW-Authenticate: Bearer error=\"transaction_authorization_required\",\n  transaction_challenge=\"<signed-challenge-jwt>\"\n```\n\nThe decoded challenge payload looks like:\n\n```\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n{\n  \"iss\":\"https://resource.example.com\",\n  \"aud\":\"https://as.example.com\",\n  \"iat\":1787094000,\n  \"exp\":1787094300,\n  \"jti\":\"f1f7c8c4-2f8c-4c6a-83d1-example\",\n  \"txn\":\"97053963-771d-49cc-a4e3-20aad399c312\",\n  \"authorization_details\":[{\n    \"type\":\"payment\",\n    \"actions\":[\"initiate\"],\n    \"locations\":[\"https://payments.example.com/accounts/123\"],\n    \"instructedAmount\":{\"currency\":\"GBP\",\"amount\":\"5000.00\"},\n    \"creditorName\":\"Example Ltd\"\n  }],\n  \"reason\":\"Approval is required before initiating this payment.\",\n  \"act\":{\"sub\":\"spiffe://example.com/aiagent/6526f880\"}\n}\n```\n\n**The agent relays the challenge and the client submits it to the authorization server.** The agent must relay the challenge without modification. Confidential clients authenticate as they would at an OAuth token endpoint; public clients identify themselves with`client_id`\n\n.\n\n```\n1\n2\n3\n4\n5\nPOST /txn-authorization\nHost: as.example.com\nContent-Type: application/x-www-form-urlencoded\n\nclient_id=s6BhdRkqt3&transaction_challenge=<signed-challenge-jwt>\n```\n\n**The authorization server accepts the request for processing.** This does not mean the payment is approved. It gives the client a transaction authorization ID, expiry, polling interval, and optionally a URI for human interaction. How the human or organizational workflow authenticates and decides is owned by the authorization server.\n\n```\n1\n2\n3\n4\n5\n6\n{\n  \"transaction_authorization_id\":\"txn-authz-abc123\",\n  \"authorization_uri\":\"https://as.example.com/txn-authorization/txn-authz-abc123\",\n  \"expires_in\":300,\n  \"interval\":5\n}\n```\n\n**The client polls the authorization server.** It posts the transaction authorization ID. While the decision is open, it receives`authorization_pending`\n\nor`slow_down`\n\n. After approval, it receives a short-lived access token and the granted authorization details.\n\n```\n1\n2\n3\n4\n5\nPOST /txn-authorization\nHost: as.example.com\nContent-Type: application/x-www-form-urlencoded\n\nclient_id=s6BhdRkqt3&transaction_authorization_id=txn-authz-abc123\n1\n2\n3\n4\nHTTP/1.1 400 Bad Request\nContent-Type: application/json\n\n{\"error\":\"authorization_pending\"}\n```\n\nAfter approval, the same poll returns:\n\n```\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n{\n  \"access_token\":\"<transaction-bound-access-token>\",\n  \"token_type\":\"Bearer\",\n  \"expires_in\":120,\n  \"authorization_details\":[{\n    \"type\":\"payment\",\n    \"actions\":[\"initiate\"],\n    \"locations\":[\"https://payments.example.com/accounts/123\"],\n    \"instructedAmount\":{\"currency\":\"GBP\",\"amount\":\"5000.00\"},\n    \"creditorName\":\"Example Ltd\"\n  }]\n}\n```\n\n**The agent retries the challenged operation with the new token.** The resource verifies the token issuer, audience, expiry,`txn`\n\n, granted authorization details, requester context, and—where required—single-use state before executing.\n\n```\n1\n2\n3\n4\n5\nPOST /payments\nAuthorization: Bearer <transaction-bound-access-token>\nContent-Type: application/json\n\n{\"amount\":\"5000.00\",\"currency\":\"GBP\",\"recipient\":\"Example Ltd\"}\n```\n\nUnlike CHEQ, TAC deliberately sends the artifact that authorizes execution through the client and agent. The signed challenge protects what was requested; the access token represents what was granted; the resource must ensure the retried operation matches both.\n\nThis draft may fit orgs nicer where policy and human workflows at an OAuth authorization server. The resource, not the agent, defines the operation being authorized, and structured authorization details can support policy evaluation and trustworthy display.\n\nThe tradeoff is that the authorizing token passes through the client. High-impact operations need short lifetimes, sender constraints, exact transaction matching, and durable replay detection. TAC also brings OAuth machinery into protocols and local-tool environments where it may not fit naturally.\n\nTAC is strongest when the authorization server is already the natural enterprise decision point.\n\n## AAuth: an authorization architecture for agents\n\n[AAuth](https://datatracker.ietf.org/doc/draft-hardt-oauth-aauth-protocol/) is broader than a confirmation protocol. It begins with portable, per-instance agent identity, dyanmic registration, and proof-of-possession signatures (ie, no bearer tokens anywhere). Resources can authorize by agent identity, manage authorization themselves, rely on the agent’s Person Server (entity that represents a person), or federate that Person Server with a resource’s Access Server. See the complete draft for more.\n\nTo compare it directly with CHEQ and TAC, start with AAuth’s simplest **resource-managed** mode:\n\n**The agent signs a request with its agent identity.**`Signature-Key`\n\ncarries an agent token bound to the key used for the HTTP Message Signature.\n\n```\n1\n2\n3\n4\n5\n6\n7\n8\nPOST /payments\nHost: resource.example\nContent-Type: application/json\nSignature-Input: sig=(\"@method\" \"@authority\" \"@path\" \"signature-key\");created=1787094000\nSignature: sig=:<signature-bytes>:\nSignature-Key: sig=jwt;jwt=\"<aa-agent-jwt>\"\n\n{\"amount\":\"5000.00\",\"currency\":\"GBP\",\"recipient\":\"Example Ltd\"}\n```\n\n**The resource requires human interaction.** It returns`202 Accepted`\n\n, a same-origin pending URL, polling guidance, and an AAuth requirement containing a human-facing URL and correlation code. AAuth also supports a back-channel human approval, discussed below.\n\n```\n1\n2\n3\n4\n5\n6\n7\n8\n9\nHTTP/1.1 202 Accepted\nLocation: https://resource.example/pending/abc123\nRetry-After: 5\nCache-Control: no-store\nAAuth-Requirement: requirement=interaction;\n  url=\"https://resource.example/interaction\"; code=\"A1B2-C3D4\"\nContent-Type: application/json\n\n{\"status\":\"pending\"}\n```\n\n**The agent directs the human to the interaction.** It opens or displays the URL with the code appended. The resource authenticates the human and collects the decision. The code locates the pending request; it does not authorize approval by itself.\n\n```\n1\nhttps://resource.example/interaction?code=A1B2-C3D4\n```\n\n**The agent polls the pending URL with signed** While the decision is open, the resource returns`GET`\n\nrequests.`202`\n\nwith`pending`\n\nor`interacting`\n\n. After approval, it returns`200`\n\nand may issue an opaque`AAuth-Access`\n\ntoken.\n\n```\n1\n2\n3\n4\n5\nGET /pending/abc123\nHost: resource.example\nSignature-Input: sig=(\"@method\" \"@authority\" \"@path\" \"signature-key\");created=1787094030\nSignature: sig=:<signature-bytes>:\nSignature-Key: sig=jwt;jwt=\"<aa-agent-jwt>\"\n1\n2\n3\n4\n5\n6\nHTTP/1.1 200 OK\nAAuth-Access: <opaque-access-token>\nCache-Control: no-store\nContent-Type: application/json\n\n{\"status\":\"authorized\",\"scope\":\"payments.initiate\"}\n```\n\n**The agent retries the operation with that authorization.** The`AAuth-Access`\n\ntoken is covered by the agent’s HTTP signature, so stealing the token alone is not enough to use it.\n\n```\n1\n2\n3\n4\n5\n6\n7\n8\n9\nPOST /payments\nHost: resource.example\nAuthorization: AAuth <opaque-access-token>\nContent-Type: application/json\nSignature-Input: sig=(\"@method\" \"@authority\" \"@path\" \"authorization\" \"signature-key\");created=1787094040\nSignature: sig=:<signature-bytes>:\nSignature-Key: sig=jwt;jwt=\"<aa-agent-jwt>\"\n\n{\"amount\":\"5000.00\",\"currency\":\"GBP\",\"recipient\":\"Example Ltd\"}\n```\n\nFor approval happening elsewhere, ie, an administrator, resource owner, or compliance queue, the same deferred flow uses `requirement=approval`\n\nwithout asking the agent to present a URL or code. The agent simply polls for the decision.\n\nThis two-party example is only the shortest AAuth path. The full protocol defines [four resource-access modes](https://datatracker.ietf.org/doc/html/draft-hardt-oauth-aauth-protocol-10#section-4.1), a [Person Server](https://datatracker.ietf.org/doc/html/draft-hardt-oauth-aauth-protocol-10#section-7) that can govern remote resources and local actions such as tool calls or file writes, and optional [missions](https://datatracker.ietf.org/doc/html/draft-hardt-oauth-aauth-protocol-10#section-8) carrying broader intent and history. Those pieces reuse the same deferred-response pattern for interaction, approval, clarification, revision, and cancellation.\n\nLike TAC, this flow polls for authorization and then retries the operation. Unlike TAC’s bearer-token example, `AAuth-Access`\n\nis bound to the agent’s signed request. The simple resource-managed grant is still intended for subsequent calls; exact, single-use approval for one high-impact invocation may require a stricter profile.\n\nOf the patterns here, AAuth is the most complete attempt at a general agent authorization architecture.\n\n## MCP MRTR: retry the original operation\n\nMCP’s [multi-round-trip request pattern](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr) lets a server pause an operation by returning `input_required`\n\nwith input requests and protected `requestState`\n\n. The client performs the requested interaction, then retries the original MCP method with that state. If approval is still pending, the server can ask it to retry again. Once approved, the server revalidates and executes the current operation.\n\nThe protocol flow looks like this:\n\n**The client makes the original tool call.**\n\n```\n1\n2\n3\n4\n5\n6\n7\n8\n9\n{\n  \"jsonrpc\":\"2.0\",\n  \"id\":1,\n  \"method\":\"tools/call\",\n  \"params\":{\n    \"name\":\"initiate_payment\",\n    \"arguments\":{\"amount\":\"5000.00\",\"currency\":\"GBP\",\"recipient\":\"Example Ltd\"}\n  }\n}\n```\n\n**The server terminates that request with** Here it asks the client to present a URL-mode elicitation and returns opaque continuation state. Because that state will influence authorization, the server must integrity-protect it and bind it to the requester and operation. The server can send this request only if the client advertised support for URL elicitation.`input_required`\n\n.\n\n```\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n{\n  \"jsonrpc\":\"2.0\",\n  \"id\":1,\n  \"result\":{\n    \"resultType\":\"input_required\",\n    \"inputRequests\":{\n      \"approval\":{\n        \"method\":\"elicitation/create\",\n        \"params\":{\n          \"mode\":\"url\",\n          \"message\":\"Review this GBP 5,000 payment\",\n          \"url\":\"https://approve.example/requests/abc123\"\n        }\n      }\n    },\n    \"requestState\":\"<opaque-integrity-protected-state>\"\n  }\n}\n```\n\n**The client presents the interaction to the human.** For URL elicitation, an`accept`\n\nresponse means the human agreed to open the URL, not that they approved the payment. The approval itself happens out of band.**The client retries the original method with a new JSON-RPC ID.** It sends the operation again, echoes`requestState`\n\nexactly, and includes the elicitation response.\n\n```\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n{\n  \"jsonrpc\":\"2.0\",\n  \"id\":2,\n  \"method\":\"tools/call\",\n  \"params\":{\n    \"name\":\"initiate_payment\",\n    \"arguments\":{\"amount\":\"5000.00\",\"currency\":\"GBP\",\"recipient\":\"Example Ltd\"},\n    \"inputResponses\":{\"approval\":{\"action\":\"accept\"}},\n    \"requestState\":\"<opaque-integrity-protected-state>\"\n  }\n}\n```\n\n**The server either asks for another retry or completes the call.** A still-pending decision can produce another`input_required`\n\n, even with only refreshed`requestState`\n\n. Once approved, the server revalidates the retried operation, executes it, and returns the ordinary tool result.\n\n```\n1\n2\n3\n4\n5\n6\n7\n8\n{\n  \"jsonrpc\":\"2.0\",\n  \"id\":2,\n  \"result\":{\n    \"resultType\":\"input_required\",\n    \"requestState\":\"<refreshed-opaque-state>\"\n  }\n}\n1\n2\n3\n4\n5\n6\n7\n8\n9\n{\n  \"jsonrpc\":\"2.0\",\n  \"id\":3,\n  \"result\":{\n    \"resultType\":\"complete\",\n    \"content\":[{\"type\":\"text\",\"text\":\"Payment initiated\"}],\n    \"isError\":false\n  }\n}\n```\n\nMRTR’s key benefit is that the original operation returns to the enforcement point before execution. The server can detect changed arguments rather than blindly executing a stored snapshot. Continuation state can also remain much smaller than a durable work object.\n\nBut MRTR supplies only the client/server pause-and-resume exchange. The approver system, policy, protected state, operation binding, audit trail, and delayed decision store are custom work. The protocol as writen doesn’t really support a good retry pacing option, and assumes the client will resume faithfully rather than re-plan.\n\nMRTR works best when the interaction is short and the original client remains engaged. For longer lived approval flows (ie, like another party – which may take days), MRTR is not an appropriate solution.\n\n## MCP Tasks: represent durable asynchronous work\n\nMCP [Tasks](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2663-tasks-extension.md) let a Task-capable operation return a durable work record instead of an immediate result. The client can poll status, supply requested input, cancel, and retrieve the eventual result. The client and approver do not have to remain online together.\n\nThe main protocol flow is:\n\n**The client and server advertise the Tasks extension.** The server declares support through`server/discover`\n\n; the client includes its support on the tool call. That means the client can handle a Task, but the server still decides whether this particular call becomes one.\n\n```\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n{\n  \"jsonrpc\":\"2.0\",\n  \"id\":1,\n  \"method\":\"tools/call\",\n  \"params\":{\n    \"name\":\"initiate_payment\",\n    \"arguments\":{\"amount\":\"5000.00\",\"currency\":\"GBP\",\"recipient\":\"Example Ltd\"},\n    \"_meta\":{\n      \"io.modelcontextprotocol/clientCapabilities\":{\n        \"extensions\":{\"io.modelcontextprotocol/tasks\":{}}\n      }\n    }\n  }\n}\n```\n\n**The server durably creates a Task before returning its handle.**`pollIntervalMs`\n\ntells the client how often to check;`ttlMs`\n\nbounds the Task’s lifetime.\n\n```\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n{\n  \"jsonrpc\":\"2.0\",\n  \"id\":1,\n  \"result\":{\n    \"resultType\":\"task\",\n    \"taskId\":\"task-abc123\",\n    \"status\":\"working\",\n    \"createdAt\":\"2026-08-18T17:00:00Z\",\n    \"lastUpdatedAt\":\"2026-08-18T17:00:00Z\",\n    \"ttlMs\":3600000,\n    \"pollIntervalMs\":5000\n  }\n}\n```\n\n**The client polls with** Unlike MRTR, it does not resend the original`tasks/get`\n\n.`tools/call`\n\n. Each response is a snapshot of the server-held Task.\n\n```\n1\n2\n3\n4\n5\n6\n{\n  \"jsonrpc\":\"2.0\",\n  \"id\":2,\n  \"method\":\"tasks/get\",\n  \"params\":{\"taskId\":\"task-abc123\"}\n}\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n{\n  \"jsonrpc\":\"2.0\",\n  \"id\":2,\n  \"result\":{\n    \"resultType\":\"complete\",\n    \"taskId\":\"task-abc123\",\n    \"status\":\"working\",\n    \"createdAt\":\"2026-08-18T17:00:00Z\",\n    \"lastUpdatedAt\":\"2026-08-18T17:00:00Z\",\n    \"ttlMs\":3600000,\n    \"pollIntervalMs\":5000\n  }\n}\n```\n\n**If the Task needs client input, it reports** The outstanding request appears in a later`status: \"input_required\"`\n\n.`tasks/get`\n\nresult, and the client answers it with`tasks/update`\n\nkeyed by`taskId`\n\n. This is not MRTR: the client does not retry the original method. The update is only an acknowledgement path; an`accept`\n\nresponse is not, by itself, authoritative human approval.\n\n```\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n{\n  \"jsonrpc\":\"2.0\",\n  \"id\":3,\n  \"result\":{\n    \"resultType\":\"complete\",\n    \"taskId\":\"task-abc123\",\n    \"status\":\"input_required\",\n    \"createdAt\":\"2026-08-18T17:00:00Z\",\n    \"lastUpdatedAt\":\"2026-08-18T17:00:05Z\",\n    \"ttlMs\":3600000,\n    \"pollIntervalMs\":5000,\n    \"inputRequests\":{\n      \"approval\":{\n        \"method\":\"elicitation/create\",\n        \"params\":{\n          \"mode\":\"url\",\n          \"message\":\"Review this GBP 5,000 payment\",\n          \"url\":\"https://approve.example/requests/abc123\"\n        }\n      }\n    }\n  }\n}\n```\n\nThe server acknowledges\n\n`tasks/update`\n\n; the client then resumes polling because the Task update may be eventually consistent.\n\n```\n1\n{\"jsonrpc\":\"2.0\",\"id\":4,\"result\":{\"resultType\":\"complete\"}}\n1\n2\n3\n4\n5\n6\n7\n8\n9\n{\n  \"jsonrpc\":\"2.0\",\n  \"id\":4,\n  \"method\":\"tasks/update\",\n  \"params\":{\n    \"taskId\":\"task-abc123\",\n    \"inputResponses\":{\"approval\":{\"action\":\"accept\"}}\n  }\n}\n```\n\n**The client keeps polling until the Task reaches a terminal state.** After a separate authorization system has approved the operation and the server has executed it,`tasks/get`\n\nreturns the stored tool result inline.\n\n```\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n{\n  \"jsonrpc\":\"2.0\",\n  \"id\":5,\n  \"result\":{\n    \"resultType\":\"complete\",\n    \"taskId\":\"task-abc123\",\n    \"status\":\"completed\",\n    \"createdAt\":\"2026-08-18T17:00:00Z\",\n    \"lastUpdatedAt\":\"2026-08-18T17:01:00Z\",\n    \"ttlMs\":3600000,\n    \"pollIntervalMs\":5000,\n    \"result\":{\n      \"content\":[{\"type\":\"text\",\"text\":\"Payment initiated\"}],\n      \"isError\":false\n    }\n  }\n}\n```\n\nThe other terminal states are `failed`\n\nand `cancelled`\n\n. A client can request cooperative cancellation with `tasks/cancel`\n\n; the acknowledgement does not guarantee that the server stopped the work.\n\n```\n1\n{\"jsonrpc\":\"2.0\",\"id\":6,\"method\":\"tasks/cancel\",\"params\":{\"taskId\":\"task-abc123\"}}\n```\n\nFor HitL, a server can create a Task and hold it while a separate approval process runs. After authorization, a server-side executor dispatches the stored or reconstructed operation and records the result.\n\nTasks standardize only the MCP client-to-server work lifecycle. They do not define:\n\n- who the agent, requester, or approver is;\n- which actions require approval;\n- how the human is authenticated and authorized;\n- what the human must see;\n- how the decision binds to the operation;\n- how delayed execution is secured; or\n- what evidence satisfies an auditor.\n\nThose pieces require a decision service, policy model, approval API and UI, secure store, single-use state transition, dispatcher, drift checks, reconciliation, and audit system. MCP Tasks are a good primitive, but the surrounding authorization control plane is most of the solution.\n\n## Where does that leave us?\n\nWhen HitL protects consequential enterprise actions, the agent cannot decide whether the control applies. The enforcement point must require the decision, bind it to the exact operation, consume it safely, and preserve the evidence.\n\n### Path one: MCP plus a custom authorization control plane\n\nUse MRTR or Tasks for the MCP-facing lifecycle, then build the mandatory policy, identity, approval, binding, execution, and evidence systems around it.\n\nThis path fits existing MCP clients, supports a tailored user experience, and can be optimized for strict per-invocation approval. It also means the hardest security and compliance pieces are local implementation work, and interoperability ends at the MCP lifecycle boundary.\n\nMRTR and Tasks serve different interactions. MRTR favors short, client-driven pause and resume. Tasks favor durable, out-of-band work that can survive the initiating client. Neither replaces the authorization control plane.\n\n### Path two: AAuth\n\nUse AAuth’s agent identity, resource access modes, Person Server governance, requirements, and deferred responses as the broader architecture.\n\nThis path addresses agents calling arbitrary tools, APIs, MCP, etc. More of the identity, authorization, interaction, and governance model is standardized. The cost is adopting an emerging and much broader protocol, integrating it with existing identity and policy systems, and defining tighter profiles where one-shot transaction semantics matter.\n\nOrganizations optimizing for MCP compatibility and local control may reasonably build around the MCP spec. Organizations seeking a general, interoperable agent authorization architecture should look hard at AAuth.\n\n[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)by the author.", "url": "https://wpnews.pro/news/human-in-the-loop-authorization-patterns-for-agents", "canonical_source": "https://blog.christianposta.com/human-in-the-loop-authorization-patterns-for-agents/", "published_at": "2026-08-24 23:17:52+00:00", "updated_at": "2026-08-25 03:12:30.665342+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "ai-policy", "ai-ethics"], "entities": ["Solo.io", "CHEQ", "TAC", "AAuth", "MCP", "Sarbanes-Oxley Act"], "alternates": {"html": "https://wpnews.pro/news/human-in-the-loop-authorization-patterns-for-agents", "markdown": "https://wpnews.pro/news/human-in-the-loop-authorization-patterns-for-agents.md", "text": "https://wpnews.pro/news/human-in-the-loop-authorization-patterns-for-agents.txt", "jsonld": "https://wpnews.pro/news/human-in-the-loop-authorization-patterns-for-agents.jsonld"}}