agf-sdk 0.1.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.
agf_sdk-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: agf-sdk
3
+ Version: 0.1.0
4
+ Summary: Agent Governance Foundation — Python SDK
5
+ Project-URL: Homepage, https://agentgovernancefoundation.com
6
+ Project-URL: Documentation, https://agentgovernancefoundation.com/docs
7
+ License: MIT
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: httpx>=0.27
10
+ Provides-Extra: all
11
+ Requires-Dist: crewai>=0.28; extra == 'all'
12
+ Requires-Dist: langchain-core>=0.2; extra == 'all'
13
+ Provides-Extra: crewai
14
+ Requires-Dist: crewai>=0.28; extra == 'crewai'
15
+ Provides-Extra: langchain
16
+ Requires-Dist: langchain-core>=0.2; extra == 'langchain'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # agf-sdk
20
+
21
+ Python SDK for the [Agent Governance Foundation](https://agentgovernancefoundation.com) authorization service. Enforce identity, trust, and policy controls on every action your AI agents take.
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install agf-sdk
27
+ ```
28
+
29
+ With LangChain support:
30
+
31
+ ```bash
32
+ pip install agf-sdk[langchain]
33
+ ```
34
+
35
+ With CrewAI support:
36
+
37
+ ```bash
38
+ pip install agf-sdk[crewai]
39
+ ```
40
+
41
+ ## Quick start
42
+
43
+ ```python
44
+ import os
45
+ from agf import AgentGovernance
46
+
47
+ agf = AgentGovernance(
48
+ api_key=os.environ["AGF_API_KEY"],
49
+ org_id="org_acme",
50
+ )
51
+
52
+ result = agf.authorize(
53
+ agent_id="did:agf:agt_01abc",
54
+ action="file:write",
55
+ resource="s3://corp-data/q2.csv",
56
+ )
57
+
58
+ if result.allowed:
59
+ write_file()
60
+ else:
61
+ raise PermissionError(f"Denied: {result.reason}")
62
+ ```
63
+
64
+ ## Authorization results
65
+
66
+ `authorize()` never raises for deny/review — it always returns an `AuthResult`:
67
+
68
+ | Field | Type | Description |
69
+ |---|---|---|
70
+ | `allowed` | `bool` | `True` when the PDP issued ALLOW |
71
+ | `denied` | `bool` | `True` when the PDP issued DENY |
72
+ | `review_required` | `bool` | `True` when HITL approval is needed |
73
+ | `reason` | `str` | Human-readable denial reason |
74
+ | `artifact_id` | `str` | Signed audit artifact ID |
75
+ | `risk_score` | `float` | 0.0–1.0 |
76
+ | `trust_score` | `int` | 0–100 |
77
+ | `approval_request_id` | `str` | HITL request ID (review_required only) |
78
+
79
+ ## Async client
80
+
81
+ For async frameworks (FastAPI, async Django, etc.) use `AGFClient` directly:
82
+
83
+ ```python
84
+ from agf import AGFClient, AGFDeniedError
85
+
86
+ async def handle_request():
87
+ async with AGFClient(api_key="agfk_...") as client:
88
+ try:
89
+ result = await client.decide(
90
+ action_type="file:write",
91
+ resource="s3://corp-data/q2.csv",
92
+ chain=[root_jwt, agent_jwt],
93
+ )
94
+ except AGFDeniedError as exc:
95
+ print(f"Denied — artifact: {exc.artifact_id}")
96
+ ```
97
+
98
+ ## LangChain integration
99
+
100
+ ### Authorization gate tool (recommended for most agents)
101
+
102
+ Add an authorization tool to your agent's tool list. The agent calls it before performing sensitive operations:
103
+
104
+ ```python
105
+ from agf import AgentGovernance
106
+ from langchain.agents import initialize_agent, AgentType
107
+ from langchain_openai import ChatOpenAI
108
+
109
+ agf = AgentGovernance(api_key="agfk_...", org_id="org_acme")
110
+ agf_tool = agf.langchain_tool(agent_id="did:agf:agt_01abc")
111
+
112
+ agent = initialize_agent(
113
+ tools=[agf_tool, *your_other_tools],
114
+ llm=ChatOpenAI(),
115
+ agent=AgentType.OPENAI_FUNCTIONS,
116
+ )
117
+ ```
118
+
119
+ ### Per-tool guard (enforces policy on every tool call)
120
+
121
+ Wrap individual tools so no call can bypass the policy check:
122
+
123
+ ```python
124
+ from langchain_community.tools import ShellTool
125
+ from agf.langchain import AGFGuardedTool
126
+ from agf import AGFClient
127
+
128
+ client = AGFClient(api_key="agfk_...")
129
+
130
+ guarded_shell = AGFGuardedTool(
131
+ tool=ShellTool(),
132
+ client=client,
133
+ agent_id="did:agf:my-assistant",
134
+ action_type="exec:shell",
135
+ resource="local-shell",
136
+ )
137
+ ```
138
+
139
+ ## CrewAI integration
140
+
141
+ ```python
142
+ from crewai import Agent
143
+ from crewai.tools import BaseTool as CrewBaseTool
144
+ from agf.crewai import AGFCrewAITool
145
+ from agf import AGFClient
146
+
147
+ client = AGFClient(api_key="agfk_...")
148
+
149
+ class MyDBTool(CrewBaseTool):
150
+ name: str = "database_query"
151
+ description: str = "Query the production database"
152
+
153
+ def _run(self, query: str) -> str:
154
+ return db.execute(query)
155
+
156
+ guarded = AGFCrewAITool(
157
+ tool=MyDBTool(),
158
+ client=client,
159
+ agent_id="did:agf:crew-researcher",
160
+ action_type="query:database",
161
+ resource="prod-db",
162
+ )
163
+
164
+ crew_agent = Agent(tools=[guarded], ...)
165
+ ```
166
+
167
+ ## Webhook verification
168
+
169
+ ```python
170
+ from agf import verify_signature, parse_event, AGFWebhookVerificationError
171
+
172
+ # FastAPI example
173
+ from fastapi import FastAPI, Request, HTTPException
174
+
175
+ app = FastAPI()
176
+
177
+ @app.post("/agf-webhook")
178
+ async def handle(request: Request):
179
+ body = await request.body()
180
+ try:
181
+ verify_signature(body, request.headers["X-AGF-Signature"], WEBHOOK_SECRET)
182
+ except AGFWebhookVerificationError:
183
+ raise HTTPException(status_code=400, detail="Invalid signature")
184
+
185
+ event = parse_event(body)
186
+ if event.type == "decision.deny":
187
+ print(f"Agent {event.agent_id} was denied — artifact {event.artifact_id}")
188
+ ```
189
+
190
+ ## Sync client
191
+
192
+ For scripts, Django views, or any non-async context:
193
+
194
+ ```python
195
+ from agf import SyncAGFClient
196
+
197
+ with SyncAGFClient(api_key="agfk_...") as client:
198
+ result = client.decide("file:write", "s3://bucket/file.csv")
199
+ agents = client.list_agents(status="active")
200
+ ```
201
+
202
+ ## Environment variable
203
+
204
+ Set `AGF_API_KEY` in your environment and pass it via `os.environ["AGF_API_KEY"]`. The SDK does not auto-read environment variables — this keeps the dependency graph minimal and the behaviour explicit.
205
+
206
+ ## Requirements
207
+
208
+ - Python 3.10+
209
+ - `httpx >= 0.27`
210
+ - `langchain-core >= 0.2` (optional, `agf-sdk[langchain]`)
211
+ - `crewai >= 0.28` (optional, `agf-sdk[crewai]`)
212
+
213
+ ## Links
214
+
215
+ - [Documentation](https://agentgovernancefoundation.com/docs)
216
+ - [Quick start](https://agentgovernancefoundation.com/docs/quick-start)
217
+ - [API reference](https://agentgovernancefoundation.com/docs/api)
@@ -0,0 +1,199 @@
1
+ # agf-sdk
2
+
3
+ Python SDK for the [Agent Governance Foundation](https://agentgovernancefoundation.com) authorization service. Enforce identity, trust, and policy controls on every action your AI agents take.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install agf-sdk
9
+ ```
10
+
11
+ With LangChain support:
12
+
13
+ ```bash
14
+ pip install agf-sdk[langchain]
15
+ ```
16
+
17
+ With CrewAI support:
18
+
19
+ ```bash
20
+ pip install agf-sdk[crewai]
21
+ ```
22
+
23
+ ## Quick start
24
+
25
+ ```python
26
+ import os
27
+ from agf import AgentGovernance
28
+
29
+ agf = AgentGovernance(
30
+ api_key=os.environ["AGF_API_KEY"],
31
+ org_id="org_acme",
32
+ )
33
+
34
+ result = agf.authorize(
35
+ agent_id="did:agf:agt_01abc",
36
+ action="file:write",
37
+ resource="s3://corp-data/q2.csv",
38
+ )
39
+
40
+ if result.allowed:
41
+ write_file()
42
+ else:
43
+ raise PermissionError(f"Denied: {result.reason}")
44
+ ```
45
+
46
+ ## Authorization results
47
+
48
+ `authorize()` never raises for deny/review — it always returns an `AuthResult`:
49
+
50
+ | Field | Type | Description |
51
+ |---|---|---|
52
+ | `allowed` | `bool` | `True` when the PDP issued ALLOW |
53
+ | `denied` | `bool` | `True` when the PDP issued DENY |
54
+ | `review_required` | `bool` | `True` when HITL approval is needed |
55
+ | `reason` | `str` | Human-readable denial reason |
56
+ | `artifact_id` | `str` | Signed audit artifact ID |
57
+ | `risk_score` | `float` | 0.0–1.0 |
58
+ | `trust_score` | `int` | 0–100 |
59
+ | `approval_request_id` | `str` | HITL request ID (review_required only) |
60
+
61
+ ## Async client
62
+
63
+ For async frameworks (FastAPI, async Django, etc.) use `AGFClient` directly:
64
+
65
+ ```python
66
+ from agf import AGFClient, AGFDeniedError
67
+
68
+ async def handle_request():
69
+ async with AGFClient(api_key="agfk_...") as client:
70
+ try:
71
+ result = await client.decide(
72
+ action_type="file:write",
73
+ resource="s3://corp-data/q2.csv",
74
+ chain=[root_jwt, agent_jwt],
75
+ )
76
+ except AGFDeniedError as exc:
77
+ print(f"Denied — artifact: {exc.artifact_id}")
78
+ ```
79
+
80
+ ## LangChain integration
81
+
82
+ ### Authorization gate tool (recommended for most agents)
83
+
84
+ Add an authorization tool to your agent's tool list. The agent calls it before performing sensitive operations:
85
+
86
+ ```python
87
+ from agf import AgentGovernance
88
+ from langchain.agents import initialize_agent, AgentType
89
+ from langchain_openai import ChatOpenAI
90
+
91
+ agf = AgentGovernance(api_key="agfk_...", org_id="org_acme")
92
+ agf_tool = agf.langchain_tool(agent_id="did:agf:agt_01abc")
93
+
94
+ agent = initialize_agent(
95
+ tools=[agf_tool, *your_other_tools],
96
+ llm=ChatOpenAI(),
97
+ agent=AgentType.OPENAI_FUNCTIONS,
98
+ )
99
+ ```
100
+
101
+ ### Per-tool guard (enforces policy on every tool call)
102
+
103
+ Wrap individual tools so no call can bypass the policy check:
104
+
105
+ ```python
106
+ from langchain_community.tools import ShellTool
107
+ from agf.langchain import AGFGuardedTool
108
+ from agf import AGFClient
109
+
110
+ client = AGFClient(api_key="agfk_...")
111
+
112
+ guarded_shell = AGFGuardedTool(
113
+ tool=ShellTool(),
114
+ client=client,
115
+ agent_id="did:agf:my-assistant",
116
+ action_type="exec:shell",
117
+ resource="local-shell",
118
+ )
119
+ ```
120
+
121
+ ## CrewAI integration
122
+
123
+ ```python
124
+ from crewai import Agent
125
+ from crewai.tools import BaseTool as CrewBaseTool
126
+ from agf.crewai import AGFCrewAITool
127
+ from agf import AGFClient
128
+
129
+ client = AGFClient(api_key="agfk_...")
130
+
131
+ class MyDBTool(CrewBaseTool):
132
+ name: str = "database_query"
133
+ description: str = "Query the production database"
134
+
135
+ def _run(self, query: str) -> str:
136
+ return db.execute(query)
137
+
138
+ guarded = AGFCrewAITool(
139
+ tool=MyDBTool(),
140
+ client=client,
141
+ agent_id="did:agf:crew-researcher",
142
+ action_type="query:database",
143
+ resource="prod-db",
144
+ )
145
+
146
+ crew_agent = Agent(tools=[guarded], ...)
147
+ ```
148
+
149
+ ## Webhook verification
150
+
151
+ ```python
152
+ from agf import verify_signature, parse_event, AGFWebhookVerificationError
153
+
154
+ # FastAPI example
155
+ from fastapi import FastAPI, Request, HTTPException
156
+
157
+ app = FastAPI()
158
+
159
+ @app.post("/agf-webhook")
160
+ async def handle(request: Request):
161
+ body = await request.body()
162
+ try:
163
+ verify_signature(body, request.headers["X-AGF-Signature"], WEBHOOK_SECRET)
164
+ except AGFWebhookVerificationError:
165
+ raise HTTPException(status_code=400, detail="Invalid signature")
166
+
167
+ event = parse_event(body)
168
+ if event.type == "decision.deny":
169
+ print(f"Agent {event.agent_id} was denied — artifact {event.artifact_id}")
170
+ ```
171
+
172
+ ## Sync client
173
+
174
+ For scripts, Django views, or any non-async context:
175
+
176
+ ```python
177
+ from agf import SyncAGFClient
178
+
179
+ with SyncAGFClient(api_key="agfk_...") as client:
180
+ result = client.decide("file:write", "s3://bucket/file.csv")
181
+ agents = client.list_agents(status="active")
182
+ ```
183
+
184
+ ## Environment variable
185
+
186
+ Set `AGF_API_KEY` in your environment and pass it via `os.environ["AGF_API_KEY"]`. The SDK does not auto-read environment variables — this keeps the dependency graph minimal and the behaviour explicit.
187
+
188
+ ## Requirements
189
+
190
+ - Python 3.10+
191
+ - `httpx >= 0.27`
192
+ - `langchain-core >= 0.2` (optional, `agf-sdk[langchain]`)
193
+ - `crewai >= 0.28` (optional, `agf-sdk[crewai]`)
194
+
195
+ ## Links
196
+
197
+ - [Documentation](https://agentgovernancefoundation.com/docs)
198
+ - [Quick start](https://agentgovernancefoundation.com/docs/quick-start)
199
+ - [API reference](https://agentgovernancefoundation.com/docs/api)
@@ -0,0 +1,37 @@
1
+ """Agent Governance Foundation Python SDK."""
2
+ from .client import AGFClient, Agent, DecisionResult
3
+ from .exceptions import (
4
+ AGFAuthError,
5
+ AGFConnectionError,
6
+ AGFDeniedError,
7
+ AGFError,
8
+ AGFReviewRequiredError,
9
+ )
10
+ from .govern import AgentGovernance, AuthResult
11
+ from .sync import SyncAGFClient
12
+ from .webhook import AGFWebhookVerificationError, WebhookEvent, parse_event, verify_signature
13
+
14
+ __all__ = [
15
+ # High-level facade (most users start here)
16
+ "AgentGovernance",
17
+ "AuthResult",
18
+ # Async client
19
+ "AGFClient",
20
+ "Agent",
21
+ "DecisionResult",
22
+ # Sync client
23
+ "SyncAGFClient",
24
+ # Exceptions
25
+ "AGFError",
26
+ "AGFAuthError",
27
+ "AGFConnectionError",
28
+ "AGFDeniedError",
29
+ "AGFReviewRequiredError",
30
+ # Webhook
31
+ "AGFWebhookVerificationError",
32
+ "WebhookEvent",
33
+ "verify_signature",
34
+ "parse_event",
35
+ ]
36
+
37
+ __version__ = "0.1.0"
@@ -0,0 +1,207 @@
1
+ """AGF Python SDK — async client."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass, field
5
+ from typing import Any
6
+
7
+ import httpx
8
+
9
+ from .exceptions import AGFAuthError, AGFConnectionError, AGFDeniedError, AGFReviewRequiredError
10
+
11
+ _DEFAULT_BASE_URL = "https://api.agentgovernancefoundation.com"
12
+ _DEFAULT_TIMEOUT = 15.0
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class DecisionResult:
17
+ """Returned by :meth:`AGFClient.decide` on an ALLOW decision."""
18
+ decision: str
19
+ artifact_id: str
20
+ trust_score: int
21
+ risk_score: float
22
+ policy_version: str
23
+ chain_depth: int
24
+ effective_scope: list[str]
25
+ reasoning: list[str]
26
+ approval_request_id: str | None = None
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class Agent:
31
+ id: str
32
+ org_id: str
33
+ name: str
34
+ did: str
35
+ status: str
36
+ trust_score: int | None = None
37
+ created_at: str = ""
38
+
39
+
40
+ @dataclass
41
+ class AGFClient:
42
+ """Async AGF runtime client.
43
+
44
+ Args:
45
+ api_key: Org-level API key from Settings → API Keys.
46
+ base_url: Override the default AGF runtime URL.
47
+ timeout: HTTP timeout in seconds.
48
+
49
+ Example::
50
+
51
+ client = AGFClient(api_key="ak_live_...")
52
+ result = await client.decide(
53
+ action_type="write:database",
54
+ resource="prod-customers",
55
+ chain=[root_jwt, agent_jwt],
56
+ )
57
+ """
58
+
59
+ api_key: str
60
+ base_url: str = _DEFAULT_BASE_URL
61
+ timeout: float = _DEFAULT_TIMEOUT
62
+ _http: httpx.AsyncClient = field(init=False, repr=False)
63
+
64
+ def __post_init__(self) -> None:
65
+ self._http = httpx.AsyncClient(
66
+ base_url=self.base_url.rstrip("/"),
67
+ headers={
68
+ "X-AGF-Key": self.api_key,
69
+ "Content-Type": "application/json",
70
+ "User-Agent": "agf-sdk/0.1.0 python",
71
+ },
72
+ timeout=self._timeout_obj,
73
+ )
74
+
75
+ @property
76
+ def _timeout_obj(self) -> httpx.Timeout:
77
+ return httpx.Timeout(self.timeout)
78
+
79
+ async def __aenter__(self) -> "AGFClient":
80
+ return self
81
+
82
+ async def __aexit__(self, *_: Any) -> None:
83
+ await self.aclose()
84
+
85
+ async def aclose(self) -> None:
86
+ await self._http.aclose()
87
+
88
+ async def _request(self, method: str, path: str, **kwargs: Any) -> dict[str, Any]:
89
+ try:
90
+ resp = await self._http.request(method, path, **kwargs)
91
+ except httpx.ConnectError as exc:
92
+ raise AGFConnectionError(f"Cannot connect to AGF runtime at {self.base_url}: {exc}") from exc
93
+ except httpx.TimeoutException as exc:
94
+ raise AGFConnectionError(f"AGF runtime request timed out: {exc}") from exc
95
+
96
+ if resp.status_code == 401:
97
+ raise AGFAuthError("Invalid API key or suspended organisation.")
98
+ if resp.status_code == 403:
99
+ raise AGFAuthError("Insufficient permissions.")
100
+
101
+ resp.raise_for_status()
102
+ return resp.json() # type: ignore[no-any-return]
103
+
104
+ async def decide(
105
+ self,
106
+ action_type: str,
107
+ resource: str,
108
+ *,
109
+ chain: list[str] | None = None,
110
+ audience: str = "agf",
111
+ context: dict[str, Any] | None = None,
112
+ policy_version: str | None = None,
113
+ ) -> DecisionResult:
114
+ """Evaluate an action against the AGF policy engine.
115
+
116
+ Raises:
117
+ AGFDeniedError: The PDP issued a DENY decision.
118
+ AGFReviewRequiredError: The action needs human review (HITL).
119
+ AGFConnectionError: Cannot reach the AGF runtime.
120
+ AGFAuthError: API key is invalid.
121
+ """
122
+ body: dict[str, Any] = {
123
+ "chain": chain or [],
124
+ "action": {"type": action_type, "resource": resource},
125
+ "audience": audience,
126
+ }
127
+ if context:
128
+ body["context"] = context
129
+ if policy_version:
130
+ body["policy_version"] = policy_version
131
+
132
+ data = await self._request("POST", "/v1/decide", json=body)
133
+ decision_data = data.get("data", data)
134
+ decision = decision_data.get("decision", "DENY")
135
+ artifact_id = decision_data.get("artifact_id", "")
136
+
137
+ if decision == "DENY":
138
+ raise AGFDeniedError(
139
+ f"Action '{action_type}' on '{resource}' was denied by policy.",
140
+ artifact_id=artifact_id,
141
+ risk_score=decision_data.get("risk_score", 0.0),
142
+ reasoning=decision_data.get("reasoning", []),
143
+ )
144
+ if decision == "REVIEW_REQUIRED":
145
+ raise AGFReviewRequiredError(
146
+ f"Action '{action_type}' on '{resource}' requires human approval.",
147
+ approval_request_id=decision_data.get("approval_request_id") or "",
148
+ artifact_id=artifact_id,
149
+ )
150
+
151
+ return DecisionResult(
152
+ decision=decision,
153
+ artifact_id=artifact_id,
154
+ trust_score=decision_data.get("trust_score", 0),
155
+ risk_score=decision_data.get("risk_score", 0.0),
156
+ policy_version=decision_data.get("policy_version", ""),
157
+ chain_depth=decision_data.get("chain_depth", 0),
158
+ effective_scope=decision_data.get("effective_scope", []),
159
+ reasoning=decision_data.get("reasoning", []),
160
+ approval_request_id=decision_data.get("approval_request_id"),
161
+ )
162
+
163
+ async def list_agents(
164
+ self,
165
+ *,
166
+ status: str | None = None,
167
+ page: int = 1,
168
+ per_page: int = 50,
169
+ ) -> list[Agent]:
170
+ params: dict[str, Any] = {"page": page, "per_page": per_page}
171
+ if status:
172
+ params["status"] = status
173
+ data = await self._request("GET", "/v1/agents", params=params)
174
+ items = data.get("items", data) if isinstance(data, dict) else data
175
+ return [
176
+ Agent(
177
+ id=a["id"],
178
+ org_id=a.get("org_id", ""),
179
+ name=a.get("name", ""),
180
+ did=a.get("did", ""),
181
+ status=a.get("status", "active"),
182
+ trust_score=a.get("trust_score"),
183
+ created_at=a.get("created_at", ""),
184
+ )
185
+ for a in items
186
+ ]
187
+
188
+ async def register_agent(
189
+ self,
190
+ name: str,
191
+ did: str,
192
+ public_key_pem: str,
193
+ metadata: dict[str, Any] | None = None,
194
+ ) -> Agent:
195
+ body: dict[str, Any] = {"name": name, "did": did, "public_key_pem": public_key_pem}
196
+ if metadata:
197
+ body["metadata"] = metadata
198
+ a = await self._request("POST", "/v1/agents", json=body)
199
+ return Agent(
200
+ id=a["id"],
201
+ org_id=a.get("org_id", ""),
202
+ name=a.get("name", name),
203
+ did=a.get("did", did),
204
+ status=a.get("status", "active"),
205
+ trust_score=a.get("trust_score"),
206
+ created_at=a.get("created_at", ""),
207
+ )