A user or AI agent sends a request to your LangChain application. The request may include a warrant (from headers, context, or session).
# User sends prompt to LangChain agent "Read the file /tmp/data.txt and tell me what it contains" # Or via API with warrant in header POST /agent/run X-Tenuo-Warrant: <base64-encoded-warrant> {"prompt": "Read /tmp/data.txt"}
The LangChain agent (powered by GPT, Claude, etc.) analyzes the request and decides which tools to invoke. It prepares tool calls with arguments. The agent doesn't know about Tenuo - it just calls tools normally.
# LangChain agent decides to call read_file tool tool_name: "read_file" arguments: { "file_path": "/tmp/data.txt" } # AgentExecutor.invoke() calls your tool function read_file(file_path="/tmp/data.txt")
Before your tool function executes, Tenuo's @guard decorator:
# Your tool function - protected by @guard from tenuo import guard, warrant_scope @guard(tool="read_file", extract_args=lambda file_path, **kwargs: {"file_path": file_path}) def read_file(file_path: str) -> str: # Tenuo checks authorization BEFORE this code runs # If authorized, function executes normally with open(file_path, 'r') as f: return f.read()
How it works: The @guard
decorator intercepts the function call,
retrieves the warrant from ContextVar (set by
your callback or middleware),
and checks if the warrant authorizes this specific tool call with these arguments.
Warrant authorizes this action. Tool function executes normally. LangChain receives result and continues processing.
# Warrant constraint: file_path = Pattern("/tmp/*") # Tool argument: file_path = "/tmp/data.txt" # โ Matches pattern โ Authorization granted # โ Tool function executes # โ Returns file contents to LangChain
Warrant doesn't authorize this action. Tenuo raises AuthorizationError.
Tool function never executes. LangChain receives error.
# Warrant constraint: file_path = Pattern("/tmp/*") # Tool argument: file_path = "/etc/passwd" # โ Doesn't match pattern โ Authorization denied # โ AuthorizationError raised # โ Tool function never executes # โ LangChain receives error
If authorized, your tool function executes normally. The @guard
decorator is transparent - your code doesn't need to know about Tenuo. It's just a regular
Python function.
# Your tool code (runs only if authorized) # No Tenuo-specific code needed here! try: with open(file_path, 'r') as f: return f.read() except FileNotFoundError: return f"Error: File not found: {file_path}" except Exception as e: return f"Error: {str(e)}"
LangChain receives either the tool result (if authorized) or an error (if blocked). The agent can handle the error and explain to the user why the action wasn't allowed.
@guard. Tenuo handles authorization automatically
- no manual checks needed.
@guard functions
automatically use it.
| Layer | Without Tenuo | With Tenuo |
|---|---|---|
| User/Agent | Makes request | Makes request (with warrant) |
| LangChain Agent | Processes request, calls tools | Processes request, calls tools |
| Authorization | Manual checks in code or external API calls |
Automatic via @guard 100% offline |
| Tool Functions | Execute directly | Execute only if authorized |
| Response | Result or generic error | Result or authorization error |
Adding Tenuo to your LangChain application is simple. Here's a complete example:
from tenuo import guard, Warrant, Pattern, SigningKey # Decorate your tool functions with @guard @guard(tool="read_file", extract_args=lambda file_path, **kwargs: {"file_path": file_path}) def read_file(file_path: str) -> str: """Read a file. Protected by Tenuo.""" with open(file_path, 'r') as f: return f.read()
from langchain.tools import Tool from langchain.agents import AgentExecutor, create_openai_tools_agent from langchain_openai import ChatOpenAI # Create LangChain tools from your protected functions tools = [ Tool( name="read_file", func=read_file, description="Read a file from the filesystem" ) ] # Create agent llm = ChatOpenAI(model="gpt-3.5-turbo") agent = create_openai_tools_agent(llm, tools) agent_executor = AgentExecutor(agent=agent, tools=tools)
from tenuo import warrant_scope # Create warrant (in production, from control plane) keypair = SigningKey.generate() warrant = (Warrant.builder() .capability("read_file", {"file_path": Pattern("/tmp/*")}) .holder(keypair.public_key) .ttl(3600) .issue(keypair)) # Set warrant AND keypair in context before running agent with warrant_scope(warrant), key_scope(keypair): # All @guard functions will use this warrant response = agent_executor.invoke({ "input": "Read the file /tmp/data.txt" }) print(response["output"])
โ That's it! All tool calls are now automatically protected. No changes to your tool code needed.
The @guard decorator handles authorization
automatically.
If the agent tries to access unauthorized files, Tenuo blocks it instantly.