Authorization Layer for AI Agents
Tenuo sits between LangChain's tool execution and your actual tool functions, providing cryptographically-enforced authorization with zero network calls.
1
User/Agent Makes Request
Prompt or API call to LangChain agent

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"}
โ†“
2
LangChain Agent Processes Request
LLM decides which tools to call

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")
โ†“
3
Tenuo Authorization Layer
@guard decorator checks warrant before execution
๐Ÿ”’ Tenuo Intercepts Tool Call

Before your tool function executes, Tenuo's @guard decorator:

  • Retrieves warrant from ContextVar (set by callback/middleware)
  • Extracts tool arguments for constraint checking
  • Verifies warrant authorizes this specific tool + arguments
  • Checks constraints (e.g., file_path matches Pattern("/tmp/*"))
  • All happens offline in ~27ฮผs
# 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.

โ†“
โœ“
Authorization Decision
Allowed or blocked based on warrant constraints
โœ“ Allowed

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
โœ— Blocked

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
โ†“
4
Tool Execution (if authorized)
Your actual tool function runs

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)}"
โ†“
5
Response to User/Agent
LangChain returns result or error

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.

๐Ÿ”’
Automatic Protection
Decorate your tool functions with @guard. Tenuo handles authorization automatically - no manual checks needed.
โšก
Zero Network Calls
Authorization happens 100% offline in ~27ฮผs. No API calls, no latency, no single point of failure.
๐Ÿ”„
ContextVar Integration
Set warrant once in context (via callback or middleware). All @guard functions automatically use it.
๐ŸŽฏ
Constraint-Based
Fine-grained control: restrict by file paths, replicas, clusters, or any argument value using patterns and ranges.
๐Ÿ”—
Delegation Provenance
Every authorization is cryptographically linked to a warrant chain showing who delegated the authority.
๐Ÿ›ก๏ธ
Monotonic Attenuation
Capabilities can only shrink when delegated. 15 replicas becomes 10, never 20.
Where Tenuo Sits in Your Stack
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
๐Ÿ’ก
Integration Pattern
How to add Tenuo to your LangChain application

Adding Tenuo to your LangChain application is simple. Here's a complete example:

Step 1: Decorate Your Tool Functions

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()

Step 2: Create LangChain Tools

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)

Step 3: Create Warrant and Set in Context

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.