Runtime enforcement layer for AI agent tool calls using Identity + Intent + Policy
Project description
tollgate
Runtime enforcement layer for AI agent tool calls using Identity + Intent + Policy.
tollgate provides a deterministic safety boundary for AI agents. It ensures every tool call is validated against a policy before execution, with support for async human-in-the-loop approvals, framework interception (MCP, Strands, LangChain, OpenAI), and structured audit logging.
[!CAUTION] Disclaimer: This project is an exploratory implementation intended for learning and discussion. It is not production-hardened and comes with no guarantees.
Architecture | Quickstart Guide | Feature Guide | Integration Comparison | Security
+-----------------+
| AI Agent |
+--------+--------+
|
+--------------v---------------+
| Tollgate ControlTower |
|------------------------------|
| 1. Identity Verification |
| 2. Circuit Breaker Check |
| 3. Rate Limiting |
| 4. Policy Evaluation |
| 5. Network Guard |
| 6. Parameter Validation |
| 7. Constraint Checking |
+--------------+---------------+
|
+--------------------+--------------------+
| | |
v v v
+--------+ +--------+ +--------+
| ALLOW | | ASK | | DENY |
+---+----+ +---+----+ +---+----+
| | |
v v v
+--------+ +-------------+ +--------+
|Execute | |Human/Grant | | Block |
| Tool | | Approval | | & Log |
+---+----+ +------+------+ +---+----+
\ | /
\ | /
+--------+--------+--------+--------+
|
v
+-----------+
| Audit Log | --> Anomaly Detection
+-----------+
Installation
pip install tollgate
# Optional: For Redis-backed persistent stores
pip install tollgate[redis]
Core Concepts
| Concept | Description |
|---|---|
| AgentContext | Who is asking? (agent_id, version, owner, delegated_by) |
| Intent | What is the goal? (action, reason, confidence) |
| ToolRequest | What tool and parameters? (tool, action, effect, params) |
| Decision | Policy result: ALLOW, ASK, or DENY |
| Grant | Pre-authorization that bypasses ASK for specific patterns |
Core Principles
- Interception-First: Enforcement at the tool execution boundary via adapters
- Safe Defaults: Unknown effects or resources default to DENY
- Trust Model: Tool metadata trusted only from developer-controlled Tool Registry
- Approval Integrity: Approvals bound to request hash with replay protection
- Async-First: Native async support with non-blocking approvals
- Audit Integrity: Every decision recorded with cryptographic context
- Defense in Depth: Multiple security layers (rate limiting, circuit breaker, network guard)
Quick Example
import asyncio
from tollgate import (
ControlTower, YamlPolicyEvaluator, ToolRegistry,
AutoApprover, JsonlAuditSink, AgentContext, Intent,
ToolRequest, Effect
)
async def main():
# Setup
policy = YamlPolicyEvaluator("policy.yaml")
registry = ToolRegistry("manifest.yaml")
audit = JsonlAuditSink("audit.jsonl")
tower = ControlTower(policy, AutoApprover(), audit, registry=registry)
# Define context
agent = AgentContext(agent_id="my-agent", version="1.0", owner="my-team")
intent = Intent(action="fetch_data", reason="User requested weather info")
request = ToolRequest(
tool="api:weather",
action="get",
resource_type="weather",
effect=Effect.READ,
params={"city": "London"},
manifest_version="1.0.0"
)
# Execute with enforcement
result = await tower.execute_async(
agent, intent, request,
lambda: fetch_weather("London") # Your tool function
)
print(result)
asyncio.run(main())
Feature Highlights
Security Hardening
| Feature | Description |
|---|---|
| Parameter Validation | JSON Schema validation for tool parameters |
| Rate Limiting | Per-agent, per-tool, per-effect rate limits |
| Agent Identity Signing | HMAC-SHA256 verification of agent contexts |
| URL Constraints | Per-tool URL allowlisting/blocklisting |
| Webhook Alerts | Real-time alerts for blocked/denied events |
| Audit Schema Versioning | Forward-compatible audit event schemas |
from tollgate import InMemoryRateLimiter, sign_agent_context, make_verifier
# Rate limiting
limiter = InMemoryRateLimiter([
{"agent_id": "*", "tool": "api:*", "max_calls": 100, "window_seconds": 60},
{"agent_id": "*", "effect": "write", "max_calls": 10, "window_seconds": 60},
])
# Agent identity signing
secret = b"your-secret-key"
signed_agent = sign_agent_context(agent, secret)
tower = ControlTower(..., verify_fn=make_verifier(secret), rate_limiter=limiter)
Resilience & Protection
| Feature | Description |
|---|---|
| Circuit Breaker | Auto-disable failing tools after threshold |
| Manifest Signing | HMAC-SHA256 integrity verification for manifests |
| NetworkGuard | Global URL policy enforcement |
| Persistent Backends | SQLite and Redis stores for grants/approvals |
from tollgate import InMemoryCircuitBreaker, NetworkGuard, sign_manifest
from tollgate.backends import SQLiteGrantStore
# Circuit breaker - opens after 5 failures, 60s cooldown
breaker = InMemoryCircuitBreaker(failure_threshold=5, cooldown_seconds=60)
# Global network policy
guard = NetworkGuard(
default="deny",
allowlist=[
{"pattern": "https://api.github.com/*"},
{"pattern": "https://api.openai.com/*"},
],
blocklist=[
{"pattern": "http://*"}, # Block insecure
]
)
# Sign your manifest at build time
sign_manifest("manifest.yaml", secret_key=b"build-secret")
# Load with signature verification
registry = ToolRegistry("manifest.yaml", signing_key=b"build-secret")
tower = ControlTower(..., circuit_breaker=breaker, network_guard=guard)
Defense in Depth
| Feature | Description |
|---|---|
| Multi-Agent Delegation | Track and control delegation chains |
| Policy Testing Framework | CI-friendly declarative policy testing |
| Context Integrity Monitor | Detect memory/context poisoning |
| Anomaly Detection | Z-score based rate spike detection |
from tollgate import (
AgentContext, PolicyTestRunner,
ContextIntegrityMonitor, AnomalyDetector
)
# Delegation chain tracking
sub_agent = AgentContext(
agent_id="sub-agent",
version="1.0",
owner="team-a",
delegated_by=("orchestrator", "router"), # Delegation chain
)
print(sub_agent.delegation_depth) # 2
print(sub_agent.root_agent) # "orchestrator"
# Policy testing (CI integration)
runner = PolicyTestRunner("policy.yaml", "test_scenarios.yaml")
results = runner.run()
assert results.all_passed # Fail CI if policy regressed
# Context integrity monitoring
monitor = ContextIntegrityMonitor()
monitor.snapshot("agent-1", "turn-1", {"system_prompt": "You are helpful..."})
# Later...
result = monitor.verify("agent-1", "turn-1", current_context)
if not result.is_valid:
print(f"Context tampered! Changed: {result.changed_fields}")
# Anomaly detection (plugs into audit pipeline)
detector = AnomalyDetector(z_score_threshold=3.0)
composite_sink = CompositeAuditSink([jsonl_sink, detector])
CLI Tools
# Test policies against scenarios (for CI)
tollgate test-policy policy.yaml --scenarios test_scenarios.yaml
Framework Integrations
Session Grants
Pre-authorize specific actions to bypass human approval:
from tollgate import Grant, InMemoryGrantStore, Effect
grant_store = InMemoryGrantStore()
tower = ControlTower(..., grant_store=grant_store)
# Issue a grant (after initial approval)
grant = Grant(
agent_id="my-agent",
effect=Effect.WRITE,
tool="mcp:*", # Wildcard: any MCP tool
action=None, # Wildcard: any action
resource_type=None,
expires_at=time.time() + 3600,
granted_by="admin",
created_at=time.time()
)
await grant_store.create_grant(grant)
MCP (Model Context Protocol)
from tollgate.integrations.mcp import TollgateMCPClient
registry = ToolRegistry("manifest.yaml")
tower = ControlTower(...)
client = TollgateMCPClient(base_client, "my_server", tower=tower, registry=registry)
await client.call_tool("read_data", {"id": 1}, agent_ctx=ctx, intent=intent)
Strands / LangChain
from tollgate.integrations.strands import guard_tools
guarded = guard_tools(my_tools, tower, registry)
await guarded[0]("input", agent_ctx=ctx, intent=intent)
Policy Example
# policy.yaml
version: "1.0"
rules:
# Allow reads for verified agents
- id: allow_reads
effect: read
decision: ALLOW
reason: "Read operations are safe"
# Block deep delegation chains
- id: block_deep_delegation
effect: write
decision: DENY
reason: "Delegation too deep"
agent:
max_delegation_depth: 2
# Require approval for writes from untrusted delegators
- id: ask_untrusted_writes
effect: write
decision: ASK
reason: "Write requires human approval"
agent:
blocked_delegators:
- "untrusted-agent"
# Default deny
- id: deny_default
decision: DENY
reason: "No matching rule"
Persistent Backends
For production deployments, use persistent stores:
# SQLite (zero dependencies, single-process)
from tollgate.backends import SQLiteGrantStore, SQLiteApprovalStore
grant_store = SQLiteGrantStore("tollgate.db")
approval_store = SQLiteApprovalStore("tollgate.db")
# Redis (multi-process, distributed)
from tollgate.backends import RedisGrantStore, RedisApprovalStore
grant_store = RedisGrantStore(redis_url="redis://localhost:6379/0")
approval_store = RedisApprovalStore(redis_url="redis://localhost:6379/0")
Development
# Install
make install
# Run Tests (188 tests)
make test
# Run Examples
python examples/mcp_minimal/demo.py
python examples/strands_minimal/demo.py
python examples/mock_tickets/demo.py
Documentation
- QUICKSTART.md - Get started in 5 minutes
- FEATURES.md - Complete feature guide
- COMPARISON.md - Integration effort comparison
- SECURITY.md - Security model and guarantees
- CHANGELOG.md - Version history
- CONTRIBUTING.md - How to contribute
License
Apache-2.0
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 tollgate-1.4.0.tar.gz.
File metadata
- Download URL: tollgate-1.4.0.tar.gz
- Upload date:
- Size: 100.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb5fb82a35e04ddc46d876796d908b5bba85b709404bae68cfb923d1ae625e6c
|
|
| MD5 |
76e7abad2c3a1ce25f7f5bfcb5971365
|
|
| BLAKE2b-256 |
5ff467cba525c13a2ae2e823cb1acf6e9e26222969d653d3e52f40e7ba676982
|
File details
Details for the file tollgate-1.4.0-py3-none-any.whl.
File metadata
- Download URL: tollgate-1.4.0-py3-none-any.whl
- Upload date:
- Size: 58.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3cc1ffc9838172f7aceeb1aaa3e9b609e9c87de75680971a11b7750f68c79cd6
|
|
| MD5 |
9e18b18bed437d8b189fe4b931fb9ee6
|
|
| BLAKE2b-256 |
6ea1125f08cbf251bdfa452108d967f362f06a471ef7f55fe025bd53ce844bc5
|