← Blog
4 min readdevelopershuman-in-the-looplanggraph

Adding a human-in-the-loop step to a LangGraph agent — with the humans included

LangGraph's interrupt() pauses your graph and waits for input. Here's a pattern for making GetABrain the human on the other end of that wait.

Every HITL framework gives you the pause button. GetABrain gives you the humans. LangGraph's interrupt() pauses your graph and waits for input — but it doesn't hand you anyone to provide that input. This is one pattern for wiring GetABrain in as that supply, with the actual LangGraph primitives involved.

The gap in every HITL framework

LangGraph's interrupt(), AWS Bedrock Agents' confirmation step, Cloudflare Agents, and the Microsoft Agent Framework's human-in-the-loop pattern all solve the same half of the problem: they pause execution, persist state, and wait for input to resume. That's genuinely useful — it's the mechanism that keeps a long-running agent from acting on a risky step unsupervised.

None of them solve the other half. They don't hand you a person. Every tutorial for every one of these frameworks ends the same way: "...and then a human reviews it," with no detail on where that human comes from. In practice that means a Slack channel someone has to babysit, or a reviewer queue your own team staffs by hand.

How LangGraph's interrupt() actually works

Quick primer, since the wiring below depends on it: LangGraph's `interrupt()` (from `langgraph.types`) is called from inside a node. It raises internally and the runtime catches it, saves the graph's current state via a configured checkpointer, and returns control to whatever is driving the graph — your code, a server, a CLI.

Nothing resumes automatically. Something external has to call the graph again with `Command(resume=<value>)`; that value becomes `interrupt()`'s return value inside the node, and execution continues from there. This requires a checkpointer to be configured — without one, there's no state to resume from.

That resume call is the exact spot GetABrain fits. The graph is already correctly paused and checkpointed by LangGraph; the only missing piece is who supplies the value passed to `Command(resume=...)`.

Wiring GetABrain in as the human supply

The pattern has three parts: (1) the node that would otherwise ask your own team posts a query to GetABrain instead of blocking, (2) it calls `interrupt()` with enough context (the query id) for something outside the graph to know what it's waiting on, and (3) a small resume loop — a webhook handler or a poll job — watches for GetABrain to deliver a completed, quality-scored human answer and then calls back into the graph with `Command(resume=answer)`.

There's no dedicated GetABrain LangGraph package today — this is a couple of REST calls around LangGraph's own primitives, not a drop-in adapter. If that changes, this post will get updated to point at it instead of the raw pattern below.

Cost

Pay-per-response, from $0.05 per response with a 15% platform fee included — no subscription, no minimum batch. New accounts get $5.00 of free trial credit that activates on email verification, so an agent (or the developer wiring it up) can make a real HITL call before spending anything.

langgraph_hitl_node.py — illustrative pattern, not a published package
from langgraph.types import interrupt, Command
import requests

GETABRAIN_API = "https://www.getabrain.ai/api/v1/requestor/queries"

def human_review_node(state):
    # Post the decision to GetABrain instead of blocking on your own team.
    query = requests.post(
        GETABRAIN_API,
        headers={"X-API-Key": API_KEY, "X-API-Secret": API_SECRET},
        json={
            "type": "yes_no",
            "title": "Approve this agent action?",
            "content_data": {"question": state["proposed_action"]},
            "required_responses": 3,
            "bid_amount_cents": 25,
        },
    ).json()

    # Surface the wait through LangGraph's own interrupt so state stays
    # correctly checkpointed while GetABrain's humans answer.
    answer = interrupt({"waiting_on": "getabrain", "query_id": query["id"]})
    return {"approved": answer == "yes"}

# Elsewhere — a webhook handler or poll job watches the GetABrain query and,
# once it has a completed answer, resumes the exact paused graph:
#   graph.invoke(Command(resume=answer), config)

FAQ

Yes — the pattern above calls GetABrain from inside the node that raises `interrupt()`, and resumes with `Command(resume=answer)` once a human answer is back. The same shape works inside AWS Bedrock Agents' confirmation step, Cloudflare Agents, and the Microsoft Agent Framework's human-in-the-loop pattern — GetABrain is just the REST call that supplies the value each framework is waiting on.
Not today — there's no dedicated LangGraph package or adapter. What's shown here is a plain REST pattern built on GetABrain's public API (documented at /docs and /llms-full.txt) plus LangGraph's own `interrupt()`/`Command(resume=...)` primitives. If a first-party integration ships later, this post is the place we'd point you to it.
There is a REST API (https://www.getabrain.ai/api/v1) with an OpenAPI spec, an MCP server, and official npm and PyPI SDKs, so the `requests.post` call above can be swapped for an SDK method if you prefer. New accounts get $5.00 of free trial credit that activates on email verification.
That depends on your resume loop, not GetABrain — a webhook-driven resume can fire the moment the query completes; a poll job fires on whatever interval you set it to. On GetABrain's side, simple yes/no or rating queries typically get real human answers back within minutes, faster with a higher per-response bid.

Put a real human at your next interrupt.

Grab an API key and wire your first GetABrain call into a LangGraph interrupt, a Bedrock confirmation, or any other HITL pause. $5.00 of free trial credit, no card required.