atensec-thoth 0.1.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atensec_thoth-0.1.16.dist-info/METADATA +714 -0
- atensec_thoth-0.1.16.dist-info/RECORD +24 -0
- atensec_thoth-0.1.16.dist-info/WHEEL +4 -0
- atensec_thoth-0.1.16.dist-info/licenses/LICENSE +201 -0
- thoth/__init__.py +28 -0
- thoth/_context.py +15 -0
- thoth/client.py +50 -0
- thoth/emitter.py +194 -0
- thoth/enforcer_client.py +140 -0
- thoth/exceptions.py +61 -0
- thoth/http_diagnostics.py +67 -0
- thoth/instrumentor.py +343 -0
- thoth/integrations/__init__.py +0 -0
- thoth/integrations/anthropic.py +88 -0
- thoth/integrations/autogen.py +62 -0
- thoth/integrations/claude_agent_sdk.py +150 -0
- thoth/integrations/crewai.py +72 -0
- thoth/integrations/langchain.py +27 -0
- thoth/integrations/openai.py +90 -0
- thoth/logging_config.py +54 -0
- thoth/models.py +286 -0
- thoth/session.py +23 -0
- thoth/step_up.py +95 -0
- thoth/tracer.py +528 -0
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: atensec-thoth
|
|
3
|
+
Version: 0.1.16
|
|
4
|
+
Summary: AI agent governance SDK by Aten Security
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Author: Aten Engineering
|
|
7
|
+
Author-email: engineering@atensecurity.com
|
|
8
|
+
Requires-Python: >=3.12,<3.15
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Provides-Extra: anthropic
|
|
14
|
+
Provides-Extra: autogen
|
|
15
|
+
Provides-Extra: claude
|
|
16
|
+
Provides-Extra: langchain
|
|
17
|
+
Provides-Extra: openai
|
|
18
|
+
Requires-Dist: anyio (>=4.0)
|
|
19
|
+
Requires-Dist: boto3 (>=1.34)
|
|
20
|
+
Requires-Dist: httpx (>=0.27)
|
|
21
|
+
Requires-Dist: pydantic (>=2.0)
|
|
22
|
+
Project-URL: Documentation, https://docs.atensecurity.com
|
|
23
|
+
Project-URL: Homepage, https://atensecurity.com/thoth
|
|
24
|
+
Project-URL: Repository, https://github.com/atensecurity/thoth-py
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# Thoth SDK
|
|
28
|
+
|
|
29
|
+
Thoth SDK instruments your AI agents for governance, policy enforcement, and behavioral monitoring.
|
|
30
|
+
Every tool call is evaluated against your organization's security policies in <100ms — blocking,
|
|
31
|
+
stepping up for human approval, or silently observing based on your configured enforcement mode.
|
|
32
|
+
|
|
33
|
+
**Package name:** `atensec-thoth` | **PyPI:** `pip install atensec-thoth`
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Table of Contents
|
|
38
|
+
|
|
39
|
+
1. [Installation](#installation)
|
|
40
|
+
2. [Quick Start](#quick-start)
|
|
41
|
+
3. [What's New Since 0.1.15](#whats-new-since-0115)
|
|
42
|
+
4. [Legacy Compatibility (`ThothClient`)](#legacy-compatibility-thothclient)
|
|
43
|
+
5. [How It Works](#how-it-works)
|
|
44
|
+
6. [Integration Examples](#integration-examples)
|
|
45
|
+
- [LangChain AgentExecutor](#langchain-agentexecutor)
|
|
46
|
+
- [LangGraph](#langgraph)
|
|
47
|
+
- [Claude Agent SDK (`query`)](#claude-agent-sdk-query)
|
|
48
|
+
- [OpenAI Function Calling](#openai-function-calling)
|
|
49
|
+
- [Anthropic Claude](#anthropic-claude)
|
|
50
|
+
- [CrewAI](#crewai)
|
|
51
|
+
- [AutoGen](#autogen)
|
|
52
|
+
- [Custom Tools (Generic)](#custom-tools-generic)
|
|
53
|
+
7. [Enforcement Modes](#enforcement-modes)
|
|
54
|
+
8. [Policy Decisions](#policy-decisions)
|
|
55
|
+
9. [Handling Violations](#handling-violations)
|
|
56
|
+
10. [Step-Up Authentication](#step-up-authentication)
|
|
57
|
+
11. [Session Inspection](#session-inspection)
|
|
58
|
+
12. [Configuration Reference](#configuration-reference)
|
|
59
|
+
13. [Dashboard](#dashboard)
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install atensec-thoth
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**With LangChain / LangGraph support:**
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install "atensec-thoth[langchain]"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**With OpenAI support:**
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install "atensec-thoth[openai]"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**With Claude Agent SDK support:**
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install "atensec-thoth[claude]"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**With AutoGen support:**
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pip install "atensec-thoth[autogen]"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Requirements:** Python 3.12+
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Quick Start
|
|
98
|
+
|
|
99
|
+
**1. Get your API key** from your Thoth dashboard at `https://<tenant>.<apex-domain>` under
|
|
100
|
+
**Settings → API Keys**.
|
|
101
|
+
|
|
102
|
+
**2. Set environment variables:**
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
export THOTH_API_KEY="thoth_live_..."
|
|
106
|
+
export THOTH_API_URL="https://enforce.<tenant>.<apex-domain>"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**3. Instrument your agent — three lines of code:**
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
import os
|
|
113
|
+
import thoth
|
|
114
|
+
|
|
115
|
+
# Instrument your agent — returns the same object, mutated in-place
|
|
116
|
+
agent = thoth.instrument(
|
|
117
|
+
agent,
|
|
118
|
+
agent_id="document-summarizer",
|
|
119
|
+
approved_scope=["read_file", "summarize"], # tools outside this list are blocked
|
|
120
|
+
tenant_id=os.environ["THOTH_TENANT_ID"],
|
|
121
|
+
api_url=os.environ["THOTH_API_URL"],
|
|
122
|
+
user_id="alice@example.com",
|
|
123
|
+
enforcement="block", # default deny
|
|
124
|
+
# api_key set via THOTH_API_KEY env var
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Every tool call is now governed — no other changes required
|
|
128
|
+
result = agent.run("Summarize the attached document and send it to the team.")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
That's it. No AWS credentials, no infrastructure setup — the SDK sends events and enforcement
|
|
132
|
+
requests over HTTPS to your tenant API URL.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## What's New Since 0.1.15
|
|
137
|
+
|
|
138
|
+
If you're upgrading from `0.1.15`, these are the material integration changes:
|
|
139
|
+
|
|
140
|
+
- **Package publish target is now `atensec-thoth`** (docs/examples updated accordingly).
|
|
141
|
+
- **Decision model expanded** beyond `ALLOW/BLOCK/STEP_UP`:
|
|
142
|
+
- `MODIFY`: enforcer can sanitize/transform tool args before execution.
|
|
143
|
+
- `DEFER`: enforcer can defer execution with retry guidance.
|
|
144
|
+
- **Decision envelope context surfaced end-to-end**:
|
|
145
|
+
- `decision_reason_code`, `action_classification`, `authorization_decision`
|
|
146
|
+
- `risk_score`, `latency_ms`, `pack_id`, `pack_version`, `rule_version`
|
|
147
|
+
- `matched_rule_ids`, `matched_control_ids`, `regulatory_regimes`, `policy_references`, `model_signals`
|
|
148
|
+
- `fastml_features`, `score_components`, `top_contributors`, `decision_evidence`, `receipt`
|
|
149
|
+
- **Async tool wrappers use async enforcement path** (`acheck`) and preserve sync/async behavior.
|
|
150
|
+
- **Structured tool metadata emission** now includes normalized `tool_call` payload and result summaries.
|
|
151
|
+
- **Enforcer payload now includes richer context** when provided:
|
|
152
|
+
- `tool_args`, `environment`, `enforcement_trace_id`
|
|
153
|
+
- `session_intent`, `purpose`, `data_classification`, `task_context`
|
|
154
|
+
- **Release automation moved to tag-driven public workflow** in `atensecurity/thoth-py`, with release notes sourced from `CHANGELOG.md`.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Legacy Compatibility (`ThothClient`)
|
|
159
|
+
|
|
160
|
+
`thoth.instrument()`, `thoth.instrument_anthropic()`, and `thoth.instrument_openai()` are the
|
|
161
|
+
preferred APIs for new integrations.
|
|
162
|
+
|
|
163
|
+
`ThothClient` is still exported for backward compatibility with older examples and wrappers:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from thoth import ThothClient
|
|
167
|
+
|
|
168
|
+
client = ThothClient(
|
|
169
|
+
agent_id="document-summarizer",
|
|
170
|
+
approved_scope=["read_file", "summarize"],
|
|
171
|
+
tenant_id="acme-corp",
|
|
172
|
+
api_url="https://enforce.acme.example",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
agent = client.wrap(agent) # legacy alias for client.instrument(agent)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
For new code, prefer the module-level APIs shown in Quick Start.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## How It Works
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
Agent calls tool
|
|
186
|
+
│
|
|
187
|
+
▼
|
|
188
|
+
Thoth intercepts (wrap_tool)
|
|
189
|
+
│
|
|
190
|
+
├── Emits TOOL_CALL_PRE event → Aten API (async, non-blocking)
|
|
191
|
+
│
|
|
192
|
+
├── Calls enforcer /v1/enforce
|
|
193
|
+
│ │
|
|
194
|
+
│ ├── ALLOW → tool executes normally
|
|
195
|
+
│ ├── STEP_UP → waits for human approval (polls /v1/enforce/hold/{token})
|
|
196
|
+
│ └── BLOCK → raises ThothPolicyViolation
|
|
197
|
+
│
|
|
198
|
+
├── Tool executes (if allowed)
|
|
199
|
+
│
|
|
200
|
+
└── Emits TOOL_CALL_POST event → Aten API (async, non-blocking)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Events are batched and flushed to the Aten ingest API in a background daemon thread with
|
|
204
|
+
at-most-10 per batch. If the enforcer is unreachable, Thoth fails closed (BLOCK) and logs a
|
|
205
|
+
warning. Tool execution is prevented until policy checks are reachable again.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Integration Examples
|
|
210
|
+
|
|
211
|
+
### LangChain AgentExecutor
|
|
212
|
+
|
|
213
|
+
Thoth detects `AgentExecutor` automatically and wraps both `tool.run` and `tool._run`:
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
from langchain.agents import AgentExecutor, create_openai_tools_agent
|
|
217
|
+
from langchain_openai import ChatOpenAI
|
|
218
|
+
from langchain.tools import tool
|
|
219
|
+
import thoth
|
|
220
|
+
|
|
221
|
+
@tool
|
|
222
|
+
def web_search(query: str) -> str:
|
|
223
|
+
"""Search the web for current information."""
|
|
224
|
+
...
|
|
225
|
+
|
|
226
|
+
@tool
|
|
227
|
+
def read_file(path: str) -> str:
|
|
228
|
+
"""Read a file from the local filesystem."""
|
|
229
|
+
...
|
|
230
|
+
|
|
231
|
+
llm = ChatOpenAI(model="gpt-4o")
|
|
232
|
+
agent = create_openai_tools_agent(llm, tools=[web_search, read_file], prompt=...)
|
|
233
|
+
executor = AgentExecutor(agent=agent, tools=[web_search, read_file])
|
|
234
|
+
|
|
235
|
+
# One call instruments all tools
|
|
236
|
+
executor = thoth.instrument(
|
|
237
|
+
executor,
|
|
238
|
+
agent_id="research-agent",
|
|
239
|
+
approved_scope=["web_search", "read_file"],
|
|
240
|
+
tenant_id="acme-corp",
|
|
241
|
+
user_id="bob@acme.com",
|
|
242
|
+
enforcement="block",
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Now every tool invocation is policy-checked
|
|
246
|
+
result = executor.invoke({"input": "Find recent SEC filings for AAPL"})
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### LangGraph
|
|
250
|
+
|
|
251
|
+
For LangGraph agents, wrap tool callables directly using `Tracer.wrap_tool`. Because LangGraph
|
|
252
|
+
doesn't use `AgentExecutor`, the low-level API is needed here:
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
import os
|
|
256
|
+
import thoth
|
|
257
|
+
from thoth.models import ThothConfig, EnforcementMode
|
|
258
|
+
from thoth.session import SessionContext
|
|
259
|
+
from thoth.emitter import HttpEmitter
|
|
260
|
+
from thoth.enforcer_client import EnforcerClient
|
|
261
|
+
from thoth.step_up import StepUpClient
|
|
262
|
+
from thoth.tracer import Tracer
|
|
263
|
+
from langchain_anthropic import ChatAnthropic
|
|
264
|
+
from langchain_core.tools import tool
|
|
265
|
+
from langgraph.prebuilt import create_react_agent
|
|
266
|
+
|
|
267
|
+
@tool
|
|
268
|
+
def search_database(query: str) -> str:
|
|
269
|
+
"""Search the internal knowledge base."""
|
|
270
|
+
...
|
|
271
|
+
|
|
272
|
+
@tool
|
|
273
|
+
def send_slack_message(channel: str, message: str) -> str:
|
|
274
|
+
"""Post a message to Slack."""
|
|
275
|
+
...
|
|
276
|
+
|
|
277
|
+
api_key = os.environ["THOTH_API_KEY"]
|
|
278
|
+
config = ThothConfig(
|
|
279
|
+
agent_id="langgraph-analyst",
|
|
280
|
+
approved_scope=["search_database", "send_slack_message"],
|
|
281
|
+
tenant_id=os.environ["THOTH_TENANT_ID"],
|
|
282
|
+
user_id="alice@acme.com",
|
|
283
|
+
enforcement=EnforcementMode.BLOCK,
|
|
284
|
+
api_key=api_key,
|
|
285
|
+
)
|
|
286
|
+
session = SessionContext(config)
|
|
287
|
+
tracer = Tracer(
|
|
288
|
+
config=config,
|
|
289
|
+
session=session,
|
|
290
|
+
emitter=HttpEmitter(api_url=config.resolved_api_url, api_key=api_key),
|
|
291
|
+
enforcer=EnforcerClient(config),
|
|
292
|
+
step_up=StepUpClient(config),
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
governed_search = tracer.wrap_tool("search_database", search_database)
|
|
296
|
+
governed_slack = tracer.wrap_tool("send_slack_message", send_slack_message)
|
|
297
|
+
|
|
298
|
+
llm = ChatAnthropic(model="claude-sonnet-4-6")
|
|
299
|
+
agent = create_react_agent(llm, [governed_search, governed_slack])
|
|
300
|
+
|
|
301
|
+
result = agent.invoke({"messages": [("user", "Search for Q4 data and post it to #analytics")]})
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
See `examples/langgraph_example.py` for the full working script.
|
|
305
|
+
|
|
306
|
+
### Claude Agent SDK (`query`)
|
|
307
|
+
|
|
308
|
+
For `claude-agent-sdk`, instrument `ClaudeAgentOptions` instead of a `.tools` list.
|
|
309
|
+
Thoth hooks into `can_use_tool` and tool lifecycle hooks.
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
import anyio
|
|
313
|
+
import os
|
|
314
|
+
from claude_agent_sdk import ClaudeAgentOptions, query
|
|
315
|
+
import thoth
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
async def prompt_stream():
|
|
319
|
+
# can_use_tool callbacks require streaming mode in claude-agent-sdk
|
|
320
|
+
yield {
|
|
321
|
+
"type": "user",
|
|
322
|
+
"message": {"role": "user", "content": "Read README.md and summarize risks."},
|
|
323
|
+
"parent_tool_use_id": None,
|
|
324
|
+
"session_id": "thoth-demo-session",
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
async def main():
|
|
329
|
+
options = ClaudeAgentOptions(
|
|
330
|
+
max_turns=1,
|
|
331
|
+
allowed_tools=["Read"], # auto-allow list in Claude SDK
|
|
332
|
+
disallowed_tools=["Bash"], # explicit deny list in Claude SDK
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
options = thoth.instrument_claude_agent_sdk(
|
|
336
|
+
options,
|
|
337
|
+
agent_id="claude-agent-sdk-demo",
|
|
338
|
+
approved_scope=["Read"], # Thoth policy scope
|
|
339
|
+
tenant_id=os.environ["THOTH_TENANT_ID"],
|
|
340
|
+
user_id="alice@acme.com",
|
|
341
|
+
enforcement="block",
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
async for message in query(prompt=prompt_stream(), options=options):
|
|
345
|
+
print(message)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
anyio.run(main)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
See `examples/claude_agent_sdk_example.py` for the full working script.
|
|
352
|
+
|
|
353
|
+
### Anthropic Claude
|
|
354
|
+
|
|
355
|
+
Use `instrument_anthropic()` to wrap tool functions for the Anthropic agentic loop:
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
import os
|
|
359
|
+
import anthropic
|
|
360
|
+
import thoth
|
|
361
|
+
from thoth import ThothPolicyViolation
|
|
362
|
+
|
|
363
|
+
governed = thoth.instrument_anthropic(
|
|
364
|
+
{"search_docs": search_docs, "delete_record": delete_record},
|
|
365
|
+
agent_id="claude-research-agent",
|
|
366
|
+
approved_scope=["search_docs"],
|
|
367
|
+
tenant_id=os.environ["THOTH_TENANT_ID"],
|
|
368
|
+
user_id="alice@acme.com",
|
|
369
|
+
enforcement="step_up",
|
|
370
|
+
# THOTH_API_KEY read from env automatically
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
client = anthropic.Anthropic()
|
|
374
|
+
messages = [{"role": "user", "content": "Find our data retention policy."}]
|
|
375
|
+
|
|
376
|
+
while True:
|
|
377
|
+
response = client.messages.create(
|
|
378
|
+
model="claude-opus-4-6", max_tokens=1024, tools=TOOLS, messages=messages
|
|
379
|
+
)
|
|
380
|
+
if response.stop_reason == "end_turn":
|
|
381
|
+
break
|
|
382
|
+
tool_results = []
|
|
383
|
+
for block in response.content:
|
|
384
|
+
if block.type == "tool_use":
|
|
385
|
+
try:
|
|
386
|
+
result = governed[block.name](block.input) # Thoth runs here
|
|
387
|
+
except ThothPolicyViolation as e:
|
|
388
|
+
result = f"[blocked: {e.reason}]"
|
|
389
|
+
tool_results.append({"type": "tool_result", "tool_use_id": block.id, "content": str(result)})
|
|
390
|
+
messages += [{"role": "assistant", "content": response.content},
|
|
391
|
+
{"role": "user", "content": tool_results}]
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
See `examples/anthropic_example.py` for the full working script.
|
|
395
|
+
|
|
396
|
+
### OpenAI Function Calling
|
|
397
|
+
|
|
398
|
+
Use `instrument_openai()` to wrap tool functions for the OpenAI tool-calling loop:
|
|
399
|
+
|
|
400
|
+
```python
|
|
401
|
+
import os
|
|
402
|
+
import openai
|
|
403
|
+
import thoth
|
|
404
|
+
from thoth import ThothPolicyViolation
|
|
405
|
+
|
|
406
|
+
def search_docs(query: str) -> str:
|
|
407
|
+
...
|
|
408
|
+
|
|
409
|
+
def send_email(to: str, subject: str, body: str) -> str:
|
|
410
|
+
...
|
|
411
|
+
|
|
412
|
+
governed = thoth.instrument_openai(
|
|
413
|
+
{"search_docs": search_docs, "send_email": send_email},
|
|
414
|
+
agent_id="openai-agent",
|
|
415
|
+
approved_scope=["search_docs"],
|
|
416
|
+
tenant_id=os.environ["THOTH_TENANT_ID"],
|
|
417
|
+
user_id="charlie@acme.com",
|
|
418
|
+
enforcement="block",
|
|
419
|
+
# THOTH_API_KEY read from env automatically
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
client = openai.OpenAI()
|
|
423
|
+
messages = [{"role": "user", "content": "Find docs about access control"}]
|
|
424
|
+
|
|
425
|
+
while True:
|
|
426
|
+
response = client.chat.completions.create(model="gpt-4o", tools=TOOLS, messages=messages)
|
|
427
|
+
msg = response.choices[0].message
|
|
428
|
+
if not msg.tool_calls:
|
|
429
|
+
break
|
|
430
|
+
messages.append(msg)
|
|
431
|
+
for call in msg.tool_calls:
|
|
432
|
+
try:
|
|
433
|
+
import json
|
|
434
|
+
result = governed[call.function.name](json.loads(call.function.arguments))
|
|
435
|
+
except ThothPolicyViolation as e:
|
|
436
|
+
result = f"[blocked: {e.reason}]"
|
|
437
|
+
messages.append({"role": "tool", "tool_call_id": call.id, "content": str(result)})
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
See `examples/openai_example.py` for the full working script.
|
|
441
|
+
|
|
442
|
+
### CrewAI
|
|
443
|
+
|
|
444
|
+
`thoth.instrument()` detects CrewAI `Agent` objects automatically:
|
|
445
|
+
|
|
446
|
+
```python
|
|
447
|
+
import os
|
|
448
|
+
import thoth
|
|
449
|
+
from crewai import Agent
|
|
450
|
+
from crewai.tools import tool
|
|
451
|
+
|
|
452
|
+
@tool("web_search")
|
|
453
|
+
def web_search(query: str) -> str:
|
|
454
|
+
"""Search the web."""
|
|
455
|
+
...
|
|
456
|
+
|
|
457
|
+
researcher = Agent(role="Researcher", goal="Find information", tools=[web_search])
|
|
458
|
+
|
|
459
|
+
thoth.instrument(
|
|
460
|
+
researcher,
|
|
461
|
+
agent_id="crewai-researcher",
|
|
462
|
+
approved_scope=["web_search"],
|
|
463
|
+
tenant_id=os.environ["THOTH_TENANT_ID"],
|
|
464
|
+
user_id="alice@acme.com",
|
|
465
|
+
enforcement="block",
|
|
466
|
+
# THOTH_API_KEY read from env automatically
|
|
467
|
+
)
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
See `examples/crewai_example.py` for the full working script.
|
|
471
|
+
|
|
472
|
+
### AutoGen
|
|
473
|
+
|
|
474
|
+
Use `wrap_autogen_tools()` to govern AutoGen's `function_map`:
|
|
475
|
+
|
|
476
|
+
```python
|
|
477
|
+
import os
|
|
478
|
+
import autogen
|
|
479
|
+
from thoth.integrations.autogen import wrap_autogen_tools
|
|
480
|
+
from thoth.tracer import Tracer
|
|
481
|
+
# ... build tracer as shown in the LangGraph section above ...
|
|
482
|
+
|
|
483
|
+
governed = wrap_autogen_tools(
|
|
484
|
+
{"search_docs": search_docs, "send_email": send_email},
|
|
485
|
+
tracer=tracer,
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
user_proxy = autogen.UserProxyAgent(
|
|
489
|
+
name="user_proxy",
|
|
490
|
+
function_map=governed, # Thoth enforcement runs on every function call
|
|
491
|
+
)
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
See `examples/autogen_example.py` for the full working script.
|
|
495
|
+
|
|
496
|
+
### Custom Tools (Generic)
|
|
497
|
+
|
|
498
|
+
Any object with a `.tools` list and tools that have a `.run` method or are directly callable
|
|
499
|
+
will be instrumented automatically:
|
|
500
|
+
|
|
501
|
+
```python
|
|
502
|
+
import thoth
|
|
503
|
+
from thoth import ThothPolicyViolation
|
|
504
|
+
|
|
505
|
+
class FileTool:
|
|
506
|
+
name = "read_file"
|
|
507
|
+
|
|
508
|
+
def run(self, path: str) -> str:
|
|
509
|
+
with open(path) as f:
|
|
510
|
+
return f.read()
|
|
511
|
+
|
|
512
|
+
class MyAgent:
|
|
513
|
+
tools = [FileTool()]
|
|
514
|
+
|
|
515
|
+
def run(self, prompt: str) -> str:
|
|
516
|
+
# ... your agent logic calls self.tools[0].run(...)
|
|
517
|
+
...
|
|
518
|
+
|
|
519
|
+
agent = MyAgent()
|
|
520
|
+
|
|
521
|
+
agent = thoth.instrument(
|
|
522
|
+
agent,
|
|
523
|
+
agent_id="file-agent",
|
|
524
|
+
approved_scope=["read_file"],
|
|
525
|
+
tenant_id="acme-corp",
|
|
526
|
+
enforcement="block",
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
try:
|
|
530
|
+
result = agent.tools[0].run("/etc/passwd")
|
|
531
|
+
except ThothPolicyViolation as e:
|
|
532
|
+
print(f"Blocked: {e.tool_name} — {e.reason}")
|
|
533
|
+
if e.violation_id:
|
|
534
|
+
print(f"Violation record: {e.violation_id}")
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## Enforcement Modes
|
|
540
|
+
|
|
541
|
+
Set via the `enforcement` parameter to `instrument()`.
|
|
542
|
+
|
|
543
|
+
| Mode | Value | Behavior |
|
|
544
|
+
|---|---|---|
|
|
545
|
+
| Observe | `observe` | All tool calls pass through. Events are still emitted for audit. No blocking, no step-up. Use for initial rollout and baselining. |
|
|
546
|
+
| 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. |
|
|
547
|
+
| Block | `block` | Calls that violate policy raise `ThothPolicyViolation` immediately. |
|
|
548
|
+
| Progressive | `progressive` | Escalating mode. Enforcer chooses the appropriate response per tool call based on policy rules. |
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## Policy Decisions
|
|
553
|
+
|
|
554
|
+
The enforcer returns one of five decisions for each tool call:
|
|
555
|
+
|
|
556
|
+
| Decision | Meaning | Agent behavior |
|
|
557
|
+
|---|---|---|
|
|
558
|
+
| `ALLOW` | Call is within policy. | Tool executes immediately. |
|
|
559
|
+
| `STEP_UP` | Call requires human approval. | SDK polls `/v1/enforce/hold/{token}` until approved or timed out. On timeout → `BLOCK`. |
|
|
560
|
+
| `MODIFY` | Call is allowed with transformed/sanitized arguments. | SDK rewrites the tool call args and then executes the tool. |
|
|
561
|
+
| `DEFER` | Call is postponed pending more context/workflow state. | SDK raises `ThothPolicyViolation` with defer context and timeout guidance. |
|
|
562
|
+
| `BLOCK` | Call violates policy. | `ThothPolicyViolation` is raised before the tool executes. |
|
|
563
|
+
|
|
564
|
+
Enforcer errors (for example, network timeout or 5xx) fail closed: the SDK treats them as
|
|
565
|
+
`BLOCK` and raises `ThothPolicyViolation`. Authentication/ingress failures include diagnostic hints
|
|
566
|
+
in logs to speed up misconfiguration debugging.
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
## Handling Violations
|
|
571
|
+
|
|
572
|
+
```python
|
|
573
|
+
from thoth import ThothPolicyViolation
|
|
574
|
+
|
|
575
|
+
try:
|
|
576
|
+
result = agent.tools[0].run(user_input)
|
|
577
|
+
except ThothPolicyViolation as e:
|
|
578
|
+
# e.tool_name — the tool that was blocked
|
|
579
|
+
# e.reason — human-readable policy reason
|
|
580
|
+
# e.violation_id — reference ID for the violation record in Maat dashboard
|
|
581
|
+
# e.decision_reason_code / e.action_classification / e.authorization_decision
|
|
582
|
+
# e.risk_score / e.pack_id / e.pack_version / e.rule_version
|
|
583
|
+
# e.matched_rule_ids / e.matched_control_ids / e.regulatory_regimes
|
|
584
|
+
# e.policy_references / e.model_signals
|
|
585
|
+
# e.fastml_features / e.score_components / e.top_contributors / e.decision_evidence
|
|
586
|
+
# e.enforcement_trace_id / e.receipt
|
|
587
|
+
logger.warning("Policy violation on %s: %s (id=%s)", e.tool_name, e.reason, e.violation_id)
|
|
588
|
+
return {"error": "This action is not permitted under your current access policy."}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## Step-Up Authentication
|
|
594
|
+
|
|
595
|
+
When the enforcer returns `STEP_UP`, Thoth automatically:
|
|
596
|
+
|
|
597
|
+
1. Sends an approval request to the configured notification channel (Slack by default).
|
|
598
|
+
2. Polls `/v1/enforce/hold/{hold_token}` every `step_up_poll_interval_seconds` (default: 5s).
|
|
599
|
+
3. If approved within `step_up_timeout_minutes` (default: 15 minutes) → tool executes.
|
|
600
|
+
4. If timed out → raises `ThothPolicyViolation` with reason `"step-up auth timeout"`.
|
|
601
|
+
|
|
602
|
+
Configure timeouts via `ThothConfig`:
|
|
603
|
+
|
|
604
|
+
```python
|
|
605
|
+
from thoth.models import ThothConfig, EnforcementMode
|
|
606
|
+
|
|
607
|
+
config = ThothConfig(
|
|
608
|
+
agent_id="sensitive-agent",
|
|
609
|
+
approved_scope=["delete_record"],
|
|
610
|
+
tenant_id="acme-corp",
|
|
611
|
+
enforcement=EnforcementMode.STEP_UP,
|
|
612
|
+
step_up_timeout_minutes=5, # fail fast in production
|
|
613
|
+
step_up_poll_interval_seconds=3,
|
|
614
|
+
)
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
---
|
|
618
|
+
|
|
619
|
+
## Session Inspection
|
|
620
|
+
|
|
621
|
+
Each `instrument()` call creates a `SessionContext` stored in a `contextvars.ContextVar`. Inspect
|
|
622
|
+
it from anywhere in the same async context:
|
|
623
|
+
|
|
624
|
+
```python
|
|
625
|
+
from thoth import get_current_session
|
|
626
|
+
|
|
627
|
+
session = get_current_session()
|
|
628
|
+
if session:
|
|
629
|
+
print(f"Session ID: {session.session_id}")
|
|
630
|
+
print(f"Tools called: {session.tool_calls}")
|
|
631
|
+
print(f"Token spend: {session.token_spend}")
|
|
632
|
+
print(f"In scope: {session.is_in_scope('web_search')}")
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
## Configuration Reference
|
|
638
|
+
|
|
639
|
+
### `instrument()` parameters
|
|
640
|
+
|
|
641
|
+
| Parameter | Type | Default | Description |
|
|
642
|
+
|---|---|---|---|
|
|
643
|
+
| `agent` | `Any` | — | Agent object to instrument. Must have a `.tools` list. |
|
|
644
|
+
| `agent_id` | `str` | — | Stable identifier for this agent definition. Used in policy rules. |
|
|
645
|
+
| `approved_scope` | `list[str]` | — | List of tool names this agent is authorized to call. |
|
|
646
|
+
| `tenant_id` | `str` | — | Your Maat tenant ID. |
|
|
647
|
+
| `user_id` | `str` | `"system"` | Identity of the user on whose behalf the agent acts. |
|
|
648
|
+
| `enforcement` | `str` | `"block"` | Enforcement mode: `observe`, `step_up`, `block`, or `progressive`. |
|
|
649
|
+
| `api_key` | `str \| None` | `$THOTH_API_KEY` | API key from the Aten dashboard. Events sent over HTTPS — no AWS credentials required. |
|
|
650
|
+
| `event_ingest_token` | `str \| None` | `$THOTH_EVENT_INGEST_TOKEN` | Optional dedicated token sent as `X-Thoth-Event-Ingest-Token` for `/v1/events/batch`. |
|
|
651
|
+
| `api_url` | `str \| None` | `$THOTH_API_URL` | Required tenant API base URL used for both event ingestion and policy checks. |
|
|
652
|
+
| `session_id` | `str \| None` | auto-generated UUID | Pass an existing session ID to continue a session across calls. |
|
|
653
|
+
| `session_intent` | `str \| None` | `None` | Intent label used for minimum-necessary/session-scope policies. |
|
|
654
|
+
| `purpose` | `str \| None` | `None` | Policy context for allowed business purpose. |
|
|
655
|
+
| `data_classification` | `str \| None` | `None` | Policy context for data sensitivity. |
|
|
656
|
+
| `task_context` | `dict[str, Any] \| None` | `{}` | Optional delegation metadata (`initiated_by`, `task_id`, `chain`, etc.). |
|
|
657
|
+
| `environment` | `str \| None` | `$THOTH_ENVIRONMENT` or `"prod"` | Environment selector sent to enforcer policy lookup. |
|
|
658
|
+
| `enforcement_trace_id` | `str \| None` | `session_id` | Correlation ID propagated across enforcement/evidence paths. |
|
|
659
|
+
|
|
660
|
+
### `ThothConfig` fields
|
|
661
|
+
|
|
662
|
+
| Field | Type | Default | Description |
|
|
663
|
+
|---|---|---|---|
|
|
664
|
+
| `agent_id` | `str` | — | Stable identifier for this agent. |
|
|
665
|
+
| `approved_scope` | `list[str]` | — | Tool names the agent is authorized to call. |
|
|
666
|
+
| `tenant_id` | `str` | — | Your Maat tenant ID. |
|
|
667
|
+
| `user_id` | `str` | `"system"` | User identity for the session. |
|
|
668
|
+
| `enforcement` | `EnforcementMode` | `BLOCK` | Enforcement mode. |
|
|
669
|
+
| `api_key` | `str \| None` | `None` | Aten API key. Falls back to `THOTH_API_KEY` env var via `_build_components`. |
|
|
670
|
+
| `event_ingest_token` | `str \| None` | `None` | Optional dedicated token for telemetry ingest. Falls back to `THOTH_EVENT_INGEST_TOKEN`. |
|
|
671
|
+
| `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`. |
|
|
672
|
+
| `step_up_timeout_minutes` | `int` | `15` | Timeout for step-up approval. |
|
|
673
|
+
| `step_up_poll_interval_seconds` | `int` | `5` | Polling interval for step-up hold status. |
|
|
674
|
+
| `session_intent` | `str \| None` | `None` | Session purpose used by intent-scoped compliance packs. |
|
|
675
|
+
| `purpose` | `str \| None` | `None` | Business-purpose label propagated to policy checks and telemetry. |
|
|
676
|
+
| `data_classification` | `str \| None` | `None` | Sensitivity label propagated to policy checks and telemetry. |
|
|
677
|
+
| `task_context` | `dict[str, Any]` | `{}` | Delegation/task metadata included in enforcement payload + events. |
|
|
678
|
+
| `environment` | `str` | `"prod"` | Environment selector included in `/v1/enforce` payload. |
|
|
679
|
+
| `enforcement_trace_id` | `str \| None` | `None` | Optional explicit trace ID; falls back to `session_id`. |
|
|
680
|
+
|
|
681
|
+
### Environment Variables
|
|
682
|
+
|
|
683
|
+
| Variable | Description |
|
|
684
|
+
|---|---|
|
|
685
|
+
| `THOTH_TENANT_ID` | Tenant identifier used by your application to pass `tenant_id` into SDK APIs (the SDK does not auto-read this by itself). |
|
|
686
|
+
| `THOTH_API_KEY` | API key from the Aten dashboard. Enables HTTPS event transport. Example: `thoth_live_abc123...` |
|
|
687
|
+
| `THOTH_EVENT_INGEST_TOKEN` | Optional dedicated telemetry token for `/v1/events/batch` (`X-Thoth-Event-Ingest-Token` header). |
|
|
688
|
+
| `THOTH_API_URL` | Required tenant API base URL for both event ingestion and policy checks. Example: `https://enforce.<tenant>.<apex-domain>` |
|
|
689
|
+
| `THOTH_ENVIRONMENT` | Optional environment selector (`prod`, `staging`, `dev`, etc.) sent to the enforcer payload. |
|
|
690
|
+
| `THOTH_LOG_LEVEL` | Optional SDK logger level override (`DEBUG`, `INFO`, `WARNING`, `ERROR`). If unset, SDK falls back to `LOG_LEVEL` when present. |
|
|
691
|
+
|
|
692
|
+
Resolution precedence:
|
|
693
|
+
- `THOTH_API_URL` overrides `api_url`.
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## Dashboard
|
|
698
|
+
|
|
699
|
+
View sessions, violations, step-up requests, and policy decisions in your Thoth dashboard at
|
|
700
|
+
`https://<tenant>.<apex-domain>`.
|
|
701
|
+
|
|
702
|
+
The dashboard shows:
|
|
703
|
+
- **Sessions** — per-agent session timelines with all tool calls
|
|
704
|
+
- **Violations** — blocked or escalated actions with full context
|
|
705
|
+
- **Approvals** — step-up queue for human reviewers
|
|
706
|
+
- **Policies** — view and edit the rules driving enforcement decisions
|
|
707
|
+
- **Behavioral Analytics** — drift detection and anomaly scores over time
|
|
708
|
+
|
|
709
|
+
---
|
|
710
|
+
|
|
711
|
+
## License
|
|
712
|
+
|
|
713
|
+
Copyright 2026 Aten Security, Inc. All rights reserved.
|
|
714
|
+
|