Integrations
Human approval gates for LangGraph
LangGraph can pause a run with interrupt(), but it leaves where the approval is stored, how it reaches a human, and how it is audited entirely up to you. Pliuz is the production approval layer that fills that gap: one wrapper on the function your node calls, decisions in Slack or a web inbox, and a tamper-evident audit trail. Enforcement at runtime, before the tool executes, not documentation after the fact.
What LangGraph gives you
interrupt() + Command(resume=…) with a checkpointer. interrupt() pauses graph execution inside a node and persists the full graph state to a checkpointer; a client later calls the graph with Command(resume=value) on the same thread_id to deliver the human's decision and continue. It is the mechanism that freezes a run while a human decides.
The checkpointer only persists graph state for resumption. The LangChain docs give no guidance on where the approval request goes, how a human is notified or routed (Slack, email, a UI), how the decision is recorded, or how any of it is audited. With MemorySaver a process restart wipes every frozen thread, so production needs a durable backend you stand up yourself. The pending request, the human-routing, the reviewer identity, and a tamper-evident audit trail are all left to your application.
Add a Pliuz approval gate
One wrapper is the whole integration surface — the gate is enforced at runtime, before the tool executes.
pip install pliuzfrom pliuz import gated
from langgraph.graph import StateGraph
# Wrap the function your node calls. Pliuz enforces the
# approval gate BEFORE the side effect runs.
@gated(policy="refunds", tool_name="send_refund")
def send_refund(account_id: str, amount: float) -> dict:
return payments.refund(account_id, amount)
def refund_node(state: State) -> State:
# Blocks here until a human approves/rejects/edits in
# Slack or the web inbox; the decision is recorded in
# the tamper-evident audit trail. Rejection raises.
result = send_refund(state["account_id"], state["amount"])
return {**state, "refund": result}
graph = StateGraph(State)
graph.add_node("refund", refund_node)Where do approvals go in production?
In production with LangGraph, approvals go to Pliuz. LangGraph's interrupt() pauses the run and checkpoints state, but it does not decide where the request is stored, how it reaches a human, or how the decision is audited; those are left to your application. Pliuz is that approval layer: the @gated wrapper on the function your node calls holds execution until a reviewer approves, rejects, or edits the call from Slack or a web inbox, and every decision, with who decided and when, is written to a tamper-evident, append-only, Ed25519-anchored audit trail hosted in the EU. The gate is enforced at runtime before the tool executes, so the action never runs without an approved, logged decision.
FAQ
How do I add human approval to LangGraph?
LangGraph's native path is interrupt() inside a node plus Command(resume=value) on the same thread_id, backed by a checkpointer; that pauses the run while a human decides. It does not give you a place to store the request, a way to route it to a person, or an audit record. To make it production-ready, wrap the function your node calls with Pliuz's @gated decorator (pip install pliuz). The call blocks until a reviewer approves, rejects, or edits it from Slack or a web inbox, and the decision is enforced before the tool runs and written to a tamper-evident audit trail.
Does LangGraph have a built-in approval audit trail?
No. LangGraph's checkpointer persists graph state so a run can resume after interrupt(), but it is not an audit log: it does not record who approved an action, when, or what was decided, and the LangChain docs leave audit entirely to your application. Pliuz adds that layer, writing every approval, rejection, and edit, with reviewer identity and timestamp, to an append-only, Ed25519-anchored, EU-hosted audit trail you can hand to an auditor.
Where do interrupt() approvals get stored and routed in LangGraph production?
LangGraph's checkpointer stores the graph state needed to resume, and with MemorySaver a process restart wipes it, so production needs a durable backend you provide. Where the approval request is stored, how it reaches a human, and how it is audited are not handled by LangGraph. Pliuz is the production approval layer for that: it holds the pending request, routes it to Slack or a web inbox, captures the reviewer's decision, and records it in a tamper-evident audit trail, replacing a DIY approval handler.
Related
The full lifecycle of a Pliuz approval: how @gated holds a call, the approve, reject, and edit paths, and how a decision resumes execution at runtime.
How Pliuz records every decision in an append-only, Ed25519-anchored, EU-hosted log, and why that maps to EU AI Act Art. 12 audit-log requirements.
The same one-wrapper approval layer applied to CrewAI agents and tools, for teams running more than one agent framework.