Build a Safe LangChain Agent in 5 Minutes
Add permission control to your LangChain agent with just 3 lines of code.
Your LangChain agent can call any tool you give it. That's powerful—and dangerous.
In this tutorial, you'll add permission control to a LangChain agent in under 5 minutes. No architecture changes. No complex setup. Just a decorator.
The Problem
Here's a typical LangChain agent:
from langchain.tools import tool
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_openai import ChatOpenAI
@tool
def get_customer_data(customer_id: str) -> dict:
"""Fetch customer data from database."""
return {"id": customer_id, "email": "alice@example.com", "balance": 5000}
@tool
def issue_refund(customer_id: str, amount: float) -> dict:
"""Process a refund for a customer."""
# This hits your payment API!
return {"status": "refunded", "amount": amount}
@tool
def delete_customer(customer_id: str) -> dict:
"""Delete a customer from the database."""
# This is destructive!
return {"status": "deleted"}
llm = ChatOpenAI(model="gpt-4")
tools = [get_customer_data, issue_refund, delete_customer]
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools)
# The agent can call ANY of these tools
executor.invoke({"input": "Delete customer 123"}) # 😱
The agent has access to everything. If it hallucinates or gets prompt-injected, it could:
- Issue unauthorized refunds
- Delete customer data
- Access sensitive information
The Solution: AgentSudo
Install AgentSudo:
pip install agentsudo
Now add permission control with 3 lines of code:
from langchain.tools import tool
from agentsudo import Agent, sudo
# 1. Create an agent identity with specific permissions
support_bot = Agent(
name="SupportBot",
scopes=["read:customers", "write:refunds"] # Can read and refund, but NOT delete
)
# 2. Protect your tools with @sudo
@tool
@sudo(scope="read:customers")
def get_customer_data(customer_id: str) -> dict:
"""Fetch customer data from database."""
return {"id": customer_id, "email": "alice@example.com", "balance": 5000}
@tool
@sudo(scope="write:refunds")
def issue_refund(customer_id: str, amount: float) -> dict:
"""Process a refund for a customer."""
return {"status": "refunded", "amount": amount}
@tool
@sudo(scope="delete:customers")
def delete_customer(customer_id: str) -> dict:
"""Delete a customer from the database."""
return {"status": "deleted"}
# 3. Run your agent in a session
with support_bot.start_session():
executor.invoke({"input": "Get customer 123"}) # ✅ Allowed
executor.invoke({"input": "Refund $50 to customer 123"}) # ✅ Allowed
executor.invoke({"input": "Delete customer 123"}) # ❌ PermissionDeniedError
That's it. The agent can only use tools it has permission for.
What Just Happened?
- Agent Identity: We created a
SupportBotwith specific scopes (read:customers,write:refunds) - Protected Tools: Each tool requires a scope via
@sudo(scope="...") - Session Context: Inside
start_session(), AgentSudo checks every tool call
When the agent tries to call delete_customer, AgentSudo sees that SupportBot doesn't have delete:customers scope and raises PermissionDeniedError.
Different Agents, Different Permissions
Create multiple agents with different access levels:
# Support can read and refund
support_bot = Agent(
name="SupportBot",
scopes=["read:customers", "write:refunds"]
)
# Analytics can only read
analytics_bot = Agent(
name="AnalyticsBot",
scopes=["read:*"] # Wildcard: read anything
)
# Admin can do everything
admin_bot = Agent(
name="AdminBot",
scopes=["*"] # Full access
)
Audit Mode: Log Without Blocking
Rolling out to production? Use audit mode to log violations without breaking anything:
@tool
@sudo(scope="delete:customers", on_deny="log")
def delete_customer(customer_id: str) -> dict:
"""Delete a customer from the database."""
return {"status": "deleted"}
# Now unauthorized calls are LOGGED but still execute
# Check your logs to see what would have been blocked
Human-in-the-Loop: Require Approval
For high-risk actions, require human approval:
def slack_approval(agent, scope, context):
"""Ask a human via Slack."""
response = ask_slack(f"Approve {agent.name} for {scope}?")
return response == "yes"
@tool
@sudo(scope="delete:customers", on_deny=slack_approval)
def delete_customer(customer_id: str) -> dict:
"""Delete a customer from the database."""
return {"status": "deleted"}
Full Example
Here's a complete working example:
from langchain.tools import tool
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from agentsudo import Agent, sudo, PermissionDeniedError
# Define protected tools
@tool
@sudo(scope="read:orders")
def get_order(order_id: str) -> dict:
"""Get order details."""
return {"id": order_id, "status": "shipped", "total": 99.99}
@tool
@sudo(scope="write:refunds")
def process_refund(order_id: str, amount: float) -> dict:
"""Process a refund."""
return {"status": "refunded", "order_id": order_id, "amount": amount}
# Create agent identity
support_bot = Agent(
name="SupportBot",
scopes=["read:orders", "write:refunds"],
session_ttl=3600 # Session expires in 1 hour
)
# Set up LangChain
llm = ChatOpenAI(model="gpt-4")
tools = [get_order, process_refund]
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful customer support agent."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools)
# Run with permission control
with support_bot.start_session():
try:
result = executor.invoke({
"input": "Check order ORD-123 and refund $25 if needed"
})
print(result)
except PermissionDeniedError as e:
print(f"Blocked: {e}")
Next Steps
AgentSudo is the permission layer for AI agents. Stop letting your agents run wild.
pip install agentsudo