agentgate-pdp 0.2.0__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.
agentgate/__init__.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgentGate SDK — drop-in trust authorization for AI agents.
|
|
3
|
+
|
|
4
|
+
Quickstart:
|
|
5
|
+
|
|
6
|
+
from agentgate import AgentGate
|
|
7
|
+
|
|
8
|
+
gate = AgentGate("http://localhost:8000")
|
|
9
|
+
gate.register("my_agent", "MyBot", "Summarize PDF reports", ["/reports/*"], ["read"])
|
|
10
|
+
|
|
11
|
+
# Authorize before any action
|
|
12
|
+
result = gate.authorize("read", "/reports/q3.pdf")
|
|
13
|
+
|
|
14
|
+
# Decorator style
|
|
15
|
+
@gate.guard("read", resource_arg="path")
|
|
16
|
+
def read_document(path: str) -> str:
|
|
17
|
+
return open(path).read()
|
|
18
|
+
|
|
19
|
+
# Scan for prompt injection
|
|
20
|
+
scan = gate.scan(email_body)
|
|
21
|
+
if scan["level"] == "injection":
|
|
22
|
+
raise ValueError("Injection detected — content blocked")
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
__version__ = "0.2.0"
|
|
26
|
+
|
|
27
|
+
import uuid
|
|
28
|
+
import httpx
|
|
29
|
+
from functools import wraps
|
|
30
|
+
|
|
31
|
+
from agentgate.exceptions import AgentGateDenied, AgentGateEscalated, AgentGateNotRegistered
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AgentGate:
|
|
35
|
+
"""
|
|
36
|
+
Client for the AgentGate Policy Decision Point.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
url: AgentGate server URL (e.g. "http://localhost:8000")
|
|
40
|
+
raise_on_deny: If True (default), .authorize() raises AgentGateDenied on DENY
|
|
41
|
+
raise_on_escalate: If True, .authorize() raises AgentGateEscalated on ESCALATE
|
|
42
|
+
timeout: HTTP request timeout in seconds (default 30)
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
url: str,
|
|
48
|
+
api_key: str = "",
|
|
49
|
+
raise_on_deny: bool = True,
|
|
50
|
+
raise_on_escalate: bool = False,
|
|
51
|
+
timeout: float = 30.0,
|
|
52
|
+
):
|
|
53
|
+
self.url = url.rstrip("/")
|
|
54
|
+
self._headers = {"X-API-Key": api_key} if api_key else {}
|
|
55
|
+
self.raise_on_deny = raise_on_deny
|
|
56
|
+
self.raise_on_escalate = raise_on_escalate
|
|
57
|
+
self.timeout = timeout
|
|
58
|
+
self._agent_id: str | None = None
|
|
59
|
+
self._token: str | None = None
|
|
60
|
+
|
|
61
|
+
# ── Registration ─────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
def register(
|
|
64
|
+
self,
|
|
65
|
+
agent_id: str,
|
|
66
|
+
name: str,
|
|
67
|
+
declared_purpose: str,
|
|
68
|
+
authorized_resources: list[str],
|
|
69
|
+
authorized_actions: list[str],
|
|
70
|
+
delegation_depth: int = 0,
|
|
71
|
+
delegated_by: str | None = None,
|
|
72
|
+
scope_at_delegation: list[str] | None = None,
|
|
73
|
+
processes_external_content: bool = False,
|
|
74
|
+
) -> "AgentGate":
|
|
75
|
+
"""Register this agent with AgentGate. Returns self for chaining."""
|
|
76
|
+
payload: dict = {
|
|
77
|
+
"agent_id": agent_id,
|
|
78
|
+
"name": name,
|
|
79
|
+
"declared_purpose": declared_purpose,
|
|
80
|
+
"authorized_resources": authorized_resources,
|
|
81
|
+
"authorized_actions": authorized_actions,
|
|
82
|
+
"delegation_depth": delegation_depth,
|
|
83
|
+
"processes_external_content": processes_external_content,
|
|
84
|
+
}
|
|
85
|
+
if delegated_by:
|
|
86
|
+
payload["delegated_by"] = delegated_by
|
|
87
|
+
if scope_at_delegation:
|
|
88
|
+
payload["scope_at_delegation"] = scope_at_delegation
|
|
89
|
+
|
|
90
|
+
r = httpx.post(f"{self.url}/agents/register", json=payload, headers=self._headers, timeout=self.timeout)
|
|
91
|
+
r.raise_for_status()
|
|
92
|
+
self._agent_id = agent_id
|
|
93
|
+
self._token = r.json()["token"]
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
# ── Authorization ─────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
def authorize(self, action: str, resource: str, justification: str = "") -> dict:
|
|
99
|
+
"""Request authorization before performing an action.
|
|
100
|
+
|
|
101
|
+
Returns dict with decision, trust_breakdown, explanation, attack_flags.
|
|
102
|
+
Raises AgentGateDenied on DENY (if raise_on_deny=True).
|
|
103
|
+
"""
|
|
104
|
+
if not self._agent_id or not self._token:
|
|
105
|
+
raise AgentGateNotRegistered("Call gate.register(...) before gate.authorize()")
|
|
106
|
+
|
|
107
|
+
r = httpx.post(
|
|
108
|
+
f"{self.url}/authorize",
|
|
109
|
+
headers=self._headers,
|
|
110
|
+
json={
|
|
111
|
+
"agent_id": self._agent_id,
|
|
112
|
+
"token": self._token,
|
|
113
|
+
"action": action,
|
|
114
|
+
"resource": resource,
|
|
115
|
+
"justification": justification,
|
|
116
|
+
"request_id": str(uuid.uuid4()),
|
|
117
|
+
},
|
|
118
|
+
timeout=self.timeout,
|
|
119
|
+
)
|
|
120
|
+
r.raise_for_status()
|
|
121
|
+
result = r.json()
|
|
122
|
+
decision = result["decision"]
|
|
123
|
+
|
|
124
|
+
if decision == "DENY" and self.raise_on_deny:
|
|
125
|
+
raise AgentGateDenied(action, resource, result.get("explanation", ""))
|
|
126
|
+
if decision == "ESCALATE" and self.raise_on_escalate:
|
|
127
|
+
raise AgentGateEscalated(action, resource, result.get("explanation", ""))
|
|
128
|
+
|
|
129
|
+
return result
|
|
130
|
+
|
|
131
|
+
# ── Content scan ──────────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
def scan(self, content: str) -> dict:
|
|
134
|
+
"""Scan document/email content for prompt injection before processing.
|
|
135
|
+
|
|
136
|
+
Returns dict with level ("clean"/"suspicious"/"injection"), confidence, evidence.
|
|
137
|
+
"""
|
|
138
|
+
if not self._agent_id:
|
|
139
|
+
raise AgentGateNotRegistered("Call gate.register(...) before gate.scan()")
|
|
140
|
+
|
|
141
|
+
r = httpx.post(
|
|
142
|
+
f"{self.url}/scan",
|
|
143
|
+
headers=self._headers,
|
|
144
|
+
json={"agent_id": self._agent_id, "content": content},
|
|
145
|
+
timeout=self.timeout,
|
|
146
|
+
)
|
|
147
|
+
r.raise_for_status()
|
|
148
|
+
return r.json()
|
|
149
|
+
|
|
150
|
+
# ── Helpers ───────────────────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
def check(self, action: str, resource: str, justification: str = "") -> bool:
|
|
153
|
+
"""Non-raising boolean check. Returns False on DENY, True otherwise."""
|
|
154
|
+
try:
|
|
155
|
+
result = self.authorize(action, resource, justification)
|
|
156
|
+
return result["decision"] != "DENY"
|
|
157
|
+
except AgentGateDenied:
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
def guard(self, action: str, resource_arg: str = "resource"):
|
|
161
|
+
"""Decorator — authorizes before the wrapped function runs.
|
|
162
|
+
|
|
163
|
+
@gate.guard("read", resource_arg="path")
|
|
164
|
+
def read_file(path: str) -> str:
|
|
165
|
+
return open(path).read()
|
|
166
|
+
"""
|
|
167
|
+
def decorator(func):
|
|
168
|
+
@wraps(func)
|
|
169
|
+
def wrapper(*args, **kwargs):
|
|
170
|
+
resource = kwargs.get(resource_arg) or (args[0] if args else "unknown")
|
|
171
|
+
self.authorize(action, str(resource), f"Calling {func.__name__}")
|
|
172
|
+
return func(*args, **kwargs)
|
|
173
|
+
return wrapper
|
|
174
|
+
return decorator
|
|
175
|
+
|
|
176
|
+
def operation(self, action: str, resource: str, justification: str = ""):
|
|
177
|
+
"""Context manager — authorizes on enter.
|
|
178
|
+
|
|
179
|
+
with gate.operation("delete", "/confidential/salary.xlsx"):
|
|
180
|
+
delete_file(...)
|
|
181
|
+
"""
|
|
182
|
+
return _AuthorizedOperation(self, action, resource, justification)
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def agent_id(self) -> str | None:
|
|
186
|
+
return self._agent_id
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def token(self) -> str | None:
|
|
190
|
+
return self._token
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class _AuthorizedOperation:
|
|
194
|
+
def __init__(self, gate: AgentGate, action: str, resource: str, justification: str):
|
|
195
|
+
self._gate = gate
|
|
196
|
+
self._action = action
|
|
197
|
+
self._resource = resource
|
|
198
|
+
self._justification = justification
|
|
199
|
+
|
|
200
|
+
def __enter__(self):
|
|
201
|
+
self._gate.authorize(self._action, self._resource, self._justification)
|
|
202
|
+
return self
|
|
203
|
+
|
|
204
|
+
def __exit__(self, *_):
|
|
205
|
+
pass
|
agentgate/exceptions.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class AgentGateDenied(Exception):
|
|
2
|
+
"""Raised when AgentGate returns a hard DENY decision."""
|
|
3
|
+
def __init__(self, action: str, resource: str, explanation: str = ""):
|
|
4
|
+
self.action = action
|
|
5
|
+
self.resource = resource
|
|
6
|
+
self.explanation = explanation
|
|
7
|
+
super().__init__(f"AgentGate DENIED {action} on {resource}: {explanation}")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AgentGateEscalated(Exception):
|
|
11
|
+
"""Raised when AgentGate returns ESCALATE and strict mode is on."""
|
|
12
|
+
def __init__(self, action: str, resource: str, explanation: str = ""):
|
|
13
|
+
self.action = action
|
|
14
|
+
self.resource = resource
|
|
15
|
+
self.explanation = explanation
|
|
16
|
+
super().__init__(f"AgentGate ESCALATED {action} on {resource}: {explanation}")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AgentGateNotRegistered(Exception):
|
|
20
|
+
"""Raised when .authorize() is called before .register()."""
|
|
21
|
+
pass
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentgate-pdp
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Context-aware trust authorization for agentic AI systems
|
|
5
|
+
Author-email: AgentGate <olame109@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/agentgate/agentgate
|
|
8
|
+
Project-URL: Documentation, https://agentgate.dev/docs
|
|
9
|
+
Keywords: ai,agents,security,authorization,llm,langchain,policy,trust,pdp,agentic
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Security
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: httpx>=0.27.0
|
|
21
|
+
Provides-Extra: langchain
|
|
22
|
+
Requires-Dist: langchain-core>=0.3.0; extra == "langchain"
|
|
23
|
+
Requires-Dist: langchain-anthropic>=0.2.0; extra == "langchain"
|
|
24
|
+
Requires-Dist: langgraph>=0.2.0; extra == "langchain"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
28
|
+
|
|
29
|
+
# AgentGate
|
|
30
|
+
|
|
31
|
+
**A Policy Decision Point (PDP) for AI agents. Every action evaluated before it executes.**
|
|
32
|
+
|
|
33
|
+
[](https://pypi.org/project/agentgate/)
|
|
34
|
+
[](https://www.python.org/)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
AI agents make decisions autonomously. AgentGate makes sure those decisions are **authorized**.
|
|
40
|
+
|
|
41
|
+
It sits between your agent and its tools. Before any action runs — reading a file, calling an API, writing to a database — AgentGate checks it against identity, scope, purpose, and behavioral context. The response is `PERMIT`, `ESCALATE`, or `DENY` in milliseconds.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Agent wants to delete /confidential/salary.xlsx
|
|
45
|
+
↓
|
|
46
|
+
AgentGate PDP
|
|
47
|
+
┌──────────────────────────────┐
|
|
48
|
+
│ Identity check ✓ │
|
|
49
|
+
│ Scope check ✗ (!) │
|
|
50
|
+
│ Purpose alignment ✗ │
|
|
51
|
+
│ Behavioral check ✓ │
|
|
52
|
+
│ │
|
|
53
|
+
│ Trust score: 18/100 │
|
|
54
|
+
│ Decision: DENY │
|
|
55
|
+
└──────────────────────────────┘
|
|
56
|
+
↓
|
|
57
|
+
Action never executes
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install agentgate
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Quickstart (3 lines)
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from agentgate import AgentGate
|
|
70
|
+
|
|
71
|
+
gate = AgentGate("http://localhost:8000", api_key="your-key")
|
|
72
|
+
gate.register("my_agent", "ReportBot", "Summarize quarterly reports",
|
|
73
|
+
authorized_resources=["/reports/*"], authorized_actions=["read"])
|
|
74
|
+
|
|
75
|
+
result = gate.authorize("read", "/reports/q3.pdf")
|
|
76
|
+
# {"decision": "PERMIT", "trust_breakdown": {...}, "explanation": "..."}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## LangChain — drop-in enforcement
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install agentgate[langchain]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from agentgate.langchain import AgentGateToolkit
|
|
87
|
+
|
|
88
|
+
toolkit = AgentGateToolkit(
|
|
89
|
+
agentgate_url="http://localhost:8000",
|
|
90
|
+
api_key="your-key",
|
|
91
|
+
agent_id="report_agent",
|
|
92
|
+
name="ReportBot",
|
|
93
|
+
declared_purpose="Summarize quarterly business reports",
|
|
94
|
+
authorized_resources=["/reports/*"],
|
|
95
|
+
authorized_actions=["read"],
|
|
96
|
+
processes_external_content=True, # enables prompt injection scanning
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
safe_tools = toolkit.wrap([read_document, list_documents, send_email])
|
|
100
|
+
agent = create_react_agent(llm, safe_tools)
|
|
101
|
+
# Every tool call now goes through AgentGate before executing
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## What gets caught
|
|
105
|
+
|
|
106
|
+
| Threat | How AgentGate stops it |
|
|
107
|
+
|--------|------------------------|
|
|
108
|
+
| Agent reads `/confidential/salary.xlsx` (out of scope) | DENY — `RESOURCE_OUT_OF_SCOPE` |
|
|
109
|
+
| Agent tries `delete` (not in authorized actions) | DENY — `UNAUTHORIZED_ACTION` |
|
|
110
|
+
| Child agent exceeds parent's delegation scope | DENY — `CHAIN_SCOPE_VIOLATION` |
|
|
111
|
+
| Agent makes 80 requests/min (velocity attack) | DENY — `CRITICAL_VELOCITY` |
|
|
112
|
+
| Document contains "ignore previous instructions" | Content blocked before agent sees it |
|
|
113
|
+
| Unknown agent attempts access | DENY — `UNREGISTERED_AGENT` |
|
|
114
|
+
|
|
115
|
+
## Multi-agent delegation enforcement
|
|
116
|
+
|
|
117
|
+
AgentGate validates the full delegation chain. A child agent cannot exceed its parent's scope — enforced at both registration and authorization time.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
# Orchestrator registers with full scope
|
|
121
|
+
gate.register("orchestrator", "Orchestrator", "Manage document workflow",
|
|
122
|
+
authorized_resources=["/documents/*"], authorized_actions=["read", "write"])
|
|
123
|
+
|
|
124
|
+
# Analyst is delegated a subset — enforced at registration
|
|
125
|
+
resp = httpx.post(f"{url}/agents/delegate", json={
|
|
126
|
+
"parent_agent_id": "orchestrator",
|
|
127
|
+
"parent_token": orchestrator_token,
|
|
128
|
+
"child_agent_id": "analyst",
|
|
129
|
+
"child_resources": ["/documents/public/*"], # subset of parent
|
|
130
|
+
"child_actions": ["read"], # subset of parent
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
# Analyst tries to access /confidential/ — blocked at authorization time
|
|
134
|
+
# {"decision": "DENY", "attack_flags": ["CHAIN_SCOPE_VIOLATION"]}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Natural language policies
|
|
138
|
+
|
|
139
|
+
Write security rules in plain English. AgentGate converts them to enforced policies using Claude.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import httpx
|
|
143
|
+
|
|
144
|
+
# These become hard rules checked before any trust scoring
|
|
145
|
+
httpx.post(f"{url}/policies", json={"rule": "Agents must never delete files"})
|
|
146
|
+
httpx.post(f"{url}/policies", json={"rule": "No agent should read salary data outside business hours"})
|
|
147
|
+
httpx.post(f"{url}/policies", json={"rule": "Flag any access to /hr folder"})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The policy engine runs **before** the trust score — a matching DENY policy is always a hard block regardless of score.
|
|
151
|
+
|
|
152
|
+
## Human-in-the-loop approval
|
|
153
|
+
|
|
154
|
+
Mark an agent as requiring human approval for escalated decisions:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
gate.register(..., requires_human_approval=True)
|
|
158
|
+
|
|
159
|
+
result = gate.authorize("read", "/confidential/merger_details.pdf")
|
|
160
|
+
if result["decision"] == "PENDING":
|
|
161
|
+
# Agent is paused — human approves or denies via dashboard
|
|
162
|
+
# Wrapper polls automatically; auto-denies after 90 seconds
|
|
163
|
+
request_id = result["request_id"]
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Prompt injection detection
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
# Register as processing external content
|
|
170
|
+
gate.register(..., processes_external_content=True)
|
|
171
|
+
|
|
172
|
+
# Scan before passing content to the agent
|
|
173
|
+
scan = gate.scan(document_content)
|
|
174
|
+
if scan["level"] == "injection":
|
|
175
|
+
raise ValueError(f"Injection blocked: {scan['evidence']}")
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Context manager and decorator
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
# Decorator
|
|
182
|
+
@gate.guard("read", resource_arg="path")
|
|
183
|
+
def read_document(path: str) -> str:
|
|
184
|
+
return open(path).read()
|
|
185
|
+
|
|
186
|
+
# Context manager
|
|
187
|
+
with gate.operation("write", "/reports/output.pdf"):
|
|
188
|
+
write_report(data)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Run the server
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# Docker (recommended)
|
|
195
|
+
docker compose up
|
|
196
|
+
|
|
197
|
+
# Or directly
|
|
198
|
+
pip install agentgate[server]
|
|
199
|
+
python -m agentgate.server
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Dashboard at `http://localhost:8000` — live feed of every decision, trust scores, delegation tree, policy editor, and compliance report export.
|
|
203
|
+
|
|
204
|
+
## Architecture
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
Your Agent
|
|
208
|
+
|
|
|
209
|
+
AgentGate SDK (pip install agentgate)
|
|
210
|
+
| POST /authorize
|
|
211
|
+
v
|
|
212
|
+
AgentGate PDP Server
|
|
213
|
+
├── Policy Engine (NL rules → hard blocks, checked first)
|
|
214
|
+
├── Trust Scoring (identity + delegation + purpose + behavioral)
|
|
215
|
+
│ ├── Identity (token, authorized actions, resource scope)
|
|
216
|
+
│ ├── Delegation (chain depth, scope attenuation, trust decay)
|
|
217
|
+
│ ├── Purpose (semantic alignment of request to declared purpose)
|
|
218
|
+
│ └── Behavioral (per-agent baseline anomaly detection)
|
|
219
|
+
├── HITL Approval (ESCALATE → human decision → APPROVED/DENIED)
|
|
220
|
+
└── Audit Log (every decision, exportable as PDF/CSV)
|
|
221
|
+
|
|
|
222
|
+
PERMIT / ESCALATE / DENY / PENDING
|
|
223
|
+
|
|
|
224
|
+
Tool executes (or doesn't)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Configuration
|
|
228
|
+
|
|
229
|
+
Environment variables (`.env`):
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
AGENTGATE_API_KEY=your-secret-key
|
|
233
|
+
ANTHROPIC_API_KEY=sk-ant-... # for NL policy parsing + purpose scoring
|
|
234
|
+
AGENTGATE_ALERT_TOPIC=your-ntfy-topic # for push alerts (ntfy.sh)
|
|
235
|
+
AGENTGATE_PORT=8000
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
MIT — see [LICENSE](LICENSE).
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
Built at University of Ottawa · ELG5901 · 2026
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
agentgate/__init__.py,sha256=blCnZP4AWcmvcsPi6f_ywA6XGVmF0rruUvO1aTbV0oQ,7583
|
|
2
|
+
agentgate/exceptions.py,sha256=cb9IVTQm0_ZACun4uon0hXLjddXHYFgu8uYIQyNr2k8,847
|
|
3
|
+
agentgate_pdp-0.2.0.dist-info/METADATA,sha256=8WgZOVE_dVMWd89lYwlc4kRy63LVz6AuAAg12z_jjL4,8603
|
|
4
|
+
agentgate_pdp-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
5
|
+
agentgate_pdp-0.2.0.dist-info/top_level.txt,sha256=qWUC8JQwEvSXwMD-j8F0kB0LIDQhef7EPRPGeosKWb4,10
|
|
6
|
+
agentgate_pdp-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agentgate
|