acr-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.
@@ -0,0 +1,8 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ dist/
8
+ *.egg-info/
acr_sdk-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,229 @@
1
+ Metadata-Version: 2.4
2
+ Name: acr-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for Agent Capability Runtime — runtime-enforced capability permissions for AI agents
5
+ Project-URL: Homepage, https://github.com/agent-capability-runtime/Agent_Capability_Runtime
6
+ Project-URL: Documentation, https://github.com/agent-capability-runtime/Agent_Capability_Runtime/tree/main/packages/sdk-python
7
+ Project-URL: Repository, https://github.com/agent-capability-runtime/Agent_Capability_Runtime
8
+ Project-URL: Issues, https://github.com/agent-capability-runtime/Agent_Capability_Runtime/issues
9
+ Author: Agent Capability Runtime Contributors
10
+ License-Expression: MIT
11
+ Keywords: ai-agents,authorization,capability-token,governance,runtime
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Security
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: httpx>=0.27.0
26
+ Requires-Dist: pydantic>=2.0.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: mypy>=1.10; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
30
+ Requires-Dist: pytest>=8.0; extra == 'dev'
31
+ Requires-Dist: respx>=0.21; extra == 'dev'
32
+ Requires-Dist: ruff>=0.4; extra == 'dev'
33
+ Provides-Extra: langchain
34
+ Requires-Dist: acr-langchain>=0.1.0; extra == 'langchain'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # ACR Python SDK
38
+
39
+ **Python client for [Agent Capability Runtime](https://github.com/agent-capability-runtime/Agent_Capability_Runtime)** — runtime-enforced capability permissions for AI agents.
40
+
41
+ [![Python 3.10+](https://img.shields.io/badge/Python-3.10%2B-blue.svg)](https://www.python.org/)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](../../LICENSE)
43
+
44
+ ## Install
45
+
46
+ From source (PyPI publish pending):
47
+
48
+ ```bash
49
+ cd Agent_Capability_Runtime/packages/sdk-python
50
+ pip install -e ".[dev]"
51
+ ```
52
+
53
+ When published:
54
+
55
+ ```bash
56
+ pip install acr-sdk
57
+ ```
58
+
59
+ ## Quick Start
60
+
61
+ ### Embedded mode — zero infrastructure
62
+
63
+ No gateway, no Docker, no Node. Policy enforcement runs inside your process:
64
+
65
+ ```python
66
+ from acr import LocalAcrClient, can
67
+
68
+ client = LocalAcrClient() # in-process runtime
69
+
70
+ grant = client.grant_sync(
71
+ can("gmail.send").only_domain("company.com").limit(5).to_grant_input(agent_id="a1")
72
+ )
73
+
74
+ result = client.execute_sync(
75
+ token=grant.token, tool="gmail.send",
76
+ payload={"to": "user@company.com", "subject": "Hello"},
77
+ )
78
+ print(result.decision) # "ALLOW"
79
+
80
+ result = client.execute_sync(
81
+ token=grant.token, tool="gmail.send",
82
+ payload={"to": "attacker@gmail.com", "subject": "Exfil"},
83
+ )
84
+ print(result.decision) # "DENY"
85
+ ```
86
+
87
+ Or let the environment decide (gateway when `ACR_GATEWAY_URL` is set, embedded otherwise):
88
+
89
+ ```python
90
+ from acr import create_client
91
+
92
+ client = create_client()
93
+ ```
94
+
95
+ ### Async (FastAPI / LangChain) — gateway mode
96
+
97
+ ```python
98
+ from acr import AcrClient, can
99
+
100
+ async with AcrClient(base_url="http://localhost:3000") as client:
101
+ # Grant a scoped capability
102
+ grant = await client.grant(
103
+ can("gmail.send")
104
+ .only_domain("company.com")
105
+ .limit(5)
106
+ .expires_in("10m")
107
+ .to_grant_input(agent_id="support_agent")
108
+ )
109
+
110
+ # Execute — ALLOW (internal domain)
111
+ result = await client.execute(
112
+ token=grant.token,
113
+ tool="gmail.send",
114
+ payload={"to": "user@company.com", "subject": "Hello"},
115
+ )
116
+ print(result.decision) # "ALLOW"
117
+
118
+ # Execute — DENY (external domain blocked)
119
+ result = await client.execute(
120
+ token=grant.token,
121
+ tool="gmail.send",
122
+ payload={"to": "attacker@gmail.com", "subject": "Exfil"},
123
+ )
124
+ print(result.decision) # "DENY"
125
+ ```
126
+
127
+ ### Sync
128
+
129
+ ```python
130
+ from acr import AcrClient, can
131
+
132
+ client = AcrClient(base_url="http://localhost:3000")
133
+
134
+ grant = client.grant_sync(
135
+ can("gmail.send")
136
+ .only_domain("company.com")
137
+ .to_grant_input(agent_id="agent_1")
138
+ )
139
+
140
+ result = client.execute_sync(
141
+ token=grant.token,
142
+ tool="gmail.send",
143
+ payload={"to": "user@company.com", "subject": "Hello"},
144
+ )
145
+ client.close()
146
+ ```
147
+
148
+ ## Fluent DSL
149
+
150
+ The `can()` builder mirrors the TypeScript DSL:
151
+
152
+ ```python
153
+ from acr import can
154
+
155
+ # Email constraints
156
+ can("gmail.send").only_domain("company.com").limit(5).no_attachments()
157
+
158
+ # HTTP constraints
159
+ can("http.request").where(method.in_(["GET", "POST"])).where(url.in_(["https://api.example.com"]))
160
+
161
+ # Spending limit with approval
162
+ can("gmail.send").max_spend(100_00).require_approval()
163
+
164
+ # Intent-based governance
165
+ can("gmail.send").when_intent("customer_support").when_intent_action("support", "reply")
166
+
167
+ # Time-based
168
+ can("gmail.send").allowed_hours(9, 17)
169
+ ```
170
+
171
+ ## Full API
172
+
173
+ | Method | Async | Sync |
174
+ |--------|-------|------|
175
+ | Grant capability | `client.grant(input)` | `client.grant_sync(input)` |
176
+ | Execute tool | `client.execute(...)` | `client.execute_sync(...)` |
177
+ | Delegate capability | `client.delegate(parent_token, input)` | `client.delegate_sync(...)` |
178
+ | Revoke capability | `client.revoke(capability_id)` | `client.revoke_sync(...)` |
179
+ | List approvals | `client.list_approvals()` | `client.list_approvals_sync()` |
180
+ | Approve | `client.approve(approval_id)` | `client.approve_sync(...)` |
181
+ | Reject | `client.reject(approval_id)` | `client.reject_sync(...)` |
182
+ | Audit log | `client.list_audit()` | `client.list_audit_sync()` |
183
+ | Verify audit chain | `client.verify_audit_chain()` | `client.verify_audit_chain_sync()` |
184
+ | Health check | `client.health()` | `client.health_sync()` |
185
+
186
+ ## Admin Authentication
187
+
188
+ ```python
189
+ client = AcrClient(
190
+ base_url="http://localhost:3000",
191
+ admin_api_key="your-admin-secret",
192
+ )
193
+ ```
194
+
195
+ ## LangChain integration
196
+
197
+ ```bash
198
+ pip install "acr-sdk[langchain]"
199
+ ```
200
+
201
+ ```python
202
+ from acr import can
203
+ from acr.langchain import protect
204
+
205
+ tools = protect(my_tools, agent_id="my_agent", policy=can("http.request").limit(50))
206
+ ```
207
+
208
+ See [packages/integrations/langchain](../integrations/langchain).
209
+
210
+ ## Requirements
211
+
212
+ - Python 3.10+
213
+ - Embedded mode: nothing else
214
+ - Gateway mode: a running ACR gateway (`pnpm dev:gateway`)
215
+
216
+ ## Gateway e2e
217
+
218
+ With the gateway running:
219
+
220
+ ```bash
221
+ python packages/sdk-python/examples/demo_wow.py # deny / approval / revoke narrative
222
+ python packages/sdk-python/examples/e2e_gateway.py
223
+ # or
224
+ ACR_RUN_E2E=1 pytest packages/sdk-python/tests/test_e2e_gateway.py -v
225
+ ```
226
+
227
+ ## License
228
+
229
+ MIT — see [LICENSE](../../LICENSE)
@@ -0,0 +1,193 @@
1
+ # ACR Python SDK
2
+
3
+ **Python client for [Agent Capability Runtime](https://github.com/agent-capability-runtime/Agent_Capability_Runtime)** — runtime-enforced capability permissions for AI agents.
4
+
5
+ [![Python 3.10+](https://img.shields.io/badge/Python-3.10%2B-blue.svg)](https://www.python.org/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](../../LICENSE)
7
+
8
+ ## Install
9
+
10
+ From source (PyPI publish pending):
11
+
12
+ ```bash
13
+ cd Agent_Capability_Runtime/packages/sdk-python
14
+ pip install -e ".[dev]"
15
+ ```
16
+
17
+ When published:
18
+
19
+ ```bash
20
+ pip install acr-sdk
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### Embedded mode — zero infrastructure
26
+
27
+ No gateway, no Docker, no Node. Policy enforcement runs inside your process:
28
+
29
+ ```python
30
+ from acr import LocalAcrClient, can
31
+
32
+ client = LocalAcrClient() # in-process runtime
33
+
34
+ grant = client.grant_sync(
35
+ can("gmail.send").only_domain("company.com").limit(5).to_grant_input(agent_id="a1")
36
+ )
37
+
38
+ result = client.execute_sync(
39
+ token=grant.token, tool="gmail.send",
40
+ payload={"to": "user@company.com", "subject": "Hello"},
41
+ )
42
+ print(result.decision) # "ALLOW"
43
+
44
+ result = client.execute_sync(
45
+ token=grant.token, tool="gmail.send",
46
+ payload={"to": "attacker@gmail.com", "subject": "Exfil"},
47
+ )
48
+ print(result.decision) # "DENY"
49
+ ```
50
+
51
+ Or let the environment decide (gateway when `ACR_GATEWAY_URL` is set, embedded otherwise):
52
+
53
+ ```python
54
+ from acr import create_client
55
+
56
+ client = create_client()
57
+ ```
58
+
59
+ ### Async (FastAPI / LangChain) — gateway mode
60
+
61
+ ```python
62
+ from acr import AcrClient, can
63
+
64
+ async with AcrClient(base_url="http://localhost:3000") as client:
65
+ # Grant a scoped capability
66
+ grant = await client.grant(
67
+ can("gmail.send")
68
+ .only_domain("company.com")
69
+ .limit(5)
70
+ .expires_in("10m")
71
+ .to_grant_input(agent_id="support_agent")
72
+ )
73
+
74
+ # Execute — ALLOW (internal domain)
75
+ result = await client.execute(
76
+ token=grant.token,
77
+ tool="gmail.send",
78
+ payload={"to": "user@company.com", "subject": "Hello"},
79
+ )
80
+ print(result.decision) # "ALLOW"
81
+
82
+ # Execute — DENY (external domain blocked)
83
+ result = await client.execute(
84
+ token=grant.token,
85
+ tool="gmail.send",
86
+ payload={"to": "attacker@gmail.com", "subject": "Exfil"},
87
+ )
88
+ print(result.decision) # "DENY"
89
+ ```
90
+
91
+ ### Sync
92
+
93
+ ```python
94
+ from acr import AcrClient, can
95
+
96
+ client = AcrClient(base_url="http://localhost:3000")
97
+
98
+ grant = client.grant_sync(
99
+ can("gmail.send")
100
+ .only_domain("company.com")
101
+ .to_grant_input(agent_id="agent_1")
102
+ )
103
+
104
+ result = client.execute_sync(
105
+ token=grant.token,
106
+ tool="gmail.send",
107
+ payload={"to": "user@company.com", "subject": "Hello"},
108
+ )
109
+ client.close()
110
+ ```
111
+
112
+ ## Fluent DSL
113
+
114
+ The `can()` builder mirrors the TypeScript DSL:
115
+
116
+ ```python
117
+ from acr import can
118
+
119
+ # Email constraints
120
+ can("gmail.send").only_domain("company.com").limit(5).no_attachments()
121
+
122
+ # HTTP constraints
123
+ can("http.request").where(method.in_(["GET", "POST"])).where(url.in_(["https://api.example.com"]))
124
+
125
+ # Spending limit with approval
126
+ can("gmail.send").max_spend(100_00).require_approval()
127
+
128
+ # Intent-based governance
129
+ can("gmail.send").when_intent("customer_support").when_intent_action("support", "reply")
130
+
131
+ # Time-based
132
+ can("gmail.send").allowed_hours(9, 17)
133
+ ```
134
+
135
+ ## Full API
136
+
137
+ | Method | Async | Sync |
138
+ |--------|-------|------|
139
+ | Grant capability | `client.grant(input)` | `client.grant_sync(input)` |
140
+ | Execute tool | `client.execute(...)` | `client.execute_sync(...)` |
141
+ | Delegate capability | `client.delegate(parent_token, input)` | `client.delegate_sync(...)` |
142
+ | Revoke capability | `client.revoke(capability_id)` | `client.revoke_sync(...)` |
143
+ | List approvals | `client.list_approvals()` | `client.list_approvals_sync()` |
144
+ | Approve | `client.approve(approval_id)` | `client.approve_sync(...)` |
145
+ | Reject | `client.reject(approval_id)` | `client.reject_sync(...)` |
146
+ | Audit log | `client.list_audit()` | `client.list_audit_sync()` |
147
+ | Verify audit chain | `client.verify_audit_chain()` | `client.verify_audit_chain_sync()` |
148
+ | Health check | `client.health()` | `client.health_sync()` |
149
+
150
+ ## Admin Authentication
151
+
152
+ ```python
153
+ client = AcrClient(
154
+ base_url="http://localhost:3000",
155
+ admin_api_key="your-admin-secret",
156
+ )
157
+ ```
158
+
159
+ ## LangChain integration
160
+
161
+ ```bash
162
+ pip install "acr-sdk[langchain]"
163
+ ```
164
+
165
+ ```python
166
+ from acr import can
167
+ from acr.langchain import protect
168
+
169
+ tools = protect(my_tools, agent_id="my_agent", policy=can("http.request").limit(50))
170
+ ```
171
+
172
+ See [packages/integrations/langchain](../integrations/langchain).
173
+
174
+ ## Requirements
175
+
176
+ - Python 3.10+
177
+ - Embedded mode: nothing else
178
+ - Gateway mode: a running ACR gateway (`pnpm dev:gateway`)
179
+
180
+ ## Gateway e2e
181
+
182
+ With the gateway running:
183
+
184
+ ```bash
185
+ python packages/sdk-python/examples/demo_wow.py # deny / approval / revoke narrative
186
+ python packages/sdk-python/examples/e2e_gateway.py
187
+ # or
188
+ ACR_RUN_E2E=1 pytest packages/sdk-python/tests/test_e2e_gateway.py -v
189
+ ```
190
+
191
+ ## License
192
+
193
+ MIT — see [LICENSE](../../LICENSE)
@@ -0,0 +1,55 @@
1
+ """ACR SDK — Python client for Agent Capability Runtime."""
2
+
3
+ from acr.client import AcrClient
4
+ from acr.dsl import can, domain, method, url, hours, intent, PolicyBuilder
5
+ from acr.exceptions import AcrError, GrantError, ExecuteError, ApprovalError
6
+ from acr.local import LocalAcrClient, create_client
7
+ from acr.models import (
8
+ ConstraintSet,
9
+ ExecuteInput,
10
+ ExecuteResult,
11
+ ExecuteSuccess,
12
+ ExecuteDenied,
13
+ ExecuteApprovalRequired,
14
+ ExecuteSimulated,
15
+ GrantCapabilityInput,
16
+ GrantResponse,
17
+ ApprovalRequest,
18
+ AuditEvent,
19
+ DelegateCapabilityInput,
20
+ )
21
+
22
+ __version__ = "0.1.0"
23
+
24
+ __all__ = [
25
+ # Client
26
+ "AcrClient",
27
+ "LocalAcrClient",
28
+ "create_client",
29
+ # DSL
30
+ "can",
31
+ "domain",
32
+ "method",
33
+ "url",
34
+ "hours",
35
+ "intent",
36
+ "PolicyBuilder",
37
+ # Exceptions
38
+ "AcrError",
39
+ "GrantError",
40
+ "ExecuteError",
41
+ "ApprovalError",
42
+ # Models
43
+ "ConstraintSet",
44
+ "ExecuteInput",
45
+ "ExecuteResult",
46
+ "ExecuteSuccess",
47
+ "ExecuteDenied",
48
+ "ExecuteApprovalRequired",
49
+ "ExecuteSimulated",
50
+ "GrantCapabilityInput",
51
+ "GrantResponse",
52
+ "ApprovalRequest",
53
+ "AuditEvent",
54
+ "DelegateCapabilityInput",
55
+ ]
@@ -0,0 +1,64 @@
1
+ """Minimal HS256 JWT encode/decode using only the standard library.
2
+
3
+ Used by the embedded local runtime (``acr.local``). Tokens are interoperable
4
+ with the gateway's HS256 signing when the same secret is configured.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import base64
10
+ import hashlib
11
+ import hmac
12
+ import json
13
+ from typing import Any
14
+
15
+
16
+ def _b64url_encode(data: bytes) -> str:
17
+ return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
18
+
19
+
20
+ def _b64url_decode(data: str) -> bytes:
21
+ padding = "=" * (-len(data) % 4)
22
+ return base64.urlsafe_b64decode(data + padding)
23
+
24
+
25
+ def encode_hs256(payload: dict[str, Any], secret: str) -> str:
26
+ """Sign a JWT payload with HS256."""
27
+ header = {"alg": "HS256", "typ": "JWT"}
28
+ header_b64 = _b64url_encode(json.dumps(header, separators=(",", ":")).encode("utf-8"))
29
+ payload_b64 = _b64url_encode(json.dumps(payload, separators=(",", ":")).encode("utf-8"))
30
+ signing_input = f"{header_b64}.{payload_b64}".encode("ascii")
31
+ signature = hmac.new(secret.encode("utf-8"), signing_input, hashlib.sha256).digest()
32
+ return f"{header_b64}.{payload_b64}.{_b64url_encode(signature)}"
33
+
34
+
35
+ def decode_hs256(token: str, secret: str) -> dict[str, Any]:
36
+ """Verify an HS256 JWT signature and return the payload.
37
+
38
+ Raises ValueError for malformed tokens or signature mismatch.
39
+ Expiry is NOT checked here — callers decide how to surface it.
40
+ """
41
+ parts = token.split(".")
42
+ if len(parts) != 3:
43
+ raise ValueError("malformed token")
44
+
45
+ header_b64, payload_b64, signature_b64 = parts
46
+ signing_input = f"{header_b64}.{payload_b64}".encode("ascii")
47
+ expected = hmac.new(secret.encode("utf-8"), signing_input, hashlib.sha256).digest()
48
+
49
+ try:
50
+ actual = _b64url_decode(signature_b64)
51
+ except Exception as exc:
52
+ raise ValueError("malformed signature") from exc
53
+
54
+ if not hmac.compare_digest(expected, actual):
55
+ raise ValueError("invalid signature")
56
+
57
+ try:
58
+ payload = json.loads(_b64url_decode(payload_b64))
59
+ except Exception as exc:
60
+ raise ValueError("malformed payload") from exc
61
+
62
+ if not isinstance(payload, dict):
63
+ raise ValueError("invalid payload")
64
+ return payload