Add Heed human-in-the-loop approval to any LangChain tool with a single decorator. Before your agent executes a consequential action, Heed intercepts it and waits for human approval — no manual polling code required.
| Requirement | Details |
|---|---|
| Heed API Key | Get one free at heed.run/signup — looks like hd_live_... or hd_test_... |
| Python | 3.10 or later |
| LangChain | langchain-core ≥ 0.3 and langchain-openai |
| Heed SDK | pip install heed[langchain] |
Use hd_test_... keys during development — they work identically but are flagged in the audit log.
pip install "heed[langchain]" langchain langchain-openai
export HEED_API_KEY="hd_live_your_key_here"
export OPENAI_API_KEY="sk-your-openai-key"
send_emailcurl -X POST https://heed.run/api/v1/hitl/policies \
-H "Content-Type: application/json" \
-H "X-API-Key: $HEED_API_KEY" \
-d '{
"name": "gate-emails",
"description": "Require approval before sending emails",
"rules": [
{
"action": "send_email",
"requires_approval": true
}
],
"is_active": true
}'
Copy the complete example below into a file and run it.
This agent can send emails, but every send_email action is gated by Heed. The @heed_gate decorator wraps your LangChain tool and blocks execution until a human approves or rejects. No manual polling code needed.
"""
LangChain + Heed: Gate send_email behind human approval.
The @heed_gate decorator wraps any LangChain @tool. When the agent
calls the tool, Heed checks your policy. If approval is required,
execution blocks until a human decides. If auto-approved by policy,
the tool runs immediately.
Requires: pip install "heed[langchain]" langchain langchain-openai
"""
import os
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.tools import tool
from langchain_core.prompts import ChatPromptTemplate
from heed.langchain import heed_gate, inject_heed_config
# Step 1: Wrap your tool with @heed_gate
@heed_gate(
action="send_email",
mode="hitl_gate", # or "grace_period" for auto-approve after timeout
summary_template="Send email to {to}: {subject}",
context_fields=["to", "subject"],
)
@tool
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email to a recipient."""
# This only executes AFTER human approval
return f"Email sent to {to}: [{subject}] {body}"
# Step 2: Inject Heed credentials into your agent config
config = inject_heed_config({}, api_key=os.environ["HEED_API_KEY"])
# Step 3: Build your agent normally
llm = ChatOpenAI(model="gpt-4o", temperature=0)
prompt = ChatPromptTemplate.from_messages([
("system", "You are an email assistant. Use send_email to send emails."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_tool_calling_agent(llm, [send_email], prompt)
agent_executor = AgentExecutor(agent=agent, tools=[send_email], verbose=True)
# Step 4: Run with Heed config
result = agent_executor.invoke(
{"input": "Send an email to alice@example.com about the Q3 report"},
config=config,
)
print(result["output"])
No policy yet? Tools run without gating when no matching policy exists. Create a policy (step 3 above) to start gating specific actions.
@heed_gate above your @tool — that's the only code changeGatedTool submits a HITL request to HeedHeedError — the agent knows to stopThe decorator works with both decorator orders: @heed_gate @tool or @tool then heed_gate()(my_tool).
For LangGraph or agents where you can't modify tool definitions, use HeedCallbackHandler to intercept tool calls at the callback level:
from heed.langchain import HeedCallbackHandler
handler = HeedCallbackHandler(
api_key=os.environ["HEED_API_KEY"],
gated_tools={
"delete_record": {"action": "delete_record", "mode": "hitl_gate"},
"send_email": {
"action": "send_email",
"mode": "grace_period",
"grace_seconds": 30,
"summary_template": "Send email to {to}: {subject}",
},
},
)
# Ungated tools pass through automatically
result = graph.invoke(
{"messages": [HumanMessage("Delete record 42")]},
config={"callbacks": [handler]},
)
| Mode | Behavior | Use When |
|---|---|---|
hitl_gate | Block until human approves | High-risk actions (payments, deletions, PII access) |
grace_period | Auto-approve after N seconds unless human objects | Medium-risk actions with SLA requirements |
hotl_monitor | Execute immediately, log for review | Low-risk actions needing audit trail |
circuit_breaker | Auto-approve until error threshold, then gate | High-volume actions that need fail-safe |
hotl_monitor and circuit_breaker modes are coming soon. hitl_gate and grace_period are live now.
heed_gate decorator| Parameter | Type | Default | Description |
|---|---|---|---|
action | str | required | Action name for policy matching |
mode | str | None | None | Override mode: hitl_gate or grace_period |
grace_seconds | int | None | None | Auto-approve timeout (grace_period only) |
summary_template | str | None | None | Format string for approval summary, e.g. "Send to {to}" |
context_fields | list | None | None | Tool kwargs to include in Heed context |
agent_id | str | None | None | Agent identifier |
auto_skip | bool | True | If no client found, execute without gating |
poll_timeout | float | 300.0 | Max seconds to wait for decision |
poll_interval | float | 2.0 | Seconds between polls |
HeedCallbackHandler| Parameter | Type | Description |
|---|---|---|
api_key | str | Heed API key |
base_url | str | Heed API base URL (default: https://heed.run) |
gated_tools | dict | Tool name → gate config mapping |
auto_approve_policy | bool | Allow ungated tools to pass through |
inject_heed_configInjects Heed credentials into a RunnableConfig so gated tools can find them:
config = inject_heed_config(
{"configurable": {"thread_id": "user-1"}},
api_key="hd_live_abc123",
)
# config["configurable"]["heed_api_key"] == "hd_live_abc123"