Quick Start

What is Tenuo?

Tenuo is a warrant-based authorization library for AI agent workflows. A warrant is a signed token specifying which tools an agent can call, under what constraints, and for how long.

Agent: "restart staging-web"
        │
        ▼
Tenuo:  Does this warrant allow "restart" on "staging-web"?
        Is the delegation chain valid?
        │
        ▼
IAM:    Does this service account have permission?

Tenuo adds a delegation layer on top of your existing IAM. If an LLM is prompt-injected, it can request anything, but the warrant only allows what you scoped. The injection succeeds at the LLM level; authorization stops the action.

Core invariant: when a warrant is delegated, its capabilities can only narrow, never widen. Enforced cryptographically.

Install

uv pip install tenuo

With framework support (quotes required in zsh):

uv pip install "tenuo[openai]"      # OpenAI Agents SDK
uv pip install "tenuo[temporal]"    # Temporal workflows
uv pip install "tenuo[langchain]"   # LangChain / LangGraph
uv pip install "tenuo[crewai]"      # CrewAI
uv pip install "tenuo[google_adk]"  # Google ADK
uv pip install "tenuo[mcp]"         # MCP (Python ≥3.10)
uv pip install "tenuo[a2a]"         # A2A inter-agent delegation
uv pip install "tenuo[fastapi]"     # FastAPI
uv pip install "tenuo[autogen]"     # AutoGen AgentChat (Python ≥3.10)

Try It (Copy-Paste, Runs Immediately)

from tenuo import configure, mint_sync, Capability, Subpath, SigningKey, guard
from tenuo.exceptions import AuthorizationDenied

# 1. Configure once at startup
configure(issuer_key=SigningKey.generate(), dev_mode=True, audit_log=False)

# 2. Protect tools with @guard
@guard(tool="read_file")
def read_file(path: str) -> str:
    return f"Contents of {path}"

# 3. Scope authority to tasks
with mint_sync(Capability("read_file", path=Subpath("/data"))):
    print(read_file("/data/reports/q3.pdf"))  # Allowed

    try:
        read_file("/etc/passwd")  # Blocked
    except AuthorizationDenied as e:
        print(f"Blocked: {e}")

What happened:

  • @guard(tool="read_file") marks the function as requiring authorization
  • mint_sync(...) creates a warrant scoped to /data/ (using Subpath for path traversal protection)
  • The second call fails because /etc/passwd is not under /data/

Choose Your Integration

What framework are you using?

Framework Integration Getting Started
OpenAI SDK from tenuo.openai import guard OpenAI Guide
Temporal from tenuo.temporal import TenuoTemporalPlugin Temporal Guide
LangChain from tenuo.langchain import auto_protect LangChain Guide
LangGraph from tenuo.langgraph import guard_node LangGraph Guide
CrewAI from tenuo.crewai import ... CrewAI Guide
Google ADK from tenuo.google_adk import TenuoGuard ADK Guide
MCP from tenuo.mcp import SecureMCPClient MCP Guide
FastAPI from tenuo.fastapi import SecureAPIRouter FastAPI Guide
AutoGen from tenuo.autogen import ... AutoGen Guide
A2A from tenuo.a2a import ... A2A Guide
Custom from tenuo import Warrant, SigningKey API Reference

Do you have agents communicating across processes? Add A2A alongside your runtime integration.

Quick Examples

OpenAI: wrap the client, tools are automatically protected:

from tenuo.openai import guard
client = guard(openai.OpenAI(), warrant=warrant, signing_key=key)

LangGraph: scope authority per graph node:

from tenuo.langgraph import guard_node, TenuoToolNode
graph.add_node("agent", guard_node(my_agent, key_id="worker"))
graph.add_node("tools", TenuoToolNode([search, write_file]))

MCP: verify tool calls between client and server:

from tenuo.mcp import SecureMCPClient
client = SecureMCPClient(server_url, warrant=warrant, signing_key=key)

Temporal: one plugin, zero workflow changes:

from temporalio.client import Client
from tenuo.temporal import TenuoTemporalPlugin, TenuoPluginConfig, EnvKeyResolver

# issuer_pubkey is the public key that mints your warrants —
# e.g. ``control_key.public_key`` from the snippets above.
plugin = TenuoTemporalPlugin(TenuoPluginConfig(
    key_resolver=EnvKeyResolver(), trusted_roots=[issuer_pubkey],
))
client = await Client.connect("localhost:7233", plugins=[plugin])

Each framework guide includes a full working example, production configuration, and troubleshooting.

Debugging Authorization Failures

When something is denied, use why_denied() for diagnostics:

result = warrant.why_denied("read_file", {"path": "/etc/passwd"})
if result.denied:
    print(f"Denied: {result.deny_code}")
    print(f"Field: {result.field}")
    print(f"Suggestion: {result.suggestion}")

Or inspect a warrant interactively in the Explorer Playground. Warrants contain only signed claims, not secrets, so they’re safe to share.

Next Steps