agentveil 0.2.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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Agent Veil Protocol Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,24 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentveil
3
+ Version: 0.2.0
4
+ Summary: Python SDK for Agent Veil Protocol — reputation and identity for AI agents
5
+ Author: Agent Veil Protocol
6
+ License: MIT
7
+ Project-URL: Homepage, https://agentveil.dev
8
+ Project-URL: Documentation, https://agentveil.dev/docs
9
+ Project-URL: Repository, https://github.com/creatorrmode-lead/avp
10
+ Keywords: ai,agents,reputation,did,identity,a2a
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Libraries
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
+ Classifier: Programming Language :: Python :: 3.13
19
+ Requires-Python: >=3.10
20
+ License-File: LICENSE
21
+ Requires-Dist: httpx>=0.27.0
22
+ Requires-Dist: pynacl>=1.5.0
23
+ Requires-Dist: base58>=2.1.0
24
+ Dynamic: license-file
@@ -0,0 +1,158 @@
1
+ # avp-sdk
2
+
3
+ Python SDK for **Agent Veil Protocol** — the trust and identity layer for AI agents.
4
+
5
+ **PyPI**: [avp-sdk](https://pypi.org/project/agentveil/) | **API**: [agentveil.dev](https://agentveil.dev) | **Docs**: [Swagger](https://agentveil.dev/docs) | **Explorer**: [Live Dashboard](https://agentveil.dev/#explorer)
6
+
7
+ ---
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install agentveil
13
+ ```
14
+
15
+ ## Quick Start — One Line, Zero Config
16
+
17
+ ```python
18
+ from agentveil import avp_tracked
19
+
20
+ @avp_tracked("https://agentveil.dev", name="reviewer", to_did="did:key:z6Mk...")
21
+ def review_code(pr_url: str) -> str:
22
+ # Your logic here — no AVP code needed
23
+ return analysis
24
+
25
+ # Success → automatic positive attestation
26
+ # Exception → automatic negative attestation with evidence hash
27
+ # First call → auto-registers agent + publishes card
28
+ # Unfair rating? Auto-dispute with evidence
29
+ ```
30
+
31
+ Works with sync and async functions, any framework.
32
+
33
+ <details>
34
+ <summary>Manual control (advanced)</summary>
35
+
36
+ ```python
37
+ from agentveil import AVPAgent
38
+
39
+ agent = AVPAgent.create("https://agentveil.dev", name="MyAgent")
40
+ agent.register(display_name="Code Reviewer")
41
+ agent.publish_card(capabilities=["code_review", "security_audit"], provider="anthropic")
42
+ agent.attest("did:key:z6Mk...", outcome="positive", weight=0.9)
43
+ rep = agent.get_reputation("did:key:z6Mk...")
44
+ print(f"Score: {rep['score']}, Confidence: {rep['confidence']}")
45
+ ```
46
+ </details>
47
+
48
+ ## Features
49
+
50
+ - **Zero-Config Decorator** — `@avp_tracked()` — auto-register, auto-attest, auto-protect. One line.
51
+ - **DID Identity** — W3C `did:key` (Ed25519). One key = one portable agent identity.
52
+ - **Reputation** — EigenTrust algorithm with Bayesian confidence. Sybil-resistant.
53
+ - **Attestations** — Signed peer-to-peer ratings with cryptographic proof. Negative ratings require evidence.
54
+ - **Dispute Protection** — Contest unfair negative ratings. Arbitrator-resolved, evidence-based.
55
+ - **Agent Cards** — Publish capabilities, find agents by skill. Machine-readable discovery.
56
+ - **Verification** — 4 trust tiers (DID, Email, GitHub, Biometric). Higher tier = more weight.
57
+ - **IPFS Anchoring** — Reputation snapshots anchored to IPFS for public auditability.
58
+
59
+ ## API Overview
60
+
61
+ ### @avp_tracked Decorator
62
+
63
+ ```python
64
+ from agentveil import avp_tracked
65
+
66
+ # Basic — auto-register + auto-attest on success/failure
67
+ @avp_tracked("https://agentveil.dev", name="my_agent", to_did="did:key:z6Mk...")
68
+ def do_work(task: str) -> str:
69
+ return result
70
+
71
+ # With capabilities and custom weight
72
+ @avp_tracked("https://agentveil.dev", name="auditor", to_did="did:key:z6Mk...",
73
+ capabilities=["security_audit"], weight=0.9)
74
+ async def audit(code: str) -> str:
75
+ return await run_audit(code)
76
+ ```
77
+
78
+ Parameters:
79
+ - `base_url` — AVP server URL
80
+ - `name` — Agent name (used for key storage)
81
+ - `to_did` — DID of agent to rate (skip to disable attestation)
82
+ - `capabilities` — Agent capabilities for card (defaults to function name)
83
+ - `weight` — Attestation weight 0.0-1.0 (default 0.8)
84
+
85
+ ### Registration (manual)
86
+
87
+ ```python
88
+ agent = AVPAgent.create(base_url, name="my_agent")
89
+ agent.register(display_name="My Agent")
90
+ ```
91
+
92
+ Keys are saved to `~/.avp/agents/{name}.json` (chmod 0600). Load later with:
93
+
94
+ ```python
95
+ agent = AVPAgent.load(base_url, name="my_agent")
96
+ ```
97
+
98
+ ### Agent Cards (Discovery)
99
+
100
+ ```python
101
+ agent.publish_card(capabilities=["code_review"], provider="anthropic")
102
+ results = agent.search_agents(capability="code_review", min_reputation=0.5)
103
+ ```
104
+
105
+ ### Attestations
106
+
107
+ ```python
108
+ agent.attest(
109
+ to_did="did:key:z6Mk...",
110
+ outcome="positive", # positive / negative / neutral
111
+ weight=0.9, # 0.0 - 1.0
112
+ context="task_completion",
113
+ evidence_hash="sha256_of_interaction_log",
114
+ )
115
+ ```
116
+
117
+ ### Reputation
118
+
119
+ ```python
120
+ rep = agent.get_reputation("did:key:z6Mk...")
121
+ # {"score": 0.85, "confidence": 0.72, "interpretation": "good"}
122
+ ```
123
+
124
+ ## Authentication
125
+
126
+ All write operations are signed with Ed25519:
127
+
128
+ ```
129
+ Authorization: AVP-Sig did="did:key:z6Mk...",ts="1710864000",nonce="random",sig="hex..."
130
+ ```
131
+
132
+ Signature covers: `{method}:{path}:{timestamp}:{nonce}:{body_sha256}`
133
+
134
+ The SDK handles signing automatically.
135
+
136
+ ## Error Handling
137
+
138
+ ```python
139
+ from agentveil import AVPAgent, AVPAuthError, AVPRateLimitError, AVPNotFoundError
140
+
141
+ try:
142
+ agent.attest(did, outcome="positive")
143
+ except AVPAuthError:
144
+ print("Signature invalid or agent not verified")
145
+ except AVPRateLimitError as e:
146
+ print(f"Rate limited, retry after {e.retry_after}s")
147
+ except AVPNotFoundError:
148
+ print("Agent not found")
149
+ ```
150
+
151
+ ## Examples
152
+
153
+ - [`examples/quickstart.py`](examples/quickstart.py) — Register, publish card, check reputation
154
+ - [`examples/two_agents.py`](examples/two_agents.py) — Full A2A interaction with attestations
155
+
156
+ ## License
157
+
158
+ MIT License. See [LICENSE](LICENSE).
@@ -0,0 +1,38 @@
1
+ """
2
+ AVP SDK — Python client for Agent Veil Protocol.
3
+
4
+ Usage:
5
+ from agentveil import AVPAgent
6
+
7
+ agent = AVPAgent.create("https://avp.example.com", name="MyAgent")
8
+ agent.register()
9
+ agent.publish_card(capabilities=["code_review", "testing"], provider="anthropic")
10
+
11
+ rep = agent.get_reputation(other_agent_did)
12
+ agent.attest(other_agent_did, outcome="positive", weight=0.9)
13
+ """
14
+
15
+ from agentveil.agent import AVPAgent
16
+ from agentveil.tracked import avp_tracked, clear_agent_cache
17
+ from agentveil.exceptions import (
18
+ AVPError,
19
+ AVPAuthError,
20
+ AVPNotFoundError,
21
+ AVPRateLimitError,
22
+ AVPInsufficientFundsError,
23
+ AVPValidationError,
24
+ )
25
+
26
+ __version__ = "0.2.0"
27
+
28
+ __all__ = [
29
+ "AVPAgent",
30
+ "avp_tracked",
31
+ "clear_agent_cache",
32
+ "AVPError",
33
+ "AVPAuthError",
34
+ "AVPNotFoundError",
35
+ "AVPRateLimitError",
36
+ "AVPInsufficientFundsError",
37
+ "AVPValidationError",
38
+ ]
@@ -0,0 +1,473 @@
1
+ """
2
+ AVPAgent — main SDK class for interacting with Agent Veil Protocol.
3
+
4
+ Handles key management, authentication, registration, attestations,
5
+ reputation queries, payments, and escrow.
6
+
7
+ Usage:
8
+ agent = AVPAgent.create("https://avp.example.com", name="MyAgent")
9
+ agent.register()
10
+
11
+ agent.attest("did:key:z6Mk...", outcome="positive", weight=0.8)
12
+ rep = agent.get_reputation("did:key:z6Mk...")
13
+ """
14
+
15
+ import json
16
+ import os
17
+ import logging
18
+ from pathlib import Path
19
+ from typing import Optional
20
+
21
+ import httpx
22
+ from nacl.signing import SigningKey, VerifyKey
23
+
24
+ from agentveil.auth import build_auth_header
25
+ from agentveil.exceptions import (
26
+ AVPError,
27
+ AVPAuthError,
28
+ AVPNotFoundError,
29
+ AVPRateLimitError,
30
+ AVPInsufficientFundsError,
31
+ AVPValidationError,
32
+ AVPServerError,
33
+ )
34
+
35
+ log = logging.getLogger("agentveil")
36
+
37
+ # Default storage for agent keys
38
+ AGENTS_DIR = os.path.expanduser("~/.avp/agents")
39
+
40
+ # Multicodec prefix for Ed25519 public key
41
+ ED25519_MULTICODEC = bytes([0xED, 0x01])
42
+
43
+
44
+ def _public_key_to_did(public_key: bytes) -> str:
45
+ """Convert Ed25519 public key to did:key."""
46
+ import base58
47
+ multicodec_key = ED25519_MULTICODEC + public_key
48
+ encoded = base58.b58encode(multicodec_key).decode("ascii")
49
+ return f"did:key:z{encoded}"
50
+
51
+
52
+ class AVPAgent:
53
+ """
54
+ AI agent identity on Agent Veil Protocol.
55
+
56
+ Manages Ed25519 keys, signs requests, and provides methods
57
+ for all AVP operations: registration, attestation, reputation,
58
+ payments, and escrow.
59
+ """
60
+
61
+ def __init__(
62
+ self,
63
+ base_url: str,
64
+ private_key: bytes,
65
+ name: str = "agent",
66
+ timeout: float = 15.0,
67
+ ):
68
+ """
69
+ Initialize agent with existing private key.
70
+ Use AVPAgent.create() or AVPAgent.load() instead of calling directly.
71
+ """
72
+ self._base_url = base_url.rstrip("/")
73
+ self._private_key = private_key
74
+ self._name = name
75
+ self._timeout = timeout
76
+
77
+ # Derive public key and DID
78
+ signing_key = SigningKey(private_key)
79
+ self._public_key = bytes(signing_key.verify_key)
80
+ self._did = _public_key_to_did(self._public_key)
81
+ self._is_registered = False
82
+ self._is_verified = False
83
+
84
+ # === Factory methods ===
85
+
86
+ @classmethod
87
+ def create(cls, base_url: str, name: str = "agent", save: bool = True) -> "AVPAgent":
88
+ """
89
+ Create a new agent with fresh Ed25519 keys.
90
+
91
+ Args:
92
+ base_url: AVP server URL (e.g. "https://avp.example.com")
93
+ name: Agent name (used for local key storage)
94
+ save: Save keys to ~/.avp/agents/{name}.json
95
+ """
96
+ signing_key = SigningKey.generate()
97
+ private_key = bytes(signing_key)
98
+ agent = cls(base_url, private_key, name=name)
99
+ if save:
100
+ agent.save()
101
+ log.info(f"Created new agent: {agent.did[:40]}...")
102
+ return agent
103
+
104
+ @classmethod
105
+ def load(cls, base_url: str, name: str = "agent") -> "AVPAgent":
106
+ """
107
+ Load agent from saved keys.
108
+
109
+ Args:
110
+ base_url: AVP server URL
111
+ name: Agent name (matches saved file)
112
+
113
+ Raises:
114
+ FileNotFoundError: If no saved agent with this name
115
+ """
116
+ path = os.path.join(AGENTS_DIR, f"{name}.json")
117
+ if not os.path.exists(path):
118
+ raise FileNotFoundError(f"No saved agent '{name}' at {path}")
119
+
120
+ with open(path) as f:
121
+ data = json.load(f)
122
+
123
+ private_key = bytes.fromhex(data["private_key_hex"])
124
+ agent = cls(base_url, private_key, name=name)
125
+ agent._is_registered = data.get("registered", False)
126
+ agent._is_verified = data.get("verified", False)
127
+ log.info(f"Loaded agent: {agent.did[:40]}...")
128
+ return agent
129
+
130
+ @classmethod
131
+ def from_private_key(cls, base_url: str, private_key_hex: str, name: str = "agent") -> "AVPAgent":
132
+ """Create agent from hex-encoded private key."""
133
+ return cls(base_url, bytes.fromhex(private_key_hex), name=name)
134
+
135
+ # === Properties ===
136
+
137
+ @property
138
+ def did(self) -> str:
139
+ """Agent's DID (did:key:z6Mk...)."""
140
+ return self._did
141
+
142
+ @property
143
+ def public_key_hex(self) -> str:
144
+ """Agent's public key as hex string."""
145
+ return self._public_key.hex()
146
+
147
+ @property
148
+ def is_registered(self) -> bool:
149
+ return self._is_registered
150
+
151
+ @property
152
+ def is_verified(self) -> bool:
153
+ return self._is_verified
154
+
155
+ # === Key management ===
156
+
157
+ def save(self) -> str:
158
+ """Save agent keys to disk. Returns file path."""
159
+ os.makedirs(AGENTS_DIR, exist_ok=True)
160
+ path = os.path.join(AGENTS_DIR, f"{self._name}.json")
161
+ with open(path, "w") as f:
162
+ json.dump({
163
+ "name": self._name,
164
+ "did": self._did,
165
+ "public_key_hex": self._public_key.hex(),
166
+ "private_key_hex": self._private_key.hex(),
167
+ "registered": self._is_registered,
168
+ "verified": self._is_verified,
169
+ "base_url": self._base_url,
170
+ }, f, indent=2)
171
+ os.chmod(path, 0o600) # Owner read/write only
172
+ return path
173
+
174
+ # === HTTP helpers ===
175
+
176
+ def _auth_headers(self, method: str, path: str, body: bytes = b"") -> dict:
177
+ """Build authenticated headers for a request."""
178
+ return build_auth_header(self._private_key, self._did, method, path, body)
179
+
180
+ def _handle_response(self, response: httpx.Response) -> dict:
181
+ """Parse response and raise appropriate exceptions."""
182
+ if response.status_code == 200:
183
+ return response.json()
184
+
185
+ try:
186
+ detail = response.json().get("detail", response.text)
187
+ except Exception:
188
+ detail = response.text
189
+
190
+ if response.status_code == 401:
191
+ raise AVPAuthError(f"Authentication failed: {detail}", 401, detail)
192
+ elif response.status_code == 403:
193
+ raise AVPAuthError(f"Forbidden: {detail}", 403, detail)
194
+ elif response.status_code == 404:
195
+ raise AVPNotFoundError(f"Not found: {detail}", 404, detail)
196
+ elif response.status_code == 409:
197
+ raise AVPValidationError(f"Conflict: {detail}", 409, detail)
198
+ elif response.status_code == 429:
199
+ raise AVPRateLimitError(f"Rate limited: {detail}")
200
+ elif response.status_code == 400:
201
+ if "Insufficient" in str(detail):
202
+ raise AVPInsufficientFundsError(f"Insufficient funds: {detail}", 400, detail)
203
+ raise AVPValidationError(f"Validation error: {detail}", 400, detail)
204
+ elif response.status_code >= 500:
205
+ raise AVPServerError(f"Server error: {detail}", response.status_code, detail)
206
+ else:
207
+ raise AVPError(f"Unexpected error ({response.status_code}): {detail}", response.status_code, detail)
208
+
209
+ # === Registration ===
210
+
211
+ def register(self, display_name: Optional[str] = None) -> dict:
212
+ """
213
+ Register agent on AVP and verify key ownership.
214
+ Does both steps (register + verify) in one call.
215
+
216
+ Args:
217
+ display_name: Optional human-readable name
218
+
219
+ Returns:
220
+ dict with 'did' and 'agnet_address'
221
+ """
222
+ with httpx.Client(base_url=self._base_url, timeout=self._timeout) as c:
223
+ # Step 1: Register
224
+ r = c.post("/v1/agents/register", json={
225
+ "public_key_hex": self.public_key_hex,
226
+ "display_name": display_name or self._name,
227
+ })
228
+ data = self._handle_response(r)
229
+ challenge = data["challenge"]
230
+
231
+ # Step 2: Sign challenge and verify
232
+ signing_key = SigningKey(self._private_key)
233
+ signed = signing_key.sign(challenge.encode())
234
+ sig_hex = signed.signature.hex()
235
+
236
+ r = c.post("/v1/agents/verify", json={
237
+ "did": self._did,
238
+ "challenge": challenge,
239
+ "signature_hex": sig_hex,
240
+ })
241
+ self._handle_response(r)
242
+
243
+ self._is_registered = True
244
+ self._is_verified = True
245
+ self.save()
246
+ log.info(f"Registered and verified: {self._did[:40]}...")
247
+ return data
248
+
249
+ # === Agent Cards ===
250
+
251
+ def publish_card(
252
+ self,
253
+ capabilities: list[str],
254
+ provider: Optional[str] = None,
255
+ endpoint_url: Optional[str] = None,
256
+ ) -> dict:
257
+ """
258
+ Publish or update agent's capability card.
259
+
260
+ Args:
261
+ capabilities: List of capabilities (e.g. ["code_review", "testing"])
262
+ provider: LLM provider (e.g. "anthropic", "openai")
263
+ endpoint_url: URL where this agent can be reached
264
+ """
265
+ body_data = {"capabilities": capabilities}
266
+ if provider:
267
+ body_data["provider"] = provider
268
+ if endpoint_url:
269
+ body_data["endpoint_url"] = endpoint_url
270
+
271
+ body = json.dumps(body_data).encode()
272
+ headers = self._auth_headers("POST", "/v1/cards", body)
273
+
274
+ with httpx.Client(base_url=self._base_url, timeout=self._timeout) as c:
275
+ r = c.post("/v1/cards", content=body, headers=headers)
276
+ return self._handle_response(r)
277
+
278
+ def search_agents(
279
+ self,
280
+ capability: Optional[str] = None,
281
+ provider: Optional[str] = None,
282
+ min_reputation: Optional[float] = None,
283
+ limit: int = 20,
284
+ ) -> list[dict]:
285
+ """Search for agents by capability, provider, or minimum reputation."""
286
+ params = {"limit": limit}
287
+ if capability:
288
+ params["capability"] = capability
289
+ if provider:
290
+ params["provider"] = provider
291
+ if min_reputation is not None:
292
+ params["min_reputation"] = min_reputation
293
+
294
+ with httpx.Client(base_url=self._base_url, timeout=self._timeout) as c:
295
+ r = c.get("/v1/cards", params=params)
296
+ return self._handle_response(r)
297
+
298
+ # === Attestations ===
299
+
300
+ def attest(
301
+ self,
302
+ to_did: str,
303
+ outcome: str = "positive",
304
+ weight: float = 1.0,
305
+ context: Optional[str] = None,
306
+ evidence_hash: Optional[str] = None,
307
+ ) -> dict:
308
+ """
309
+ Submit an attestation about another agent.
310
+
311
+ Args:
312
+ to_did: DID of agent being rated
313
+ outcome: "positive", "negative", or "neutral"
314
+ weight: Confidence weight (0.0 to 1.0)
315
+ context: Interaction type (e.g. "task_completion")
316
+ evidence_hash: SHA256 of interaction log
317
+
318
+ Returns:
319
+ Attestation details
320
+ """
321
+ if outcome not in ("positive", "negative", "neutral"):
322
+ raise AVPValidationError(f"Invalid outcome: {outcome}. Must be positive/negative/neutral")
323
+ if not 0.0 <= weight <= 1.0:
324
+ raise AVPValidationError(f"Weight must be 0.0-1.0, got {weight}")
325
+
326
+ # Build and sign attestation payload
327
+ attest_payload = json.dumps({
328
+ "to": to_did,
329
+ "outcome": outcome,
330
+ "weight": weight,
331
+ "context": context or "",
332
+ "evidence_hash": evidence_hash or "",
333
+ }, sort_keys=True, separators=(",", ":")).encode()
334
+
335
+ signing_key = SigningKey(self._private_key)
336
+ signed = signing_key.sign(attest_payload)
337
+ sig_hex = signed.signature.hex()
338
+
339
+ body_data = {
340
+ "to_agent_did": to_did,
341
+ "outcome": outcome,
342
+ "weight": weight,
343
+ "signature": sig_hex,
344
+ }
345
+ if context:
346
+ body_data["context"] = context
347
+ if evidence_hash:
348
+ body_data["evidence_hash"] = evidence_hash
349
+
350
+ body = json.dumps(body_data).encode()
351
+ headers = self._auth_headers("POST", "/v1/attestations", body)
352
+
353
+ with httpx.Client(base_url=self._base_url, timeout=self._timeout) as c:
354
+ r = c.post("/v1/attestations", content=body, headers=headers)
355
+ return self._handle_response(r)
356
+
357
+ # === Reputation ===
358
+
359
+ def get_reputation(self, did: Optional[str] = None) -> dict:
360
+ """
361
+ Get reputation score for an agent.
362
+
363
+ Args:
364
+ did: Agent DID (defaults to self)
365
+
366
+ Returns:
367
+ dict with score, confidence, interpretation
368
+ """
369
+ target = did or self._did
370
+ with httpx.Client(base_url=self._base_url, timeout=self._timeout) as c:
371
+ r = c.get(f"/v1/reputation/{target}")
372
+ return self._handle_response(r)
373
+
374
+ def get_agent_info(self, did: Optional[str] = None) -> dict:
375
+ """Get public info about an agent."""
376
+ target = did or self._did
377
+ with httpx.Client(base_url=self._base_url, timeout=self._timeout) as c:
378
+ r = c.get(f"/v1/agents/{target}")
379
+ return self._handle_response(r)
380
+
381
+ # === Payments ===
382
+
383
+ def get_balance(self, did: Optional[str] = None) -> dict:
384
+ """Get agent's balance (internal + Agnet)."""
385
+ target = did or self._did
386
+ with httpx.Client(base_url=self._base_url, timeout=self._timeout) as c:
387
+ r = c.get(f"/v1/payments/balance/{target}")
388
+ return self._handle_response(r)
389
+
390
+ def transfer(self, to_did: str, amount_nagn: int, memo: Optional[str] = None) -> dict:
391
+ """
392
+ Transfer funds to another agent.
393
+
394
+ Args:
395
+ to_did: Recipient DID
396
+ amount_nagn: Amount in nAGN (1 AGN = 1,000,000 nAGN)
397
+ memo: Optional memo
398
+ """
399
+ body_data = {"to_agent_did": to_did, "amount_nagn": amount_nagn}
400
+ if memo:
401
+ body_data["memo"] = memo
402
+
403
+ body = json.dumps(body_data).encode()
404
+ headers = self._auth_headers("POST", "/v1/payments/transfer", body)
405
+
406
+ with httpx.Client(base_url=self._base_url, timeout=self._timeout) as c:
407
+ r = c.post("/v1/payments/transfer", content=body, headers=headers)
408
+ return self._handle_response(r)
409
+
410
+ # === Escrow ===
411
+
412
+ def create_escrow(
413
+ self,
414
+ payee_did: str,
415
+ amount_nagn: int,
416
+ ttl_hours: int = 24,
417
+ memo: Optional[str] = None,
418
+ ) -> dict:
419
+ """
420
+ Create an escrow — hold funds until interaction completes.
421
+
422
+ Args:
423
+ payee_did: Agent who will receive funds on success
424
+ amount_nagn: Amount to hold
425
+ ttl_hours: Hours before auto-refund (1-168)
426
+ memo: Optional memo
427
+ """
428
+ body_data = {
429
+ "payee_did": payee_did,
430
+ "amount_nagn": amount_nagn,
431
+ "ttl_hours": ttl_hours,
432
+ }
433
+ if memo:
434
+ body_data["memo"] = memo
435
+
436
+ body = json.dumps(body_data).encode()
437
+ headers = self._auth_headers("POST", "/v1/payments/escrow", body)
438
+
439
+ with httpx.Client(base_url=self._base_url, timeout=self._timeout) as c:
440
+ r = c.post("/v1/payments/escrow", content=body, headers=headers)
441
+ return self._handle_response(r)
442
+
443
+ def release_escrow(self, escrow_id: str) -> dict:
444
+ """Release escrow — send funds to payee. Only payee can call this."""
445
+ body_data = {"escrow_id": escrow_id, "action": "release"}
446
+ body = json.dumps(body_data).encode()
447
+ headers = self._auth_headers("POST", "/v1/payments/escrow/resolve", body)
448
+
449
+ with httpx.Client(base_url=self._base_url, timeout=self._timeout) as c:
450
+ r = c.post("/v1/payments/escrow/resolve", content=body, headers=headers)
451
+ return self._handle_response(r)
452
+
453
+ def refund_escrow(self, escrow_id: str) -> dict:
454
+ """Refund escrow — return funds to payer. Only payer can call this."""
455
+ body_data = {"escrow_id": escrow_id, "action": "refund"}
456
+ body = json.dumps(body_data).encode()
457
+ headers = self._auth_headers("POST", "/v1/payments/escrow/resolve", body)
458
+
459
+ with httpx.Client(base_url=self._base_url, timeout=self._timeout) as c:
460
+ r = c.post("/v1/payments/escrow/resolve", content=body, headers=headers)
461
+ return self._handle_response(r)
462
+
463
+ # === Utility ===
464
+
465
+ def health(self) -> dict:
466
+ """Check AVP server health."""
467
+ with httpx.Client(base_url=self._base_url, timeout=self._timeout) as c:
468
+ r = c.get("/v1/health")
469
+ return self._handle_response(r)
470
+
471
+ def __repr__(self) -> str:
472
+ status = "verified" if self._is_verified else ("registered" if self._is_registered else "new")
473
+ return f"AVPAgent(name={self._name}, did={self._did[:35]}..., status={status})"
@@ -0,0 +1,45 @@
1
+ """
2
+ AVP authentication helpers.
3
+ Builds signed Authorization headers for API requests.
4
+ """
5
+
6
+ import hashlib
7
+ import secrets
8
+ import time
9
+
10
+ from nacl.signing import SigningKey
11
+
12
+
13
+ def build_auth_header(
14
+ private_key: bytes,
15
+ did: str,
16
+ method: str,
17
+ path: str,
18
+ body: bytes = b"",
19
+ ) -> dict[str, str]:
20
+ """
21
+ Build AVP-Sig Authorization header.
22
+
23
+ Signs: {method}:{path}:{timestamp}:{nonce}:{body_sha256}
24
+ Returns dict with Authorization header ready for httpx.
25
+
26
+ Args:
27
+ private_key: Ed25519 private key (32 bytes)
28
+ did: Agent's DID (did:key:z6Mk...)
29
+ method: HTTP method (GET, POST, etc.)
30
+ path: URL path (/v1/attestations)
31
+ body: Request body bytes
32
+ """
33
+ ts = int(time.time())
34
+ nonce = secrets.token_hex(16)
35
+ body_hash = hashlib.sha256(body).hexdigest()
36
+
37
+ message = f"{method}:{path}:{ts}:{nonce}:{body_hash}"
38
+ signing_key = SigningKey(private_key)
39
+ signed = signing_key.sign(message.encode())
40
+ sig_hex = signed.signature.hex()
41
+
42
+ return {
43
+ "Authorization": f'AVP-Sig did="{did}",ts="{ts}",nonce="{nonce}",sig="{sig_hex}"',
44
+ "Content-Type": "application/json",
45
+ }
@@ -0,0 +1,44 @@
1
+ """AVP SDK exceptions — clear, actionable error messages."""
2
+
3
+
4
+ class AVPError(Exception):
5
+ """Base exception for all AVP SDK errors."""
6
+
7
+ def __init__(self, message: str, status_code: int = 0, detail: str = ""):
8
+ self.message = message
9
+ self.status_code = status_code
10
+ self.detail = detail
11
+ super().__init__(message)
12
+
13
+
14
+ class AVPAuthError(AVPError):
15
+ """Authentication failed — invalid signature, expired timestamp, or nonce reuse."""
16
+ pass
17
+
18
+
19
+ class AVPNotFoundError(AVPError):
20
+ """Resource not found — agent, card, escrow, etc."""
21
+ pass
22
+
23
+
24
+ class AVPRateLimitError(AVPError):
25
+ """Rate limit exceeded — wait and retry."""
26
+
27
+ def __init__(self, message: str = "Rate limit exceeded", retry_after: int = 60):
28
+ self.retry_after = retry_after
29
+ super().__init__(message, status_code=429)
30
+
31
+
32
+ class AVPInsufficientFundsError(AVPError):
33
+ """Not enough balance for the operation."""
34
+ pass
35
+
36
+
37
+ class AVPValidationError(AVPError):
38
+ """Invalid input data."""
39
+ pass
40
+
41
+
42
+ class AVPServerError(AVPError):
43
+ """Server-side error — retry later."""
44
+ pass
@@ -0,0 +1,227 @@
1
+ """
2
+ Universal @avp_tracked decorator — one line = full AVP integration.
3
+
4
+ Usage:
5
+ from agentveil import avp_tracked
6
+
7
+ @avp_tracked("https://agentveil.dev", name="my_agent", capabilities=["code_review"])
8
+ def review_code(pr_url: str) -> str:
9
+ # Your logic here
10
+ return analysis
11
+
12
+ # That's it. The decorator handles:
13
+ # - Auto-registration on first call
14
+ # - Positive attestation on success
15
+ # - Negative attestation with evidence on failure
16
+ # - Agent card publishing with capabilities
17
+
18
+ Works with sync and async functions, any framework.
19
+ """
20
+
21
+ import asyncio
22
+ import functools
23
+ import hashlib
24
+ import inspect
25
+ import logging
26
+ import traceback
27
+ from typing import Optional
28
+
29
+ from agentveil.agent import AVPAgent
30
+ from agentveil.exceptions import AVPError
31
+
32
+ log = logging.getLogger("agentveil.tracked")
33
+
34
+ # Cache of initialized agents (by name) to avoid re-registration
35
+ _agent_cache: dict[str, AVPAgent] = {}
36
+
37
+
38
+ def _get_or_create_agent(
39
+ base_url: str,
40
+ name: str,
41
+ capabilities: list[str],
42
+ provider: Optional[str],
43
+ ) -> AVPAgent:
44
+ """Get cached agent or create+register a new one."""
45
+ if name in _agent_cache:
46
+ return _agent_cache[name]
47
+
48
+ # Try loading existing agent
49
+ try:
50
+ agent = AVPAgent.load(base_url, name=name)
51
+ if agent.is_verified:
52
+ _agent_cache[name] = agent
53
+ log.info(f"Loaded existing agent: {name}")
54
+ return agent
55
+ except FileNotFoundError:
56
+ pass
57
+
58
+ # Create and register new agent
59
+ agent = AVPAgent.create(base_url, name=name, save=True)
60
+ try:
61
+ agent.register(display_name=name)
62
+ log.info(f"Auto-registered agent: {name} ({agent.did[:40]}...)")
63
+ except AVPError as e:
64
+ # Already registered (409) — load state and continue
65
+ if e.status_code == 409:
66
+ log.info(f"Agent already registered: {name}")
67
+ agent._is_registered = True
68
+ agent._is_verified = True
69
+ agent.save()
70
+ else:
71
+ log.warning(f"Registration failed: {e}")
72
+ raise
73
+
74
+ # Publish capabilities card
75
+ if capabilities:
76
+ try:
77
+ agent.publish_card(capabilities=capabilities, provider=provider)
78
+ log.info(f"Published card: {capabilities}")
79
+ except AVPError as e:
80
+ log.warning(f"Card publish failed (non-fatal): {e}")
81
+
82
+ _agent_cache[name] = agent
83
+ return agent
84
+
85
+
86
+ def _make_evidence_hash(exc: Exception) -> str:
87
+ """Create SHA256 hash of exception traceback for evidence."""
88
+ tb = traceback.format_exception(type(exc), exc, exc.__traceback__)
89
+ tb_text = "".join(tb)
90
+ return hashlib.sha256(tb_text.encode()).hexdigest()
91
+
92
+
93
+ def _derive_context(func_name: str) -> str:
94
+ """Derive attestation context from function name."""
95
+ # Sanitize: only alphanumeric, underscore, hyphen, dot (AVP context rules)
96
+ clean = "".join(c if c.isalnum() or c in ("_", "-", ".") else "_" for c in func_name)
97
+ return clean[:100]
98
+
99
+
100
+ def avp_tracked(
101
+ base_url: str,
102
+ *,
103
+ name: str = "agent",
104
+ to_did: Optional[str] = None,
105
+ capabilities: Optional[list[str]] = None,
106
+ provider: Optional[str] = None,
107
+ weight: float = 0.8,
108
+ attest_self: bool = False,
109
+ ):
110
+ """
111
+ Decorator that integrates any function with Agent Veil Protocol.
112
+
113
+ On first call: auto-registers agent (if not registered).
114
+ On success: submits positive attestation.
115
+ On exception: submits negative attestation with stack trace hash as evidence.
116
+
117
+ Args:
118
+ base_url: AVP server URL (e.g. "https://agentveil.dev")
119
+ name: Agent name (used for key storage and display)
120
+ to_did: DID of agent to attest (required unless attest_self=True)
121
+ capabilities: Agent capabilities for card (defaults to [function_name])
122
+ provider: LLM provider for card (e.g. "anthropic")
123
+ weight: Attestation weight 0.0-1.0 (default 0.8)
124
+ attest_self: If True and to_did is None, skip attestation (no self-attest)
125
+
126
+ Usage:
127
+ @avp_tracked("https://agentveil.dev", name="reviewer", to_did="did:key:z6Mk...")
128
+ def review_code(code: str) -> str:
129
+ return "LGTM"
130
+
131
+ @avp_tracked("https://agentveil.dev", name="reviewer", capabilities=["code_review"])
132
+ async def review_code(code: str) -> str:
133
+ return "LGTM"
134
+ """
135
+
136
+ def decorator(func):
137
+ func_name = func.__name__
138
+ caps = capabilities if capabilities is not None else [func_name]
139
+ context = _derive_context(func_name)
140
+
141
+ if inspect.iscoroutinefunction(func):
142
+ @functools.wraps(func)
143
+ async def async_wrapper(*args, **kwargs):
144
+ agent = _get_or_create_agent(base_url, name, caps, provider)
145
+ target_did = to_did
146
+
147
+ try:
148
+ result = await func(*args, **kwargs)
149
+
150
+ # Positive attestation on success
151
+ if target_did:
152
+ try:
153
+ agent.attest(
154
+ to_did=target_did,
155
+ outcome="positive",
156
+ weight=weight,
157
+ context=context,
158
+ )
159
+ except AVPError as e:
160
+ log.warning(f"Positive attestation failed (non-fatal): {e}")
161
+
162
+ return result
163
+
164
+ except Exception as exc:
165
+ # Negative attestation on failure
166
+ if target_did:
167
+ evidence = _make_evidence_hash(exc)
168
+ try:
169
+ agent.attest(
170
+ to_did=target_did,
171
+ outcome="negative",
172
+ weight=weight,
173
+ context=context,
174
+ evidence_hash=evidence,
175
+ )
176
+ except AVPError as e:
177
+ log.warning(f"Negative attestation failed (non-fatal): {e}")
178
+ raise
179
+
180
+ return async_wrapper
181
+ else:
182
+ @functools.wraps(func)
183
+ def sync_wrapper(*args, **kwargs):
184
+ agent = _get_or_create_agent(base_url, name, caps, provider)
185
+ target_did = to_did
186
+
187
+ try:
188
+ result = func(*args, **kwargs)
189
+
190
+ # Positive attestation on success
191
+ if target_did:
192
+ try:
193
+ agent.attest(
194
+ to_did=target_did,
195
+ outcome="positive",
196
+ weight=weight,
197
+ context=context,
198
+ )
199
+ except AVPError as e:
200
+ log.warning(f"Positive attestation failed (non-fatal): {e}")
201
+
202
+ return result
203
+
204
+ except Exception as exc:
205
+ # Negative attestation on failure
206
+ if target_did:
207
+ evidence = _make_evidence_hash(exc)
208
+ try:
209
+ agent.attest(
210
+ to_did=target_did,
211
+ outcome="negative",
212
+ weight=weight,
213
+ context=context,
214
+ evidence_hash=evidence,
215
+ )
216
+ except AVPError as e:
217
+ log.warning(f"Negative attestation failed (non-fatal): {e}")
218
+ raise
219
+
220
+ return sync_wrapper
221
+
222
+ return decorator
223
+
224
+
225
+ def clear_agent_cache():
226
+ """Clear the cached agents. Useful for testing."""
227
+ _agent_cache.clear()
@@ -0,0 +1,24 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentveil
3
+ Version: 0.2.0
4
+ Summary: Python SDK for Agent Veil Protocol — reputation and identity for AI agents
5
+ Author: Agent Veil Protocol
6
+ License: MIT
7
+ Project-URL: Homepage, https://agentveil.dev
8
+ Project-URL: Documentation, https://agentveil.dev/docs
9
+ Project-URL: Repository, https://github.com/creatorrmode-lead/avp
10
+ Keywords: ai,agents,reputation,did,identity,a2a
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Libraries
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
+ Classifier: Programming Language :: Python :: 3.13
19
+ Requires-Python: >=3.10
20
+ License-File: LICENSE
21
+ Requires-Dist: httpx>=0.27.0
22
+ Requires-Dist: pynacl>=1.5.0
23
+ Requires-Dist: base58>=2.1.0
24
+ Dynamic: license-file
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ agentveil/__init__.py
5
+ agentveil/agent.py
6
+ agentveil/auth.py
7
+ agentveil/exceptions.py
8
+ agentveil/tracked.py
9
+ agentveil.egg-info/PKG-INFO
10
+ agentveil.egg-info/SOURCES.txt
11
+ agentveil.egg-info/dependency_links.txt
12
+ agentveil.egg-info/requires.txt
13
+ agentveil.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ httpx>=0.27.0
2
+ pynacl>=1.5.0
3
+ base58>=2.1.0
@@ -0,0 +1 @@
1
+ agentveil
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "agentveil"
3
+ version = "0.2.0"
4
+ description = "Python SDK for Agent Veil Protocol — reputation and identity for AI agents"
5
+ requires-python = ">=3.10"
6
+ license = {text = "MIT"}
7
+ authors = [{name = "Agent Veil Protocol"}]
8
+ keywords = ["ai", "agents", "reputation", "did", "identity", "a2a"]
9
+ classifiers = [
10
+ "Development Status :: 3 - Alpha",
11
+ "Intended Audience :: Developers",
12
+ "Topic :: Software Development :: Libraries",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.10",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ ]
19
+
20
+ dependencies = [
21
+ "httpx>=0.27.0",
22
+ "pynacl>=1.5.0",
23
+ "base58>=2.1.0",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://agentveil.dev"
28
+ Documentation = "https://agentveil.dev/docs"
29
+ Repository = "https://github.com/creatorrmode-lead/avp"
30
+
31
+ [build-system]
32
+ requires = ["setuptools>=68.0"]
33
+ build-backend = "setuptools.build_meta"
34
+
35
+ [tool.setuptools.packages.find]
36
+ include = ["agentveil*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+