AI agent governance SDK by Aten Security
Project description
Thoth SDK
Thoth SDK instruments your AI agents for governance, policy enforcement, and behavioral monitoring. Every tool call is evaluated against your organization's security policies in <100ms — blocking, stepping up for human approval, or silently observing based on your configured enforcement mode.
Package name: aten-thoth | PyPI: pip install aten-thoth
Table of Contents
- Installation
- Quick Start
- How It Works
- Integration Examples
- Enforcement Modes
- Policy Decisions
- Handling Violations
- Step-Up Authentication
- Session Inspection
- Configuration Reference
- Dashboard
Installation
pip install aten-thoth
With LangChain / LangGraph support:
pip install "aten-thoth[langchain]"
With OpenAI support:
pip install "aten-thoth[openai]"
With AutoGen support:
pip install "aten-thoth[autogen]"
Requirements: Python 3.12+
Quick Start
1. Get your API key from your Thoth dashboard at https://<tenant>.<apex-domain> under
Settings → API Keys.
2. Set environment variables:
export THOTH_API_KEY="thoth_live_..."
export THOTH_API_URL="https://enforce.<tenant>.<apex-domain>"
3. Instrument your agent — three lines of code:
import os
import thoth
# Instrument your agent — returns the same object, mutated in-place
agent = thoth.instrument(
agent,
agent_id="document-summarizer",
approved_scope=["read_file", "summarize"], # tools outside this list are blocked
tenant_id=os.environ["THOTH_TENANT_ID"],
api_url=os.environ["THOTH_API_URL"],
user_id="alice@example.com",
enforcement="progressive", # observe → step_up → block
# api_key set via THOTH_API_KEY env var
)
# Every tool call is now governed — no other changes required
result = agent.run("Summarize the attached document and send it to the team.")
That's it. No AWS credentials, no infrastructure setup — the SDK sends events and enforcement requests over HTTPS to your tenant API URL.
How It Works
Agent calls tool
│
▼
Thoth intercepts (wrap_tool)
│
├── Emits TOOL_CALL_PRE event → Aten API (async, non-blocking)
│
├── Calls enforcer /v1/enforce
│ │
│ ├── ALLOW → tool executes normally
│ ├── STEP_UP → waits for human approval (polls /v1/enforce/hold/{token})
│ └── BLOCK → raises ThothPolicyViolation
│
├── Tool executes (if allowed)
│
└── Emits TOOL_CALL_POST event → Aten API (async, non-blocking)
Events are batched and flushed to the Aten ingest API in a background daemon thread with at-most-10 per batch. If the enforcer is unreachable, Thoth fails open (ALLOW) and logs a warning — it never blocks production traffic due to an infrastructure fault.
Integration Examples
LangChain AgentExecutor
Thoth detects AgentExecutor automatically and wraps both tool.run and tool._run:
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_openai import ChatOpenAI
from langchain.tools import tool
import thoth
@tool
def web_search(query: str) -> str:
"""Search the web for current information."""
...
@tool
def read_file(path: str) -> str:
"""Read a file from the local filesystem."""
...
llm = ChatOpenAI(model="gpt-4o")
agent = create_openai_tools_agent(llm, tools=[web_search, read_file], prompt=...)
executor = AgentExecutor(agent=agent, tools=[web_search, read_file])
# One call instruments all tools
executor = thoth.instrument(
executor,
agent_id="research-agent",
approved_scope=["web_search", "read_file"],
tenant_id="acme-corp",
user_id="bob@acme.com",
enforcement="block",
)
# Now every tool invocation is policy-checked
result = executor.invoke({"input": "Find recent SEC filings for AAPL"})
LangGraph
For LangGraph agents, wrap tool callables directly using Tracer.wrap_tool. Because LangGraph
doesn't use AgentExecutor, the low-level API is needed here:
import os
import thoth
from thoth.models import ThothConfig, EnforcementMode
from thoth.session import SessionContext
from thoth.emitter import HttpEmitter
from thoth.enforcer_client import EnforcerClient
from thoth.step_up import StepUpClient
from thoth.tracer import Tracer
from langchain_anthropic import ChatAnthropic
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
@tool
def search_database(query: str) -> str:
"""Search the internal knowledge base."""
...
@tool
def send_slack_message(channel: str, message: str) -> str:
"""Post a message to Slack."""
...
api_key = os.environ["THOTH_API_KEY"]
config = ThothConfig(
agent_id="langgraph-analyst",
approved_scope=["search_database", "send_slack_message"],
tenant_id=os.environ["THOTH_TENANT_ID"],
user_id="alice@acme.com",
enforcement=EnforcementMode.BLOCK,
api_key=api_key,
)
session = SessionContext(config)
tracer = Tracer(
config=config,
session=session,
emitter=HttpEmitter(api_url=config.resolved_api_url, api_key=api_key),
enforcer=EnforcerClient(config),
step_up=StepUpClient(config),
)
governed_search = tracer.wrap_tool("search_database", search_database)
governed_slack = tracer.wrap_tool("send_slack_message", send_slack_message)
llm = ChatAnthropic(model="claude-sonnet-4-6")
agent = create_react_agent(llm, [governed_search, governed_slack])
result = agent.invoke({"messages": [("user", "Search for Q4 data and post it to #analytics")]})
See examples/langgraph_example.py for the full working script.
Anthropic Claude
Use instrument_anthropic() to wrap tool functions for the Anthropic agentic loop:
import os
import anthropic
import thoth
from thoth import ThothPolicyViolation
governed = thoth.instrument_anthropic(
{"search_docs": search_docs, "delete_record": delete_record},
agent_id="claude-research-agent",
approved_scope=["search_docs"],
tenant_id=os.environ["THOTH_TENANT_ID"],
user_id="alice@acme.com",
enforcement="step_up",
# THOTH_API_KEY read from env automatically
)
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Find our data retention policy."}]
while True:
response = client.messages.create(
model="claude-opus-4-6", max_tokens=1024, tools=TOOLS, messages=messages
)
if response.stop_reason == "end_turn":
break
tool_results = []
for block in response.content:
if block.type == "tool_use":
try:
result = governed[block.name](block.input) # Thoth runs here
except ThothPolicyViolation as e:
result = f"[blocked: {e.reason}]"
tool_results.append({"type": "tool_result", "tool_use_id": block.id, "content": str(result)})
messages += [{"role": "assistant", "content": response.content},
{"role": "user", "content": tool_results}]
See examples/anthropic_example.py for the full working script.
OpenAI Function Calling
Use instrument_openai() to wrap tool functions for the OpenAI tool-calling loop:
import os
import openai
import thoth
from thoth import ThothPolicyViolation
def search_docs(query: str) -> str:
...
def send_email(to: str, subject: str, body: str) -> str:
...
governed = thoth.instrument_openai(
{"search_docs": search_docs, "send_email": send_email},
agent_id="openai-agent",
approved_scope=["search_docs"],
tenant_id=os.environ["THOTH_TENANT_ID"],
user_id="charlie@acme.com",
enforcement="block",
# THOTH_API_KEY read from env automatically
)
client = openai.OpenAI()
messages = [{"role": "user", "content": "Find docs about access control"}]
while True:
response = client.chat.completions.create(model="gpt-4o", tools=TOOLS, messages=messages)
msg = response.choices[0].message
if not msg.tool_calls:
break
messages.append(msg)
for call in msg.tool_calls:
try:
import json
result = governed[call.function.name](json.loads(call.function.arguments))
except ThothPolicyViolation as e:
result = f"[blocked: {e.reason}]"
messages.append({"role": "tool", "tool_call_id": call.id, "content": str(result)})
See examples/openai_example.py for the full working script.
CrewAI
thoth.instrument() detects CrewAI Agent objects automatically:
import os
import thoth
from crewai import Agent
from crewai.tools import tool
@tool("web_search")
def web_search(query: str) -> str:
"""Search the web."""
...
researcher = Agent(role="Researcher", goal="Find information", tools=[web_search])
thoth.instrument(
researcher,
agent_id="crewai-researcher",
approved_scope=["web_search"],
tenant_id=os.environ["THOTH_TENANT_ID"],
user_id="alice@acme.com",
enforcement="block",
# THOTH_API_KEY read from env automatically
)
See examples/crewai_example.py for the full working script.
AutoGen
Use wrap_autogen_tools() to govern AutoGen's function_map:
import os
import autogen
from thoth.integrations.autogen import wrap_autogen_tools
from thoth.tracer import Tracer
# ... build tracer as shown in the LangGraph section above ...
governed = wrap_autogen_tools(
{"search_docs": search_docs, "send_email": send_email},
tracer=tracer,
)
user_proxy = autogen.UserProxyAgent(
name="user_proxy",
function_map=governed, # Thoth enforcement runs on every function call
)
See examples/autogen_example.py for the full working script.
Custom Tools (Generic)
Any object with a .tools list and tools that have a .run method or are directly callable
will be instrumented automatically:
import thoth
from thoth import ThothPolicyViolation
class FileTool:
name = "read_file"
def run(self, path: str) -> str:
with open(path) as f:
return f.read()
class MyAgent:
tools = [FileTool()]
def run(self, prompt: str) -> str:
# ... your agent logic calls self.tools[0].run(...)
...
agent = MyAgent()
agent = thoth.instrument(
agent,
agent_id="file-agent",
approved_scope=["read_file"],
tenant_id="acme-corp",
enforcement="block",
)
try:
result = agent.tools[0].run("/etc/passwd")
except ThothPolicyViolation as e:
print(f"Blocked: {e.tool_name} — {e.reason}")
if e.violation_id:
print(f"Violation record: {e.violation_id}")
Enforcement Modes
Set via the enforcement parameter to instrument().
| Mode | Value | Behavior |
|---|---|---|
| Observe | observe |
All tool calls pass through. Events are still emitted for audit. No blocking, no step-up. Use for initial rollout and baselining. |
| Step-Up | step_up |
Suspicious calls trigger a human approval request (e.g. Slack DM to a reviewer). Tool execution is held until approved or timed out. |
| Block | block |
Calls that violate policy raise ThothPolicyViolation immediately. |
| Progressive | progressive |
Default. Enforcer chooses the appropriate response per tool call based on policy rules. |
Policy Decisions
The enforcer returns one of three decisions for each tool call:
| Decision | Meaning | Agent behavior |
|---|---|---|
ALLOW |
Call is within policy. | Tool executes immediately. |
STEP_UP |
Call requires human approval. | SDK polls /v1/enforce/hold/{token} until approved or timed out. On timeout → BLOCK. |
BLOCK |
Call violates policy. | ThothPolicyViolation is raised before the tool executes. |
Enforcer errors (network timeout, 5xx) always result in ALLOW so that infrastructure faults
never interrupt production workloads. All errors are logged at WARNING level.
Handling Violations
from thoth import ThothPolicyViolation
try:
result = agent.tools[0].run(user_input)
except ThothPolicyViolation as e:
# e.tool_name — the tool that was blocked
# e.reason — human-readable policy reason
# e.violation_id — reference ID for the violation record in Maat dashboard
logger.warning("Policy violation on %s: %s (id=%s)", e.tool_name, e.reason, e.violation_id)
return {"error": "This action is not permitted under your current access policy."}
Step-Up Authentication
When the enforcer returns STEP_UP, Thoth automatically:
- Sends an approval request to the configured notification channel (Slack by default).
- Polls
/v1/enforce/hold/{hold_token}everystep_up_poll_interval_seconds(default: 5s). - If approved within
step_up_timeout_minutes(default: 15 minutes) → tool executes. - If timed out → raises
ThothPolicyViolationwith reason"step-up auth timeout".
Configure timeouts via ThothConfig:
from thoth.models import ThothConfig, EnforcementMode
config = ThothConfig(
agent_id="sensitive-agent",
approved_scope=["delete_record"],
tenant_id="acme-corp",
enforcement=EnforcementMode.STEP_UP,
step_up_timeout_minutes=5, # fail fast in production
step_up_poll_interval_seconds=3,
)
Session Inspection
Each instrument() call creates a SessionContext stored in a contextvars.ContextVar. Inspect
it from anywhere in the same async context:
from thoth import get_current_session
session = get_current_session()
if session:
print(f"Session ID: {session.session_id}")
print(f"Tools called: {session.tool_calls}")
print(f"Token spend: {session.token_spend}")
print(f"In scope: {session.is_in_scope('web_search')}")
Configuration Reference
instrument() parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
agent |
Any |
— | Agent object to instrument. Must have a .tools list. |
agent_id |
str |
— | Stable identifier for this agent definition. Used in policy rules. |
approved_scope |
list[str] |
— | List of tool names this agent is authorized to call. |
tenant_id |
str |
— | Your Maat tenant ID. |
user_id |
str |
"system" |
Identity of the user on whose behalf the agent acts. |
enforcement |
str |
"progressive" |
Enforcement mode: observe, step_up, block, or progressive. |
api_key |
str | None |
$THOTH_API_KEY |
API key from the Aten dashboard. Events sent over HTTPS — no AWS credentials required. |
api_url |
str | None |
$THOTH_API_URL |
Required tenant API base URL used for both event ingestion and policy checks. |
session_id |
str | None |
auto-generated UUID | Pass an existing session ID to continue a session across calls. |
ThothConfig fields
| Field | Type | Default | Description |
|---|---|---|---|
agent_id |
str |
— | Stable identifier for this agent. |
approved_scope |
list[str] |
— | Tool names the agent is authorized to call. |
tenant_id |
str |
— | Your Maat tenant ID. |
user_id |
str |
"system" |
User identity for the session. |
enforcement |
EnforcementMode |
PROGRESSIVE |
Enforcement mode. |
api_key |
str | None |
None |
Aten API key. Falls back to THOTH_API_KEY env var via _build_components. |
api_url |
str | None |
None |
Required tenant API base URL. Provide directly or via THOTH_API_URL. Used for both /v1/enforce and /v1/events/batch. |
step_up_timeout_minutes |
int |
15 |
Timeout for step-up approval. |
step_up_poll_interval_seconds |
int |
5 |
Polling interval for step-up hold status. |
Environment Variables
| Variable | Description |
|---|---|
THOTH_API_KEY |
API key from the Aten dashboard. Enables HTTPS event transport. Example: thoth_live_abc123... |
THOTH_API_URL |
Required tenant API base URL for both event ingestion and policy checks. Example: https://enforce.<tenant>.<apex-domain> |
Resolution precedence:
THOTH_API_URLoverridesapi_url.
Dashboard
View sessions, violations, step-up requests, and policy decisions in your Thoth dashboard at
https://<tenant>.<apex-domain>.
The dashboard shows:
- Sessions — per-agent session timelines with all tool calls
- Violations — blocked or escalated actions with full context
- Approvals — step-up queue for human reviewers
- Policies — view and edit the rules driving enforcement decisions
- Behavioral Analytics — drift detection and anomaly scores over time
License
Copyright 2026 Aten Security, Inc. All rights reserved.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aten_thoth-0.1.6.tar.gz.
File metadata
- Download URL: aten_thoth-0.1.6.tar.gz
- Upload date:
- Size: 20.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
295a527184f0300542cdda42ae5244c142c850bb518faf81f540db80b06ea907
|
|
| MD5 |
505d330caf5b826a19373aadc81deff8
|
|
| BLAKE2b-256 |
c779768dbdf6465962fe4525da86cd55e64e6c86a146aff203676edbac296565
|
File details
Details for the file aten_thoth-0.1.6-py3-none-any.whl.
File metadata
- Download URL: aten_thoth-0.1.6-py3-none-any.whl
- Upload date:
- Size: 22.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
167f94dbd08f04108eee5559d8c8e4fefd0bbf7cb34e635d7196cfb050ea13d6
|
|
| MD5 |
11d9efd37b42f8bc3b43894cb82202d0
|
|
| BLAKE2b-256 |
c47203214c6704a022e0230e55c2a46acdcba21f2325e3291d09b7a0993f7c5c
|