Agent workflows become operationally useful when they can pause safely, ask a person to review consequential actions, and resume from exactly the state that was approved. A prompt that says “ask before doing anything important” is not a control. The control needs to exist in the graph.
Treat approval as a state transition
Model approval as an explicit node boundary. The agent can prepare a proposed action, but it cannot call the write tool until the approval state is present. That distinction makes the workflow inspectable and testable.
A compact state might contain the request identifier, proposed operation, evidence used to form it, and the reviewer decision. Avoid storing secrets or unnecessary personal data in the state payload.
from typing import Literal, TypedDict
from langgraph.types import interrupt
class ReviewState(TypedDict):
request_id: str
proposal: dict
evidence_ids: list[str]
decision: Literal["pending", "approved", "rejected"]
def request_review(state: ReviewState):
decision = interrupt({
"request_id": state["request_id"],
"proposal": state["proposal"],
"evidence": state["evidence_ids"],
})
return {"decision": decision}
The interrupt returns control to the application. The application presents the proposal and evidence to an authorized reviewer, then resumes the graph with a decision. The graph decides what may happen next.
Make resumption safe
A durable checkpointer lets the workflow resume after a process restart. It does not, by itself, make side effects safe. Give every write operation an idempotency key derived from the workflow and action identifiers. If the caller retries after a timeout, the system can distinguish “the write succeeded but the response was lost” from “the write never happened.”
Keep the approval payload stable and reviewable. If the proposal changes after a reviewer approves it, invalidate that approval and create a new review event. Store who approved, when they approved, the version of the proposal, and the policy that applied.
Bound the execution path
Use explicit graph edges for approved, rejected, and expired reviews. Add a deadline, a maximum number of retries, and a terminal state for unresolved work. Observability should include the graph run ID, state transitions, tool outcomes, and approval history without placing sensitive payloads in logs.
Human review is a meaningful control only when the exact reviewed action is the action the runtime executes.
Test the boundary, not just the prompt
Test rejection, stale approvals, duplicate resumes, checkpoint recovery, and tool timeouts. A useful integration test asserts that the write tool is never invoked on the rejected path and that retrying the same approved action does not create a duplicate side effect.
These checks turn a human approval step from a reassuring UI element into a deterministic property of the workflow. The same design applies to clinical documentation, financial approvals, and any enterprise action where a person must retain authority over the final decision.