hitl recipe

Human-in-the-loop approval before risky tool calls

Pause the graph before a sensitive tool fires, surface the proposed call to a human, then resume with their decision. Used for refunds, prod DB writes, transfers.

4 min read · Published May 5, 2026 · Languages: python, typescript, rust

The pattern

When an LLM is about to call a tool that’s hard to undo (refund money, drop a table, send an email), pause. Surface the proposed call to a human. Let them approve, modify, or reject. Resume.

HITL in one line: interrupt_before([“dangerous_tool”]) pauses the graph at the named tool; the run resumes when you reinvoke with the same thread_id.

Setup

agent = (Agent.builder("payments")
    .llm(OpenAI())
    .tools([read_account, transfer_funds])
    .checkpoint(PostgresSaver.from_env())
    .interrupt_before(["transfer_funds"])
    .build())

Run, inspect, approve

# Run pauses at the first transfer_funds call.
state = await agent.ainvoke(
    {"messages": [HumanMessage("Refund order #42 for $250.")]},
    config={"configurable": {"thread_id": "case-42"}},
)

print(state.next)  # ('transfer_funds',)
proposed = state.checkpoint.next_tool_call
print(proposed.args)  # {"to": "...", "amount": 250}

# Human approves → resume.
await agent.ainvoke(None, config={"configurable": {"thread_id": "case-42"}})

# Or the human modifies the args:
await agent.update_state(
    {"configurable": {"thread_id": "case-42"}},
    {"messages": [HumanMessage("Approved, but amount should be 245.")]},
)
await agent.ainvoke(None, config={"configurable": {"thread_id": "case-42"}})

Conditional interrupts

Only pause when the args meet a threshold:

agent = (Agent.builder("payments")
    .interrupt_before_when(
        tool="transfer_funds",
        predicate=lambda args: args["amount"] >= 100,
    )
    .build())

After-tool interrupts

Pause after a tool fires (useful for showing the user what changed):

.interrupt_after(["create_user"])

Persistence

HITL requires a durable checkpointer — interrupts persist run state to the backend so a different process / day can resume. Memory backend works for tests; use SQLite / Postgres / Redis / S3 in production.

Ship your next agent in minutes, not weeks.

MIT licensed. Drop-in for LangGraph. Native SDKs in 5 languages. Battle-tested resilience primitives in the box.