tollgate 1.0.5__tar.gz → 1.4.0__tar.gz

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.
Files changed (92) hide show
  1. tollgate-1.4.0/.gitignore +35 -0
  2. tollgate-1.4.0/ARCHITECTURE.md +579 -0
  3. tollgate-1.4.0/CHANGELOG.md +66 -0
  4. tollgate-1.4.0/FEATURES.md +837 -0
  5. tollgate-1.4.0/PKG-INFO +393 -0
  6. tollgate-1.4.0/QUICKSTART.md +473 -0
  7. tollgate-1.4.0/README.md +358 -0
  8. tollgate-1.4.0/SECURITY.md +356 -0
  9. tollgate-1.4.0/examples/security_hardened/README.md +70 -0
  10. tollgate-1.4.0/examples/security_hardened/audit.jsonl +34 -0
  11. tollgate-1.4.0/examples/security_hardened/demo.py +340 -0
  12. tollgate-1.4.0/examples/security_hardened/manifest.yaml +62 -0
  13. tollgate-1.4.0/examples/security_hardened/manifest.yaml.sig +1 -0
  14. tollgate-1.4.0/examples/security_hardened/policy.yaml +63 -0
  15. tollgate-1.4.0/examples/security_hardened/test_scenarios.yaml +123 -0
  16. {tollgate-1.0.5 → tollgate-1.4.0}/pyproject.toml +18 -2
  17. {tollgate-1.0.5 → tollgate-1.4.0}/specs/audit_event.schema.json +2 -1
  18. tollgate-1.4.0/specs/identity.schema.json +64 -0
  19. {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/__init__.py +34 -2
  20. tollgate-1.4.0/src/tollgate/anomaly_detector.py +396 -0
  21. tollgate-1.4.0/src/tollgate/audit.py +136 -0
  22. tollgate-1.4.0/src/tollgate/backends/__init__.py +37 -0
  23. tollgate-1.4.0/src/tollgate/backends/redis_store.py +411 -0
  24. tollgate-1.4.0/src/tollgate/backends/sqlite_store.py +458 -0
  25. tollgate-1.4.0/src/tollgate/circuit_breaker.py +206 -0
  26. tollgate-1.4.0/src/tollgate/context_monitor.py +292 -0
  27. {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/exceptions.py +20 -0
  28. tollgate-1.4.0/src/tollgate/manifest_signing.py +90 -0
  29. tollgate-1.4.0/src/tollgate/network_guard.py +114 -0
  30. {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/policy.py +37 -0
  31. tollgate-1.4.0/src/tollgate/policy_testing.py +360 -0
  32. tollgate-1.4.0/src/tollgate/rate_limiter.py +162 -0
  33. tollgate-1.4.0/src/tollgate/registry.py +281 -0
  34. {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/tower.py +182 -11
  35. {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/types.py +21 -1
  36. tollgate-1.4.0/src/tollgate/verification.py +81 -0
  37. tollgate-1.4.0/tests/test_defense_in_depth.py +1146 -0
  38. tollgate-1.4.0/tests/test_resilience_protection.py +1108 -0
  39. tollgate-1.4.0/tests/test_security_hardening.py +816 -0
  40. tollgate-1.0.5/.gitignore +0 -12
  41. tollgate-1.0.5/CHANGELOG.md +0 -27
  42. tollgate-1.0.5/PKG-INFO +0 -144
  43. tollgate-1.0.5/QUICKSTART.md +0 -136
  44. tollgate-1.0.5/README.md +0 -119
  45. tollgate-1.0.5/SECURITY.md +0 -29
  46. tollgate-1.0.5/specs/identity.schema.json +0 -12
  47. tollgate-1.0.5/src/tollgate/audit.py +0 -47
  48. tollgate-1.0.5/src/tollgate/registry.py +0 -58
  49. {tollgate-1.0.5 → tollgate-1.4.0}/.claude/settings.local.json +0 -0
  50. {tollgate-1.0.5 → tollgate-1.4.0}/COMPARISON.md +0 -0
  51. {tollgate-1.0.5 → tollgate-1.4.0}/CONTRIBUTING.md +0 -0
  52. {tollgate-1.0.5 → tollgate-1.4.0}/LICENSE +0 -0
  53. {tollgate-1.0.5 → tollgate-1.4.0}/Makefile +0 -0
  54. {tollgate-1.0.5 → tollgate-1.4.0}/examples/mcp_minimal/audit.jsonl +0 -0
  55. {tollgate-1.0.5 → tollgate-1.4.0}/examples/mcp_minimal/demo.py +0 -0
  56. {tollgate-1.0.5 → tollgate-1.4.0}/examples/mcp_minimal/manifest.yaml +0 -0
  57. {tollgate-1.0.5 → tollgate-1.4.0}/examples/mcp_minimal/policy.yaml +0 -0
  58. {tollgate-1.0.5 → tollgate-1.4.0}/examples/mock_tickets/README.md +0 -0
  59. {tollgate-1.0.5 → tollgate-1.4.0}/examples/mock_tickets/agent.py +0 -0
  60. {tollgate-1.0.5 → tollgate-1.4.0}/examples/mock_tickets/demo.py +0 -0
  61. {tollgate-1.0.5 → tollgate-1.4.0}/examples/mock_tickets/manifest.yaml +0 -0
  62. {tollgate-1.0.5 → tollgate-1.4.0}/examples/mock_tickets/tickets.json +0 -0
  63. {tollgate-1.0.5 → tollgate-1.4.0}/examples/mock_tickets/tools.py +0 -0
  64. {tollgate-1.0.5 → tollgate-1.4.0}/examples/strands_minimal/audit.jsonl +0 -0
  65. {tollgate-1.0.5 → tollgate-1.4.0}/examples/strands_minimal/demo.py +0 -0
  66. {tollgate-1.0.5 → tollgate-1.4.0}/examples/strands_minimal/manifest.yaml +0 -0
  67. {tollgate-1.0.5 → tollgate-1.4.0}/examples/strands_minimal/policy.yaml +0 -0
  68. {tollgate-1.0.5 → tollgate-1.4.0}/policies/default.yaml +0 -0
  69. {tollgate-1.0.5 → tollgate-1.4.0}/specs/decision.schema.json +0 -0
  70. {tollgate-1.0.5 → tollgate-1.4.0}/specs/intent.schema.json +0 -0
  71. {tollgate-1.0.5 → tollgate-1.4.0}/specs/tool_request.schema.json +0 -0
  72. {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/approvals.py +0 -0
  73. {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/grants.py +0 -0
  74. {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/helpers.py +0 -0
  75. {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/integrations/__init__.py +0 -0
  76. {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/integrations/mcp.py +0 -0
  77. {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/integrations/strands.py +0 -0
  78. {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/interceptors/__init__.py +0 -0
  79. {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/interceptors/base.py +0 -0
  80. {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/interceptors/langchain.py +0 -0
  81. {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/interceptors/openai.py +0 -0
  82. {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_adapters_v1.py +0 -0
  83. {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_audit_integrity_v1.py +0 -0
  84. {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_deferred_v1.py +0 -0
  85. {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_grants.py +0 -0
  86. {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_helpers_v1.py +0 -0
  87. {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_integrations_v1.py +0 -0
  88. {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_policy_v1.py +0 -0
  89. {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_registry_v1.py +0 -0
  90. {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_security_v1.py +0 -0
  91. {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_tower_v1.py +0 -0
  92. {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_v1_integrations.py +0 -0
@@ -0,0 +1,35 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .venv/
7
+ env/
8
+ venv/
9
+ ENV/
10
+ *.egg-info/
11
+ dist/
12
+ build/
13
+
14
+ # Testing
15
+ .pytest_cache/
16
+ .ruff_cache/
17
+ .coverage
18
+ htmlcov/
19
+
20
+ # Environment variables
21
+ .env
22
+ .env.*
23
+
24
+ # Office documents (requested)
25
+ *.docx
26
+ *.pptx
27
+
28
+ # Local demo files
29
+ examples/mock_tickets/audit.jsonl
30
+ examples/mock_tickets/tickets.json.bak
31
+
32
+ # IDEs
33
+ .vscode/
34
+ .idea/
35
+ .cursor/
@@ -0,0 +1,579 @@
1
+ # Tollgate Architecture
2
+
3
+ This document explains Tollgate's architecture — how its components fit together and where they sit in the lifecycle of an AI agent tool call.
4
+
5
+ ---
6
+
7
+ ## The Problem
8
+
9
+ When an AI agent calls a tool — reading a file, sending an email, querying a database — there is no built-in mechanism to verify **who** is calling, **why** they're calling, or whether the call should be **allowed**. Tollgate is a runtime enforcement layer that sits between the agent and the tool, making every call go through a security checkpoint.
10
+
11
+ ---
12
+
13
+ ## Core Idea: Identity + Intent + Policy
14
+
15
+ Every tool call in Tollgate is evaluated against three questions:
16
+
17
+ ```
18
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
19
+ │ WHO? │ │ WHY? │ │ WHAT? │
20
+ │ │ │ │ │ │
21
+ │ AgentContext │ │ Intent │ │ ToolRequest │
22
+ │ │ │ │ │ │
23
+ │ agent_id │ │ action │ │ tool │
24
+ │ version │ │ reason │ │ action │
25
+ │ owner │ │ confidence │ │ effect │
26
+ │ delegated_by │ │ │ │ params │
27
+ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
28
+ │ │ │
29
+ └────────────────────┼────────────────────┘
30
+
31
+
32
+ ┌─────────────────┐
33
+ │ ControlTower │
34
+ │ (Enforcer) │
35
+ └────────┬────────┘
36
+
37
+
38
+ ┌─────────────────┐
39
+ │ Decision │
40
+ │ │
41
+ │ ALLOW │ ASK │ │
42
+ │ DENY │
43
+ └─────────────────┘
44
+ ```
45
+
46
+ ---
47
+
48
+ ## The Three Core Entities
49
+
50
+ ### AgentContext — "Who is calling?"
51
+
52
+ Represents the agent making the request. Immutable and optionally signed with HMAC-SHA256.
53
+
54
+ ```python
55
+ AgentContext(
56
+ agent_id="weather-bot", # Unique identifier
57
+ version="2.1.0", # Agent version
58
+ owner="platform-team", # Responsible team
59
+ delegated_by=("orchestrator",) # Who delegated to this agent
60
+ )
61
+ ```
62
+
63
+ **Key properties:**
64
+ - `delegation_depth` — How many agents are in the delegation chain
65
+ - `is_delegated` — Whether this agent was called by another agent
66
+ - `root_agent` — The original agent that started the chain
67
+
68
+ ### Intent — "Why are they calling?"
69
+
70
+ Describes the agent's stated goal. This is the agent's claim about what it's trying to accomplish.
71
+
72
+ ```python
73
+ Intent(
74
+ action="get_weather", # What the agent wants to do
75
+ reason="User asked about London", # Why
76
+ confidence=0.95, # LLM confidence (optional)
77
+ )
78
+ ```
79
+
80
+ ### ToolRequest — "What exactly are they doing?"
81
+
82
+ The specific tool call being made. The `effect` field is the most important — it comes from the trusted Tool Registry, not the agent.
83
+
84
+ ```python
85
+ ToolRequest(
86
+ tool="api:weather", # Tool identifier
87
+ action="get", # Action on the tool
88
+ effect=Effect.READ, # READ | WRITE | DELETE | NOTIFY | UNKNOWN
89
+ resource_type="weather", # What kind of resource
90
+ params={"city": "London"}, # Parameters
91
+ manifest_version="1.0.0", # Links to trusted registry
92
+ )
93
+ ```
94
+
95
+ **Effects** determine the risk level:
96
+
97
+ | Effect | Risk | Default Policy |
98
+ |--------|------|----------------|
99
+ | `READ` | Low | Typically ALLOW |
100
+ | `WRITE` | Medium | Typically ASK |
101
+ | `DELETE` | High | Typically ASK or DENY |
102
+ | `NOTIFY` | Medium | Typically ALLOW |
103
+ | `UNKNOWN` | Highest | Always DENY |
104
+
105
+ ---
106
+
107
+ ## ControlTower — The Enforcer
108
+
109
+ The `ControlTower` is the central class. It orchestrates all security checks and makes the final decision. You configure it once with all the security layers you want, then route every tool call through it.
110
+
111
+ ```python
112
+ tower = ControlTower(
113
+ # Required
114
+ policy=YamlPolicyEvaluator("policy.yaml"),
115
+ approver=CliApprover(),
116
+ audit=JsonlAuditSink("audit.jsonl"),
117
+
118
+ # Optional security layers
119
+ registry=ToolRegistry("manifest.yaml"),
120
+ grant_store=InMemoryGrantStore(),
121
+ rate_limiter=InMemoryRateLimiter([...]),
122
+ circuit_breaker=InMemoryCircuitBreaker(...),
123
+ network_guard=NetworkGuard(...),
124
+ verify_fn=make_verifier(secret),
125
+ )
126
+ ```
127
+
128
+ ---
129
+
130
+ ## The Enforcement Pipeline
131
+
132
+ When `tower.execute_async()` is called, the request goes through these checks **in order**. Any check can halt the pipeline.
133
+
134
+ ```
135
+ ┌─────────────────────────────────────────────────────────────────┐
136
+ │ ENFORCEMENT PIPELINE │
137
+ │ │
138
+ │ ┌─────────────────────────────────────────────────────────┐ │
139
+ │ │ 1. IDENTITY VERIFICATION │ │
140
+ │ │ verify_fn(agent_ctx) → signed? not tampered? │ │
141
+ │ │ ✗ → TollgateDenied │ │
142
+ │ └─────────────────────┬───────────────────────────────────┘ │
143
+ │ │ ✓ │
144
+ │ ┌─────────────────────▼───────────────────────────────────┐ │
145
+ │ │ 2. CIRCUIT BREAKER │ │
146
+ │ │ Is this tool in OPEN state? (too many recent fails) │ │
147
+ │ │ ✗ → TollgateDenied │ │
148
+ │ └─────────────────────┬───────────────────────────────────┘ │
149
+ │ │ ✓ │
150
+ │ ┌─────────────────────▼───────────────────────────────────┐ │
151
+ │ │ 3. RATE LIMITING │ │
152
+ │ │ Sliding window check per agent/tool/effect │ │
153
+ │ │ ✗ → TollgateRateLimited (with retry_after) │ │
154
+ │ └─────────────────────┬───────────────────────────────────┘ │
155
+ │ │ ✓ │
156
+ │ ┌─────────────────────▼───────────────────────────────────┐ │
157
+ │ │ 4. POLICY EVALUATION │ │
158
+ │ │ YAML rules: effect, tool, agent, delegation, metadata │ │
159
+ │ │ → ALLOW │ ASK │ DENY │ │
160
+ │ └─────────────────────┬───────────────────────────────────┘ │
161
+ │ │ │
162
+ │ ┌─────────────────────▼───────────────────────────────────┐ │
163
+ │ │ 5. NETWORK GUARD (if not DENY) │ │
164
+ │ │ Check all URL params against global allowlist/blocklist│ │
165
+ │ │ ✗ → TollgateConstraintViolation │ │
166
+ │ └─────────────────────┬───────────────────────────────────┘ │
167
+ │ │ │
168
+ │ ┌─────────────────────▼───────────────────────────────────┐ │
169
+ │ │ 6. PARAMETER VALIDATION (if not DENY) │ │
170
+ │ │ JSON Schema: type, required, pattern, enum, range │ │
171
+ │ │ ✗ → TollgateDenied │ │
172
+ │ └─────────────────────┬───────────────────────────────────┘ │
173
+ │ │ │
174
+ │ ┌─────────────────────▼───────────────────────────────────┐ │
175
+ │ │ 7. PER-TOOL CONSTRAINTS (if not DENY) │ │
176
+ │ │ URL patterns, param value constraints │ │
177
+ │ │ ✗ → TollgateConstraintViolation │ │
178
+ │ └─────────────────────┬───────────────────────────────────┘ │
179
+ │ │ │
180
+ │ ┌─────────────────────▼───────────────────────────────────┐ │
181
+ │ │ 8. DECISION ROUTING │ │
182
+ │ │ │ │
183
+ │ │ DENY → Log + TollgateDenied │ │
184
+ │ │ │ │
185
+ │ │ ASK → Check grants first: │ │
186
+ │ │ ├─ Grant found? → Execute (skip approval) │ │
187
+ │ │ └─ No grant? → Request human approval │ │
188
+ │ │ ├─ Approved → Execute │ │
189
+ │ │ ├─ Denied → TollgateApprovalDenied│
190
+ │ │ └─ Deferred → TollgateDeferred│ │
191
+ │ │ │ │
192
+ │ │ ALLOW → Execute │ │
193
+ │ └─────────────────────┬───────────────────────────────────┘ │
194
+ │ │ │
195
+ │ ┌─────────────────────▼───────────────────────────────────┐ │
196
+ │ │ 9. TOOL EXECUTION │ │
197
+ │ │ Run the actual tool function │ │
198
+ │ │ ├─ Success → Record in circuit breaker │ │
199
+ │ │ └─ Failure → Record failure in circuit breaker │ │
200
+ │ └─────────────────────┬───────────────────────────────────┘ │
201
+ │ │ │
202
+ │ ┌─────────────────────▼───────────────────────────────────┐ │
203
+ │ │ 10. AUDIT LOGGING │ │
204
+ │ │ Every outcome is logged with full context: │ │
205
+ │ │ correlation_id, request_hash, decision, outcome, │ │
206
+ │ │ agent, intent, tool_request, timestamps │ │
207
+ │ └─────────────────────────────────────────────────────────┘ │
208
+ │ │
209
+ └─────────────────────────────────────────────────────────────────┘
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Component Map
215
+
216
+ Here's how every component relates to the pipeline:
217
+
218
+ ```
219
+ ┌────────────────────────────────────────────────────────────────────────┐
220
+ │ CONFIGURATION │
221
+ │ │
222
+ │ manifest.yaml policy.yaml secrets │
223
+ │ ┌────────────┐ ┌────────────┐ ┌──────────────────────┐ │
224
+ │ │ Tools: │ │ Rules: │ │ Agent signing key │ │
225
+ │ │ effect │ │ effect │ │ Manifest signing key │ │
226
+ │ │ schema │ │ decision │ └──────────────────────┘ │
227
+ │ │ constraints│ │ delegation│ │
228
+ │ └─────┬──────┘ └─────┬──────┘ │
229
+ │ │ │ │
230
+ │ ▼ ▼ │
231
+ │ ┌────────────┐ ┌──────────────────┐ │
232
+ │ │ToolRegistry│ │YamlPolicyEvaluator│ │
233
+ │ └────────────┘ └──────────────────┘ │
234
+ │ │
235
+ ├────────────────────────────────────────────────────────────────────────┤
236
+ │ RUNTIME ENFORCEMENT │
237
+ │ │
238
+ │ ┌──────────────────────────────────────────────────────────────┐ │
239
+ │ │ ControlTower │ │
240
+ │ │ │ │
241
+ │ │ Pre-execution guards: │ │
242
+ │ │ ┌──────────┐ ┌───────────────┐ ┌──────────────┐ │ │
243
+ │ │ │verify_fn │ │CircuitBreaker │ │ RateLimiter │ │ │
244
+ │ │ └──────────┘ └───────────────┘ └──────────────┘ │ │
245
+ │ │ │ │
246
+ │ │ Policy + validation: │ │
247
+ │ │ ┌─────────────┐ ┌────────────┐ ┌──────────────┐ │ │
248
+ │ │ │PolicyEvaluator│ │NetworkGuard│ │ToolRegistry │ │ │
249
+ │ │ └─────────────┘ └────────────┘ └──────────────┘ │ │
250
+ │ │ │ │
251
+ │ │ Approval flow: │ │
252
+ │ │ ┌────────────┐ ┌──────────┐ │ │
253
+ │ │ │ GrantStore │ │ Approver │ │ │
254
+ │ │ └────────────┘ └──────────┘ │ │
255
+ │ └──────────────────────────────────────────────────────────────┘ │
256
+ │ │
257
+ ├────────────────────────────────────────────────────────────────────────┤
258
+ │ POST-EXECUTION │
259
+ │ │
260
+ │ ┌──────────────────────────────────────────────────────────────┐ │
261
+ │ │ Audit Pipeline │ │
262
+ │ │ ┌────────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
263
+ │ │ │ JsonlAuditSink │ │WebhookAudit │ │AnomalyDetector │ │ │
264
+ │ │ │ (file logging) │ │(alerts) │ │(pattern analysis)│ │ │
265
+ │ │ └────────────────┘ └──────────────┘ └─────────────────┘ │ │
266
+ │ │ ↑ ↑ ↑ │ │
267
+ │ │ └──────────────────┼─────────────────┘ │ │
268
+ │ │ ┌────────┴────────┐ │ │
269
+ │ │ │CompositeAuditSink│ │ │
270
+ │ │ └─────────────────┘ │ │
271
+ │ └──────────────────────────────────────────────────────────────┘ │
272
+ │ │
273
+ │ ┌──────────────────────────────────────────────────────────────┐ │
274
+ │ │ Monitoring (out-of-band) │ │
275
+ │ │ ┌────────────────────────┐ ┌──────────────────────────┐ │ │
276
+ │ │ │ContextIntegrityMonitor │ │ PolicyTestRunner (CI) │ │ │
277
+ │ │ │(memory poisoning check)│ │ (policy regression test) │ │ │
278
+ │ │ └────────────────────────┘ └──────────────────────────┘ │ │
279
+ │ └──────────────────────────────────────────────────────────────┘ │
280
+ │ │
281
+ ├────────────────────────────────────────────────────────────────────────┤
282
+ │ PERSISTENT STORAGE │
283
+ │ │
284
+ │ In-Memory (dev/test) SQLite (single-process) │
285
+ │ ┌───────────────────┐ ┌──────────────────┐ │
286
+ │ │InMemoryGrantStore │ │SQLiteGrantStore │ │
287
+ │ │InMemoryApprovalSt.│ │SQLiteApprovalStore│ │
288
+ │ └───────────────────┘ └──────────────────┘ │
289
+ │ │
290
+ │ Redis (distributed) │
291
+ │ ┌──────────────────┐ │
292
+ │ │RedisGrantStore │ │
293
+ │ │RedisApprovalStore│ │
294
+ │ └──────────────────┘ │
295
+ └────────────────────────────────────────────────────────────────────────┘
296
+ ```
297
+
298
+ ---
299
+
300
+ ## Where Tollgate Fits in the Agent Lifecycle
301
+
302
+ ```
303
+ ┌─────────────────────────────────────────────────────────────────┐
304
+ │ AI AGENT LIFECYCLE │
305
+ │ │
306
+ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
307
+ │ │ User │───▶│ LLM │───▶│ Agent │───▶│ Tool │ │
308
+ │ │ Input │ │ Reasoning│ │ Decision │ │ Execution│ │
309
+ │ └──────────┘ └──────────┘ └────┬─────┘ └──────────┘ │
310
+ │ │ │
311
+ │ │ "I need to call │
312
+ │ │ api:weather.get" │
313
+ │ │ │
314
+ │ ┌───────▼───────┐ │
315
+ │ │ │ │
316
+ │ │ TOLLGATE │ │
317
+ │ │ │ │
318
+ │ │ WHO + WHY + │ │
319
+ │ │ WHAT → OK? │ │
320
+ │ │ │ │
321
+ │ └───────┬───────┘ │
322
+ │ │ │
323
+ │ ┌────────┼────────┐ │
324
+ │ │ │ │ │
325
+ │ ALLOW ASK DENY │
326
+ │ │ │ │ │
327
+ │ ▼ ▼ ▼ │
328
+ │ Execute Human Block │
329
+ │ Tool Review + Log │
330
+ │ │
331
+ └─────────────────────────────────────────────────────────────────┘
332
+ ```
333
+
334
+ Tollgate operates at the **tool-call boundary** — the moment an agent decides to call an external tool. It does not interfere with the LLM's reasoning or the agent's planning. It only enforces rules at the point of action.
335
+
336
+ ### Multi-Agent Scenarios
337
+
338
+ In multi-agent systems, Tollgate tracks the delegation chain:
339
+
340
+ ```
341
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
342
+ │ Orchestrator │────▶│ Router │────▶│ Worker │
343
+ │ │ │ │ │ │
344
+ │ agent_id: │ │ agent_id: │ │ agent_id: │
345
+ │ "orch" │ │ "router" │ │ "worker" │
346
+ └──────────────┘ └──────────────┘ │ │
347
+ │ delegated_by:│
348
+ │ ("orch", │
349
+ │ "router") │
350
+ └──────┬───────┘
351
+
352
+ ┌──────▼───────┐
353
+ │ ControlTower │
354
+ │ │
355
+ │ Checks: │
356
+ │ • depth ≤ 2? │
357
+ │ • "orch" in │
358
+ │ allowed? │
359
+ │ • "router" in │
360
+ │ blocked? │
361
+ └───────────────┘
362
+ ```
363
+
364
+ ---
365
+
366
+ ## Policy System
367
+
368
+ Policies are YAML files with ordered rules. First match wins.
369
+
370
+ ```yaml
371
+ rules:
372
+ - id: allow_reads
373
+ effect: read # Match by effect
374
+ decision: ALLOW
375
+
376
+ - id: trusted_delegation
377
+ agent:
378
+ allowed_delegators: # Only these can delegate
379
+ - "orchestrator"
380
+ max_delegation_depth: 2 # Chain length limit
381
+ effect: write
382
+ decision: ALLOW
383
+
384
+ - id: ask_writes
385
+ effect: write
386
+ decision: ASK # Human must approve
387
+
388
+ - id: deny_default
389
+ decision: DENY # Catch-all: fail closed
390
+ ```
391
+
392
+ **Matching capabilities:**
393
+ - `effect` — READ, WRITE, DELETE, NOTIFY
394
+ - `tool`, `action`, `resource_type` — Exact match
395
+ - `agent.agent_id`, `agent.version` — Agent attributes
396
+ - `agent.allowed_delegators` — Delegation trust list
397
+ - `agent.blocked_delegators` — Delegation block list
398
+ - `agent.max_delegation_depth` — Chain depth limit
399
+ - `agent.deny_delegated` — Skip rule for delegated agents
400
+ - `when` — Metadata conditions with operators (`>=`, `<=`, `==`, `!=`)
401
+
402
+ ---
403
+
404
+ ## Approvals & Grants
405
+
406
+ When a policy returns `ASK`, two mechanisms can resolve it:
407
+
408
+ ### Grants (Pre-approvals)
409
+
410
+ Grants are pre-authorized permissions that bypass the human approval step. They support wildcards and expiration.
411
+
412
+ ```python
413
+ Grant(
414
+ agent_id="batch-agent", # or None for any agent
415
+ effect=Effect.WRITE, # or None for any effect
416
+ tool="api:*", # Prefix matching
417
+ expires_at=time.time()+3600 # 1 hour
418
+ )
419
+ ```
420
+
421
+ ### Approvers
422
+
423
+ If no grant matches, an `Approver` handles the request:
424
+
425
+ | Approver | Use Case |
426
+ |----------|----------|
427
+ | `AutoApprover` | Testing — auto-approves READs, denies everything else |
428
+ | `CliApprover` | Development — interactive terminal prompt |
429
+ | `AsyncQueueApprover` | Production — external system decides via API |
430
+
431
+ ---
432
+
433
+ ## Audit Pipeline
434
+
435
+ Every decision is logged as a structured `AuditEvent`:
436
+
437
+ ```
438
+ ┌─────────────────────────────────────────────┐
439
+ │ AuditEvent │
440
+ │ │
441
+ │ timestamp, correlation_id, request_hash │
442
+ │ agent, intent, tool_request │
443
+ │ decision (ALLOW/ASK/DENY) │
444
+ │ outcome (EXECUTED/BLOCKED/FAILED) │
445
+ │ grant_id (if grant was used) │
446
+ │ approval_id (if approval was requested) │
447
+ │ result_summary (truncated) │
448
+ │ schema_version: "1.0" │
449
+ └──────────────────┬──────────────────────────┘
450
+
451
+ ┌────────┼────────┐
452
+ ▼ ▼ ▼
453
+ ┌──────────┐ ┌──────┐ ┌─────────────────┐
454
+ │ JSONL │ │Webhook│ │AnomalyDetector │
455
+ │ file │ │alerts │ │ │
456
+ │ │ │(BLOCK,│ │ rate_spike │
457
+ │ append- │ │ DENY, │ │ error_burst │
458
+ │ only log │ │ FAIL) │ │ deny_surge │
459
+ └──────────┘ └──────┘ │ unusual_tool │
460
+ └─────────────────┘
461
+ ```
462
+
463
+ **Sensitive parameters** (password, token, secret, api_key) are **redacted** before logging, not before enforcement.
464
+
465
+ ---
466
+
467
+ ## Monitoring Components
468
+
469
+ These components operate **outside** the main enforcement pipeline:
470
+
471
+ ### ContextIntegrityMonitor
472
+
473
+ Detects unauthorized changes to agent context between turns. Takes SHA-256 snapshots and verifies immutable fields haven't changed.
474
+
475
+ ```
476
+ Turn 1: snapshot(context) → { system_prompt: sha256("..."), ... }
477
+ Turn 2: verify(context) → ✓ Valid
478
+ Turn 3: verify(context) → ✗ security_level changed! ALERT
479
+ ```
480
+
481
+ ### AnomalyDetector
482
+
483
+ Plugs into the audit pipeline as an `AuditSink`. Analyzes patterns using z-score statistics over sliding windows.
484
+
485
+ | Alert Type | What It Detects |
486
+ |------------|-----------------|
487
+ | `rate_spike` | Call frequency suddenly increases |
488
+ | `error_burst` | Sudden increase in tool failures |
489
+ | `deny_surge` | Unusual number of policy denials |
490
+ | `unusual_tool` | Agent calling a tool it never used before |
491
+
492
+ ### PolicyTestRunner
493
+
494
+ Runs declarative test scenarios against policies in CI. Catches regressions before deployment.
495
+
496
+ ```bash
497
+ tollgate test-policy policy.yaml --scenarios test_scenarios.yaml
498
+ # Exit code 0 = all pass, 1 = failures
499
+ ```
500
+
501
+ ---
502
+
503
+ ## Framework Integrations
504
+
505
+ Tollgate integrates with agent frameworks by wrapping their tool-calling mechanisms:
506
+
507
+ ```
508
+ ┌─────────────────────────────────────────────────────────────┐
509
+ │ FRAMEWORK ADAPTERS │
510
+ │ │
511
+ │ ┌──────────────────┐ ┌──────────────────┐ │
512
+ │ │ MCP │ │ Strands │ │
513
+ │ │ │ │ │ │
514
+ │ │ TollgateMCPClient│ │ guard_tools() │ │
515
+ │ │ wraps base client│ │ wraps tool list │ │
516
+ │ └────────┬─────────┘ └────────┬─────────┘ │
517
+ │ │ │ │
518
+ │ └───────────┬───────────┘ │
519
+ │ ▼ │
520
+ │ ┌─────────────────┐ │
521
+ │ │ ControlTower │ │
522
+ │ │ .execute_async │ │
523
+ │ └─────────────────┘ │
524
+ │ │
525
+ │ ┌──────────────────┐ ┌──────────────────┐ │
526
+ │ │ LangChain │ │ OpenAI │ │
527
+ │ │ │ │ │ │
528
+ │ │ guard_tools() │ │ OpenAIToolRunner │ │
529
+ │ │ wraps LC tools │ │ wraps tool_calls│ │
530
+ │ └──────────────────┘ └──────────────────┘ │
531
+ └─────────────────────────────────────────────────────────────┘
532
+ ```
533
+
534
+ Each adapter:
535
+ 1. Intercepts the tool call
536
+ 2. Resolves tool name to registry format (e.g., `mcp:server.tool`)
537
+ 3. Builds `AgentContext`, `Intent`, `ToolRequest`
538
+ 4. Routes through `ControlTower.execute_async()`
539
+ 5. Returns result or raises Tollgate exception
540
+
541
+ ---
542
+
543
+ ## Exception Hierarchy
544
+
545
+ ```
546
+ TollgateError (base)
547
+ ├── TollgateDenied — Policy says no
548
+ ├── TollgateApprovalDenied — Human said no (or timeout)
549
+ ├── TollgateDeferred — Approval pending (async)
550
+ ├── TollgateRateLimited — Too many calls (includes retry_after)
551
+ └── TollgateConstraintViolation — Parameter or URL constraint failed
552
+ ```
553
+
554
+ Every exception carries a descriptive `reason` so the agent (or operator) understands what went wrong.
555
+
556
+ ---
557
+
558
+ ## Storage Backends
559
+
560
+ | Backend | Best For | Dependencies | Features |
561
+ |---------|----------|-------------|----------|
562
+ | `InMemory*` | Testing, development | None | Fast, no persistence |
563
+ | `SQLite*` | Single-process production | None (stdlib) | WAL mode, persistent |
564
+ | `Redis*` | Distributed production | `redis[hiredis]` | TTL, pub/sub, multi-host |
565
+
566
+ All backends implement the same `GrantStore` and `ApprovalStore` protocols, so you can swap them without changing any other code.
567
+
568
+ ---
569
+
570
+ ## Design Principles
571
+
572
+ 1. **Fail closed** — Unknown effects, missing rules, and unverified agents all result in DENY
573
+ 2. **Developer controls metadata** — Tool effects come from the registry, not from agents
574
+ 3. **Deterministic decisions** — No LLM in the enforcement loop; policies are pure logic
575
+ 4. **Defense in depth** — Multiple independent layers; no single point of failure
576
+ 5. **Async-first** — All enforcement is async; sync wrapper available for convenience
577
+ 6. **Protocol-based** — Every component is a pluggable protocol; swap implementations freely
578
+ 7. **Audit everything** — Every outcome (allow, deny, execute, fail) is logged with full context
579
+ 8. **Least agency** — Grant only the minimum autonomy needed for safe, bounded tasks