Agentic systems / Pattern 09 / Design the system

Design human approval as a durable authorization decision

Make the approved action precise enough that a resumed agent cannot quietly change what the reviewer accepted.

By Sandeep Belgavi · 23 October 2026 · Architecture and design patterns

Approve the action, not the conversation

An incident assistant recommends restarting worker-7 to reduce a queue backlog. An operator clicks approve, but before execution the assistant changes the target to worker-8 or expands the operation to every worker. A generic approved flag cannot distinguish those proposals. Human involvement is not an effective control unless the system binds the decision to the exact action that was presented.

Our proposed approval record includes a proposal fingerprint, workflow revision, assigned reviewer, expiry and state. The model may draft the proposal, but trusted application code determines the reviewer and validates the eventual decision. Approval applies to a bounded action in a known context. It is not a standing permission for the assistant to pursue the same broad goal by any means it later chooses.

Architecture and request sequence

Component diagram for Design human approval as a durable authorization decision
Original reference architecture by Sandeep Belgavi. Arrows show the responsibilities and transitions described below.
Read the architecture as text
  • Proposal adapter: Suggest a bounded operation
  • Approval record: Digest, revision and expiry
  • Reviewer interface: Authenticated human decision
  • Decision validator: Identity and context checks
  • Dispatch marker: Single local consumption
  • Proposal adapter → Approval record: persist proposal
  • Approval record → Reviewer interface: present exact action
  • Reviewer interface → Decision validator: submit decision
  • Decision validator → Approval record: approved or rejected
  • Approval record → Dispatch marker: consume if current
Request sequence for Design human approval as a durable authorization decision
Illustrative sequence for this design; failure and recovery variants are explained in the article.
Read the sequence as text
  1. Agent → Store: Create pending proposal
  2. Reviewer → Store: Read exact action
  3. Reviewer → Store: Submit bound decision
  4. Dispatcher → Store: Recheck and consume
  5. Store → Dispatcher: Return local marker

Separate waiting from authorization

A workflow runtime can pause execution and later resume it with external input. LangGraph documents persisted interrupts and resumption using the same thread identifier. That mechanism is useful plumbing, but the application still needs to determine who supplied the response, what they approved and whether it remains valid. A resume payload containing true is not itself authenticated consent.

The reference does not use LangGraph. It implements an explicit SQLite approval state machine so these checks can be examined independently of a framework. Pending, approved, rejected and consumed have different meanings. A rejected request cannot be dispatched; an approved request can expire before dispatch; a consumed approval cannot authorize a second local dispatch. Keeping these states distinct makes both UI wording and recovery behavior more honest.

Bind the proposal to its context

The action is serialized with sorted keys and compact separators, then hashed. Equivalent key ordering yields the same fingerprint, while a changed target yields another fingerprint. The workflow revision is checked separately because identical action arguments can have different implications under changed code or policy. The reviewer must see a readable action summary, target and consequence; a cryptographic digest is a binding mechanism, not a usable explanation.

Decide which contextual changes invalidate consent. A fresh incident severity, changed deployment version or expired maintenance window may require a new review even when the action text is unchanged. The lab uses one revision string and an expiry timestamp to keep this boundary visible. A production design should derive those values from trusted state rather than accepting model-generated claims that the proposal remains current.

The dispatch boundary needs its own receipt

Consumption atomically changes approved to consumed only when the fingerprint, revision and expiry still match. The returned object is explicitly a local dispatch marker. It does not restart a worker, call a cloud API or prove that a remote action happened. This distinction prevents a successful authorization check from being mislabeled as successful remediation.

For a remote effect, combine approval consumption with a durable dispatch record, then send a stable operation identifier through an idempotent adapter. A crash after marking approval consumed but before sending needs a recoverable pending dispatch, not a second approval bypass. A crash after sending needs reconciliation with the destination. Our module intentionally stops before that distributed boundary; integrating the transactional outbox pattern is a separate engineering step.

Capacity and reviewer experience

Assume a team can review twenty proposals per hour and the assistant generates fifty. A durable queue prevents loss but does not create reviewer capacity. Prioritize by incident risk, combine redundant proposals and suppress requests that lack sufficient evidence. Track age and expiry so an operator does not approve a stale action simply because it finally reached the top of the queue.

Present enough information for a decision: incident identity, exact target, proposed operation, evidence references, expected impact, expiry and available alternatives. Avoid approval fatigue by requiring review where it changes risk, while allowing already authorized read-only investigation to proceed. The escalation policy should be explicit. Automatically changing a pending request to approved after a timeout defeats the purpose of the boundary; expiry should lead to a defined nonexecution path.

Acceptance before live tools

The original example requires matching reviewer identity, action digest, revision and time, and makes local consumption single-use. Its tests cover changed proposals, expired approval, rejection, duplicate decisions and restart persistence. The reviewer identity in the fixture is supplied by trusted test code; the module does not authenticate a browser user or verify a signed token.

Before adding live tools, test the UI-to-service identity path and a replayed approval response from another session. Evaluate whether operators can actually understand the proposed consequences through the evaluation process, not just whether the database accepts a boolean. A safe pause is only the first half of this pattern. The resume path must prove that the same bounded action is still authorized at the moment of dispatch.

Sources and implementation