LangChain & LangGraph Integration

Add cryptographic human approval to LangChain agents and LangGraph workflows with a drop-in tool and decorator.

Key Concepts

The signedapproval-langchain package provides a LangChain-compatible tool and a @require_approval decorator that block agent execution until a real human authenticates and approves. Unlike Slack-based approval flows, every decision produces an unforgeable Ed25519 signature verifiable entirely offline.

It is a direct replacement for HumanLayer's @require_approval — swap the import and add your sa_live_ API key.

Setup Guide
1

Install the package

bash
pip install signedapproval-langchain
# With async support (LangGraph async nodes):
pip install "signedapproval-langchain[async]"
# With langchain-core:
pip install "signedapproval-langchain[all]"
2

Get your API key

Create an API key in the Dashboard → API Keys. Your key starts with sa_live_. Store it in an environment variable — never commit it to source control.

bash
export SIGNEDAPPROVAL_API_KEY="sa_live_..."
3

Use as a LangChain tool

Pass SignedApprovalTool to any LangChain agent. The tool blocks until the human decides.

python
import os
from signedapproval_langchain import SignedApprovalTool

tool = SignedApprovalTool(api_key=os.environ["SIGNEDAPPROVAL_API_KEY"])

# Standalone usage
result = tool.invoke("Transfer $500 to vendor account")

if result.decision == "approved":
    print(f"Approved! Sig: {result.signature[:32]}...")
    # proceed with the action
else:
    print(f"Not approved: {result.decision}")
    # abort
4

Use the @require_approval decorator

Wrap any function so it requires human approval before running. This is the closest equivalent to HumanLayer's decorator API.

python
from signedapproval_langchain import require_approval

@require_approval(
    api_key=os.environ["SIGNEDAPPROVAL_API_KEY"],
    action_template="Execute database migration: {args}",
)
def run_migration(migration_name: str) -> str:
    # Only called if a human approves
    return f"Migration {migration_name} complete"

# Raises PermissionError if rejected or expired
run_migration("add_users_table")

LangGraph Example

Use SignedApprovalTool as a LangGraph node to gate high-risk transitions:

python
from langgraph.graph import StateGraph, END
from signedapproval_langchain import SignedApprovalTool, ApprovalResult
from typing import TypedDict

tool = SignedApprovalTool(api_key=os.environ["SIGNEDAPPROVAL_API_KEY"])

class AgentState(TypedDict):
    action: str
    approval: ApprovalResult | None
    result: str | None

def request_approval_node(state: AgentState) -> AgentState:
    result = tool.invoke(state["action"])
    return {**state, "approval": result}

def execute_node(state: AgentState) -> AgentState:
    # Only reached if approved
    return {**state, "result": f"Executed: {state['action']}"}

def route_after_approval(state: AgentState) -> str:
    if state["approval"] and state["approval"].decision == "approved":
        return "execute"
    return END

graph = StateGraph(AgentState)
graph.add_node("request_approval", request_approval_node)
graph.add_node("execute", execute_node)
graph.set_entry_point("request_approval")
graph.add_conditional_edges("request_approval", route_after_approval, {"execute": "execute", END: END})
graph.add_edge("execute", END)

app = graph.compile()
result = app.invoke({"action": "Deploy main to production", "approval": None, "result": None})

Async Support

For async LangGraph nodes or async agent frameworks, install with the async extra and call _arun():

python
result = await tool._arun("Send invoice to client")

The async version uses aiohttp and does not block the event loop.

ApprovalResult reference

FieldTypeDescription
decisionstr"approved", "rejected", or "expired"
signaturestr | NoneBase64 Ed25519 signature of the canonical payload
public_key_idstr | NoneKey ID — use with /api/v1/approvals/:id/verify
canonical_payloadstr | NoneJSON payload that was signed
request_idstrUUID of the approval request
verifiedboolTrue when decision == approved
commentstr | NoneOptional note from the approver
Note
Verifying offline — the signature and canonical_payloadfields let you verify the approval without calling SignedApproval's servers. Fetch the public key once from GET /api/v1/approvals/{id}/verifyand verify the Ed25519 signature locally with PyNaCl or Python's cryptography library.
Tip
Set require_passkey=True or require_totp=True on SignedApprovalTool to enforce stronger authentication for the highest-risk actions.