LangChain logo
PythonJavaScript

LangChain Integration

Add human approval as a tool in any LangChain agent or chain.

LangChain is the most widely used framework for building AI agents in both Python and JavaScript. This guide uses the current recommended patterns — LangGraph's create_react_agent for Python and the equivalent in JavaScript. If you're on an older codebase using AgentExecutor, there's a compatibility section at the bottom.

How it works

You define a request_human_approval tool and add it to your agent's tool list. When the agent decides an action needs human sign-off, it calls the tool automatically — the tool sends the request to The Handover, polls for a response, and returns the decision back to the agent so it can continue.

Python

Installation

pip install langchain langchain-openai langgraph requests

Step 1: Define the approval tool

The @tool decorator is the simplest way to create a LangChain tool. The docstring is what the model reads to decide when to call it — write it clearly.

from langchain_core.tools import tool
import requests, time

HANDOVER_API_KEY = "ho_your_key_here"  # from your dashboard
APPROVER_EMAIL   = "approver@yourcompany.com"

@tool
def request_human_approval(action: str, context: str, urgency: str = "medium") -> str:
    """Request human approval before taking a sensitive or irreversible action.

    Use this tool for: financial transactions, sending emails to customers,
    deleting or modifying records, deployments, or any action the user
    would want to review before it happens.

    Args:
        action: A clear description of what you want to do (be specific)
        context: Why you want to do it and any relevant background
        urgency: How time-sensitive this is — low | medium | high | critical
    """
    headers = {"Authorization": f"Bearer {HANDOVER_API_KEY}"}

    # Create the decision — approver gets an email with Approve/Deny buttons
    resp = requests.post(
        "https://thehandover.xyz/decisions",
        headers=headers,
        json={
            "action": action,
            "context": context,
            "urgency": urgency,
            "approver": APPROVER_EMAIL,
        },
    )
    resp.raise_for_status()
    decision_id = resp.json()["id"]

    # Poll every 5 seconds for up to 10 minutes
    for _ in range(120):
        time.sleep(5)
        result = requests.get(
            f"https://thehandover.xyz/decisions/{decision_id}",
            headers=headers,
        ).json()
        if result["status"] != "pending":
            notes = result.get("response_notes") or "none"
            return f"Decision: {result['status']}. Approver notes: {notes}"

    return "Approval timed out — no response within 10 minutes. Do not proceed."

Step 2: Create the agent

Add the tool to your agent. create_react_agent from LangGraph is the current recommended pattern — it replaces the older AgentExecutor.

from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent

llm = ChatOpenAI(model="gpt-4o")

tools = [request_human_approval]  # add your other tools here too

system_prompt = """You are a helpful assistant.

Before taking any of the following actions, you MUST call request_human_approval first:
- Any financial transaction or refund
- Sending emails or messages to customers
- Deleting or permanently modifying records
- Calling external APIs that trigger real-world actions
- Any deployment or infrastructure change

Be specific in the 'action' field — describe exactly what you intend to do."""

agent = create_react_agent(llm, tools, prompt=system_prompt)

Step 3: Run it

result = agent.invoke({
    "messages": [{"role": "user", "content": "Issue a €340 refund to customer #4821."}]
})

# Agent calls request_human_approval automatically
# Approver receives an email and clicks Approve or Deny
# Agent resumes and completes the task

final_answer = result["messages"][-1].content
print(final_answer)

Note on AgentExecutor: If you're using the older create_openai_functions_agent + AgentExecutor pattern, the tool definition above works identically — just add it to your tools list. The only difference is the invoke pattern: use executor.invoke({"input": "..."}) and read result["output"].

JavaScript / TypeScript

Installation

npm install @langchain/core @langchain/openai @langchain/langgraph zod

Step 1: Define the approval tool

import { tool } from "@langchain/core/tools";
import { z } from "zod";

const HANDOVER_API_KEY = "ho_your_key_here";
const APPROVER_EMAIL   = "approver@yourcompany.com";

const requestHumanApproval = tool(
  async ({ action, context, urgency }) => {
    const headers = {
      "Authorization": `Bearer ${HANDOVER_API_KEY}`,
      "Content-Type": "application/json",
    };

    // Create the decision
    const resp = await fetch("https://thehandover.xyz/decisions", {
      method: "POST",
      headers,
      body: JSON.stringify({ action, context, urgency, approver: APPROVER_EMAIL }),
    });
    const { id } = await resp.json();

    // Poll every 5 seconds for up to 10 minutes
    for (let i = 0; i < 120; i++) {
      await new Promise(r => setTimeout(r, 5000));
      const result = await (await fetch(`https://thehandover.xyz/decisions/${id}`, { headers })).json();
      if (result.status !== "pending") {
        return `Decision: ${result.status}. Notes: ${result.response_notes ?? "none"}`;
      }
    }
    return "Approval timed out. Do not proceed.";
  },
  {
    name: "request_human_approval",
    description: "Request human approval before a sensitive or irreversible action. " +
                 "Use for financial transactions, customer emails, record deletion, deployments.",
    schema: z.object({
      action:  z.string().describe("Exactly what you want to do"),
      context: z.string().describe("Why, and any relevant background"),
      urgency: z.enum(["low", "medium", "high", "critical"]).default("medium"),
    }),
  }
);

Step 2: Create the agent and run it

import { ChatOpenAI } from "@langchain/openai";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";

const llm = new ChatOpenAI({ model: "gpt-4o" });
const agent = createReactAgent({ llm, tools: [requestHumanApproval] });

const result = await agent.invoke({
  messages: [
    new SystemMessage("You are a helpful assistant. Before any financial transaction, " +
                       "customer communication, or data deletion, call request_human_approval."),
    new HumanMessage("Issue a €340 refund to customer #4821."),
  ],
});

console.log(result.messages.at(-1)?.content);

Writing a good system prompt

The system prompt is what tells the agent when to ask for approval. Vague instructions lead to the agent skipping the tool. Be explicit:

  • Name specific actions: "any refund", "any email to a customer", "any DELETE operation"
  • Set thresholds: "any transaction over €100" rather than "large transactions"
  • Use MUST: "you MUST call request_human_approval before..." is more reliable than "you should"
  • Cover edge cases: If the agent could find a loophole (e.g. issuing store credit instead of a refund), close it explicitly

Production tip: use a callback instead of polling

For long-running or async agents, polling inside the tool blocks the process for up to 10 minutes. Instead, pass a callback_url — The Handover will POST the result the instant the human decides:

json={
    "action": action,
    "context": context,
    "urgency": urgency,
    "approver": APPROVER_EMAIL,
    "callback_url": "https://your-server.com/webhook/handover",
}

Your webhook receives the full decision object (status, response_notes, original fields) the moment the approver clicks. See the webhooks docs for the payload format.

Ready to add human oversight?

Free to start. No credit card required. Five minutes to integrate.

Get Started Free