redphonr vs LangGraph interrupt(): build or buy human-in-the-loop?

LangGraph's interrupt() pauses a graph. It does not notify anyone, escalate or log who answered. How redphonr fills that gap, with code for both together.

Written by
Published
Reading time
2 min

LangGraph has a clean human-in-the-loop primitive: interrupt(). Many teams assume that solves the problem. It solves half of it. This post explains which half, and when to add redphonr.

What interrupt() does

from langgraph.types import interrupt, Command

def approve_refund(state):
    decision = interrupt({"question": f"Refund ${state['amount']} to {state['order_id']}?"})
    return {"approved": decision == "approve"}

graph = builder.compile(checkpointer=checkpointer)
graph.invoke(inputs, config={"configurable": {"thread_id": "order-88213"}})
# ...later, once you have the answer:
graph.invoke(Command(resume="approve"), config={"configurable": {"thread_id": "order-88213"}})

The graph stops, its state is saved by the checkpointer, and it resumes with whatever you pass to Command(resume=...). It is durable, free and well designed.

What it does not do

interrupt() hands a payload back to the code that ran the graph. Everything after that is yours:

  • Delivering the question to a person: Slack, SMS, email, a call.
  • Waiting for them across minutes or hours, outside your request cycle.
  • Escalating when they do not answer: another channel, then the backup person.
  • Collecting the answer and calling Command(resume=...) on the right thread.
  • Timeouts with a safe default.
  • An audit trail of who decided, when and how.

If the user is sitting in your app’s chat UI, this is easy: show a button. If the agent runs in the background, it is a small notification system you now have to build and run.

Comparison

LangGraph interrupt()redphonr
Pauses and saves agent stateYesThe agent waits on a tool call
Delivers the question to a personNoSlack, SMS, WhatsApp, Telegram, Discord, email, voice
Escalation and on-callNoYes
Timeout with deny defaultYou build itBuilt in
Audit logYou build itBuilt in
Works outside LangGraphNoAny MCP agent or HTTP client
CostFree, open sourceFree to start

Option 1: give the agent redphonr’s tools

Load the redphonr MCP server into a LangChain agent (which runs on LangGraph) with the LangChain MCP adapters, and the model can call request_approval itself:

from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent

client = MultiServerMCPClient({
    "redphonr": {
        "transport": "streamable_http",
        "url": "https://mcp.redphonr.com/mcp",
        "headers": {"Authorization": f"Bearer {REDPHONR_API_KEY}"},
    }
})
tools = await client.get_tools()
agent = create_agent(model, tools=tools + your_tools, system_prompt=SYSTEM_PROMPT)

This is the fastest route. The tool call blocks until a person answers or the request times out.

Option 2: keep interrupt(), use redphonr for delivery

For very long waits, you may not want a process blocked on a tool call. Keep interrupt() for state, create a redphonr request when the graph pauses, and resume the thread when the answer arrives:

state = graph.invoke(inputs, config)
if "__interrupt__" in state:
    question = state["__interrupt__"][0].value["question"]
    req = create_redphonr_request(question, thread_id=config["configurable"]["thread_id"])

# In your webhook or poller, when the request is resolved:
graph.invoke(Command(resume=decision), config={"configurable": {"thread_id": thread_id}})

Which should you use?

  • interrupt() alone when the person is in your UI when the graph pauses.
  • redphonr when the agent runs unattended and the person could be anywhere, or when you need escalation and an audit trail.
  • Both for long-running graphs: LangGraph keeps the state, redphonr gets the answer.

See also the human-in-the-loop guide.

Frequently asked questions

How does human-in-the-loop work in LangGraph?

You call interrupt() inside a node with a payload. LangGraph saves the graph state with a checkpointer and stops. Later you resume the same thread with Command(resume=value), and that value becomes the return value of interrupt().

Does LangGraph interrupt() send a notification?

No. interrupt() pauses the graph and surfaces a payload to whatever code invoked it. Delivering the question to a person, waiting for them, escalating and resuming the graph is up to you or a service like redphonr.

Can I use redphonr with LangGraph?

Yes, in two ways. Load redphonr's MCP tools into a LangGraph agent so it can call request_approval directly, or keep interrupt() for state and use redphonr to deliver the question and resume the thread when the person answers.