← All articles

A Human-in-the-Loop SDK for LangGraph, CrewAI and the OpenAI Agents SDK in Python

TL;DR

LangGraph pauses with an interrupt and resumes from a checkpoint. CrewAI lets a task ask for human input. The OpenAI Agents SDK has no pause primitive of its own, so the tool has to block. DoubleOh sits under all three with three calls: skills_for to ask what the fleet already knows, request_fix to get a person on the live screen, report_skill to keep the library honest. The person acts once; the framework resumes; the fix is reused.

Every Python agent framework has some way to stop and wait for a person, and none of them remembers what the person did. LangGraph interrupts and resumes from a checkpoint, CrewAI marks a task as needing human input, the OpenAI Agents SDK relies on your tool blocking until somebody answers. The missing piece is the same in all three: a person acting on the agent's live browser or desktop, and that action becoming a procedure the next run follows without anybody being paged. That is what the DoubleOh SDK adds, with three calls.

The three calls

pip install doubleoh

from doubleoh import DoubleOh
oo = DoubleOh()                                   # reads DOUBLEOH_API_KEY

skills = oo.skills_for(task, url=url)             # what the fleet already knows here
fix    = oo.request_fix(url=url, task=task)       # a known wall, or a person on the live screen
oo.report_skill(skill_name, worked=True)          # a skill that stops working retires itself

LangGraph

Put the wall check in the node that drives the browser. When the page is a wall, request the fix and interrupt the graph with the fix link in the payload; resume when the person is done. The checkpoint holds the rest of the state, so the run continues where it stopped.

from langgraph.types import interrupt

def browse(state):
    if hint := oo.skills_for(state["task"], url=state["url"]):
        return {"procedure": hint[0].instructions}
    outcome = drive(state)
    if outcome.blocked:
        fix = oo.request_fix(url=state["url"], task=state["task"])
        interrupt({"fix_url": fix.fix_url, "why": outcome.reason})   # a person acts, then resumes
        return drive(state)                                          # same browser, same session
    return outcome.result

CrewAI

CrewAI's own human input asks a person to type an answer into the console. For walls that need action rather than text, give the crew a tool that requests a fix and waits; the task completes when the person has acted on the live session.

from crewai.tools import tool

@tool("ask_a_person_to_clear_the_wall")
def clear_wall(url: str, task: str) -> str:
    """When the browser is blocked by a login, dialog or approval, a person clears it on the live session."""
    if hint := oo.skills_for(task, url=url):
        return f"Already known. Follow:\n{hint[0].instructions}"
    fix = oo.request_fix(url=url, task=task)
    wait_for(fix)
    return "Cleared by a person; continue on the same session."

OpenAI Agents SDK

There is no pause primitive; a function tool simply does not return until the person is done. That is fine for a fix that takes a minute, and the tool's docstring is what teaches the model when to call it rather than retrying.

from agents import function_tool

@function_tool
def request_human_fix(url: str, task: str) -> str:
    """Call this once when a page is blocked by a login, a code, a dialog or an approval. Do not retry the action first."""
    fix = oo.request_fix(url=url, task=task)
    wait_for(fix)
    return "A person cleared it on the live session. Continue."

What is framework-independent

  • Ask before acting. The first call returns the procedure when the wall is known, and the person is never involved.
  • Ask once. A second request for the same wall within the window is a deflection, not a page.
  • Report outcomes. The library is only as good as the truth agents tell it.
  • MCP, if you prefer no code at all: npx doubleoh-mcp exposes the same three calls as tools to any MCP-capable agent.

Questions people ask

Does LangGraph have human in the loop built in?

Yes, interrupt and resume from a checkpoint. What it does not have is a person acting on the agent's live browser, or memory of what the person did. That is the part the SDK adds.

How do I add human input to a CrewAI task?

CrewAI can ask a person for text. For a wall that needs action, give the crew a tool that requests a fix and waits for the person to act on the live session.

Does the OpenAI Agents SDK support pausing for a human?

Not as a primitive. A function tool that blocks until the person is done is the usual pattern, and the docstring should tell the model to call it once rather than retry.

Is there an SDK for TypeScript too?

Yes, @doubleoh/sdk on npm, with the same three calls, and adapters for LangChain, LangGraph, Mastra, Browser Use and others.

DoubleOh is the authority layer for AI agents. When one gets stuck, a person fixes it once in a live browser, and the fix becomes a skill the whole fleet follows from then on.

Start free