xproof 0.2.6__tar.gz → 0.2.7__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.
- xproof-0.2.7/PKG-INFO +800 -0
- xproof-0.2.7/README.md +754 -0
- xproof-0.2.7/pyproject.toml +132 -0
- {xproof-0.2.6 → xproof-0.2.7}/tests/test_client.py +180 -1
- xproof-0.2.7/tests/test_confidence_trail.py +360 -0
- xproof-0.2.7/tests/test_context_drift.py +244 -0
- {xproof-0.2.6 → xproof-0.2.7}/tests/test_integration.py +1 -2
- xproof-0.2.7/tests/test_langchain_tool.py +343 -0
- xproof-0.2.7/tests/test_policy_check.py +186 -0
- {xproof-0.2.6 → xproof-0.2.7}/xproof/__init__.py +13 -1
- {xproof-0.2.6 → xproof-0.2.7}/xproof/client.py +32 -21
- {xproof-0.2.6 → xproof-0.2.7}/xproof/exceptions.py +19 -0
- {xproof-0.2.6 → xproof-0.2.7}/xproof/integrations/autogen.py +141 -5
- xproof-0.2.7/xproof/integrations/crewai.py +440 -0
- {xproof-0.2.6 → xproof-0.2.7}/xproof/integrations/deerflow.py +1 -1
- {xproof-0.2.6 → xproof-0.2.7}/xproof/integrations/fetchai.py +88 -21
- {xproof-0.2.6 → xproof-0.2.7}/xproof/integrations/langchain.py +7 -6
- {xproof-0.2.6 → xproof-0.2.7}/xproof/integrations/llamaindex.py +6 -5
- {xproof-0.2.6 → xproof-0.2.7}/xproof/integrations/openai_agents.py +7 -6
- xproof-0.2.7/xproof/langchain_tool.py +314 -0
- {xproof-0.2.6 → xproof-0.2.7}/xproof/models.py +78 -1
- xproof-0.2.7/xproof.egg-info/PKG-INFO +800 -0
- {xproof-0.2.6 → xproof-0.2.7}/xproof.egg-info/SOURCES.txt +5 -0
- {xproof-0.2.6 → xproof-0.2.7}/xproof.egg-info/requires.txt +5 -0
- xproof-0.2.6/PKG-INFO +0 -283
- xproof-0.2.6/README.md +0 -240
- xproof-0.2.6/pyproject.toml +0 -73
- xproof-0.2.6/xproof/integrations/crewai.py +0 -225
- xproof-0.2.6/xproof.egg-info/PKG-INFO +0 -283
- {xproof-0.2.6 → xproof-0.2.7}/LICENSE +0 -0
- {xproof-0.2.6 → xproof-0.2.7}/setup.cfg +0 -0
- {xproof-0.2.6 → xproof-0.2.7}/xproof/integrations/__init__.py +0 -0
- {xproof-0.2.6 → xproof-0.2.7}/xproof/py.typed +0 -0
- {xproof-0.2.6 → xproof-0.2.7}/xproof/utils.py +0 -0
- {xproof-0.2.6 → xproof-0.2.7}/xproof.egg-info/dependency_links.txt +0 -0
- {xproof-0.2.6 → xproof-0.2.7}/xproof.egg-info/top_level.txt +0 -0
xproof-0.2.7/PKG-INFO
ADDED
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xproof
|
|
3
|
+
Version: 0.2.7
|
|
4
|
+
Summary: Python SDK for xProof — blockchain-anchored proof-of-existence for AI agents on MultiversX
|
|
5
|
+
Author-email: xProof <contact@xproof.app>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://xproof.app
|
|
8
|
+
Project-URL: Repository, https://github.com/jasonxkensei/xproof
|
|
9
|
+
Project-URL: Documentation, https://xproof.app/docs
|
|
10
|
+
Project-URL: Issues, https://github.com/jasonxkensei/xproof/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/jasonxkensei/xproof/releases
|
|
12
|
+
Keywords: blockchain,multiversx,proof-of-existence,certification,sha256,ai-agents,accountability,audit-trail,web3
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Security :: Cryptography
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: requests>=2.25.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
31
|
+
Requires-Dist: responses>=0.20.0; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
33
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
34
|
+
Requires-Dist: types-requests>=2.25.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pre-commit>=3.0; extra == "dev"
|
|
36
|
+
Provides-Extra: llamaindex
|
|
37
|
+
Requires-Dist: llama-index-core>=0.13.0; extra == "llamaindex"
|
|
38
|
+
Provides-Extra: autogen
|
|
39
|
+
Requires-Dist: pyautogen>=0.2.0; extra == "autogen"
|
|
40
|
+
Provides-Extra: openai-agents
|
|
41
|
+
Requires-Dist: openai-agents>=0.0.3; extra == "openai-agents"
|
|
42
|
+
Provides-Extra: deerflow
|
|
43
|
+
Provides-Extra: fetchai
|
|
44
|
+
Requires-Dist: uagents>=0.9.0; extra == "fetchai"
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
|
|
47
|
+
# xproof
|
|
48
|
+
|
|
49
|
+
[](https://github.com/jasonxkensei/xproof/actions/workflows/python-sdk.yml) [](https://pypi.org/project/xproof/) [](https://pypi.org/project/xproof/)
|
|
50
|
+
|
|
51
|
+
On-chain decision provenance for autonomous agents. **WHY before acting. WHAT after.** Timestamps written by the chain, not your agent.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install xproof
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 3 steps. 30 seconds.
|
|
60
|
+
|
|
61
|
+
### Step 1 — Register (no wallet, no payment)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
curl -X POST https://xproof.app/api/agent/register \
|
|
65
|
+
-H "Content-Type: application/json" \
|
|
66
|
+
-d '{"agent_name": "my-agent"}'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{ "api_key": "pm_...", "trial": { "remaining": 10 } }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Step 2 — Anchor WHY before acting
|
|
74
|
+
|
|
75
|
+
Hash your reasoning and certify it *before* your agent executes.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
curl -X POST https://xproof.app/api/proof \
|
|
79
|
+
-H "Authorization: Bearer pm_..." \
|
|
80
|
+
-H "Content-Type: application/json" \
|
|
81
|
+
-d '{
|
|
82
|
+
"file_hash": "<sha256_of_reasoning>",
|
|
83
|
+
"file_name": "reasoning.json",
|
|
84
|
+
"author": "my-agent",
|
|
85
|
+
"metadata": { "action_type": "decision" }
|
|
86
|
+
}'
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{ "id": "why-proof-uuid", "transaction_hash": "0x..." }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Step 3 — Anchor WHAT after acting
|
|
94
|
+
|
|
95
|
+
Hash your output and link it to the WHY proof.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
curl -X POST https://xproof.app/api/proof \
|
|
99
|
+
-H "Authorization: Bearer pm_..." \
|
|
100
|
+
-H "Content-Type: application/json" \
|
|
101
|
+
-d '{
|
|
102
|
+
"file_hash": "<sha256_of_output>",
|
|
103
|
+
"file_name": "output.json",
|
|
104
|
+
"author": "my-agent",
|
|
105
|
+
"metadata": { "action_type": "execution", "why_proof_id": "why-proof-uuid" }
|
|
106
|
+
}'
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{ "id": "what-proof-uuid", "transaction_hash": "0x..." }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
When something goes wrong, you don't guess. You verify.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Python SDK
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from xproof import XProofClient, hash_string
|
|
121
|
+
|
|
122
|
+
# Register — zero-friction, no wallet, no payment
|
|
123
|
+
client = XProofClient.register("my-agent")
|
|
124
|
+
# 10 free certs, API key stored automatically
|
|
125
|
+
|
|
126
|
+
# Step 2: Anchor WHY before acting
|
|
127
|
+
why = client.certify_hash(
|
|
128
|
+
file_hash=hash_string('{"action": "summarize", "model": "gpt-4"}'),
|
|
129
|
+
file_name="reasoning.json",
|
|
130
|
+
author="my-agent",
|
|
131
|
+
metadata={"action_type": "decision"},
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Step 3: Anchor WHAT after acting
|
|
135
|
+
what = client.certify_hash(
|
|
136
|
+
file_hash=hash_string(execution_output),
|
|
137
|
+
file_name="output.json",
|
|
138
|
+
author="my-agent",
|
|
139
|
+
metadata={"action_type": "execution", "why_proof_id": why.id},
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
print(what.transaction_url) # MultiversX explorer link
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Or use an existing API key:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
client = XProofClient(api_key="pm_your_api_key")
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Framework Integrations
|
|
154
|
+
|
|
155
|
+
### LangChain
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from xproof.integrations.langchain import XProofCallbackHandler
|
|
159
|
+
|
|
160
|
+
handler = XProofCallbackHandler(api_key="pm_...")
|
|
161
|
+
llm = ChatOpenAI(callbacks=[handler])
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### CrewAI
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from xproof.integrations.crewai import XProofListener
|
|
168
|
+
|
|
169
|
+
listener = XProofListener(api_key="pm_...")
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### AutoGen
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from xproof.integrations.autogen import XProofHook
|
|
176
|
+
|
|
177
|
+
hook = XProofHook(api_key="pm_...")
|
|
178
|
+
agent.register_hook("process_last_received_message", hook.on_message)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### LlamaIndex
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from xproof.integrations.llamaindex import XProofCallbackHandler
|
|
185
|
+
from llama_index.core import Settings
|
|
186
|
+
from llama_index.core.callbacks import CallbackManager
|
|
187
|
+
|
|
188
|
+
Settings.callback_manager = CallbackManager([XProofCallbackHandler(api_key="pm_...")])
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 4W Framework (WHO / WHAT / WHEN / WHY)
|
|
194
|
+
|
|
195
|
+
Full accountability metadata on every certification:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from xproof import XProofClient, hash_bytes
|
|
199
|
+
|
|
200
|
+
client = XProofClient(api_key="pm_your_key")
|
|
201
|
+
|
|
202
|
+
action_data = b'{"action": "generate_report", "model": "gpt-4"}'
|
|
203
|
+
action_hash = hash_bytes(action_data)
|
|
204
|
+
|
|
205
|
+
cert = client.certify_hash(
|
|
206
|
+
file_hash=action_hash,
|
|
207
|
+
file_name="agent-action.json",
|
|
208
|
+
author="research-agent",
|
|
209
|
+
who="erd1abc...or-agent-id",
|
|
210
|
+
what=action_hash,
|
|
211
|
+
when="2026-03-20T12:00:00Z",
|
|
212
|
+
why=hash_bytes(b"Summarize Q1 earnings report"),
|
|
213
|
+
metadata={"model": "gpt-4", "session_id": "sess-123"},
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Batch Certification
|
|
218
|
+
|
|
219
|
+
Certify up to 50 files in a single API call:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
results = client.batch_certify([
|
|
223
|
+
{"file_hash": "abc123...", "file_name": "file1.pdf", "author": "my-agent"},
|
|
224
|
+
{"file_hash": "def456...", "file_name": "file2.pdf"},
|
|
225
|
+
])
|
|
226
|
+
|
|
227
|
+
print(results.summary.total) # 2
|
|
228
|
+
print(results.summary.created) # 2
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Certify a Local File
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
# Auto-hashes with SHA-256
|
|
235
|
+
cert = client.certify("path/to/report.pdf", author="my-agent")
|
|
236
|
+
print(cert.id)
|
|
237
|
+
print(cert.transaction_url)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Verify a Proof
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
# By proof ID
|
|
244
|
+
proof = client.verify("certification-uuid")
|
|
245
|
+
print(proof.file_name, proof.blockchain_status)
|
|
246
|
+
|
|
247
|
+
# By file hash
|
|
248
|
+
proof = client.verify_hash("e3b0c442...")
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Policy Compliance
|
|
252
|
+
|
|
253
|
+
Check whether a decision meets governance requirements — without fetching the full confidence trail:
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
from xproof import XProofClient, PolicyCheckResult
|
|
257
|
+
|
|
258
|
+
client = XProofClient(api_key="pm_your_key")
|
|
259
|
+
|
|
260
|
+
result: PolicyCheckResult = client.get_policy_check("trade-xyz-2026")
|
|
261
|
+
|
|
262
|
+
if result.policy_compliant:
|
|
263
|
+
print("Decision is compliant.")
|
|
264
|
+
else:
|
|
265
|
+
for v in result.policy_violations:
|
|
266
|
+
print(f"VIOLATION [{v.severity}] — {v.rule}")
|
|
267
|
+
print(f" {v.message}")
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`get_policy_check()` is a lightweight yes/no compliance check. It returns `result.policy_compliant` (bool) and `result.policy_violations` (list). For the full audit trail including timestamps and intermediate confidence checkpoints, use `get_confidence_trail()` instead.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Governance & Policy Enforcement
|
|
275
|
+
|
|
276
|
+
xProof detects automatically when an agent acted with insufficient confidence on an irreversible action — and writes the evidence on-chain before you ever open an incident report.
|
|
277
|
+
|
|
278
|
+
### Mark decisions as reversible, costly, or irreversible
|
|
279
|
+
|
|
280
|
+
Add `reversibility_class` to any certified action. The server enforces a policy: **irreversible actions require `confidence_level >= 0.95`**. Anything below that threshold generates a policy violation anchored to the chain.
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
# An agent is about to execute a trade it cannot undo.
|
|
284
|
+
# It certifies its reasoning at 0.72 confidence — below the 0.95 threshold.
|
|
285
|
+
cert = client.certify_with_confidence(
|
|
286
|
+
file_hash=hash_string('{"action": "sell", "ticker": "AAPL", "qty": 500}'),
|
|
287
|
+
file_name="trade-decision.json",
|
|
288
|
+
author="trading-agent",
|
|
289
|
+
confidence_level=0.72, # Agent's self-assessed confidence
|
|
290
|
+
threshold_stage="pre-commitment",
|
|
291
|
+
decision_id="trade-xyz-2026",
|
|
292
|
+
reversibility_class="irreversible", # This action cannot be undone
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# cert.reversibility_class == "irreversible"
|
|
296
|
+
# The server has recorded a policy violation: 0.72 < 0.95 required
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Check compliance — without fetching the full trail
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
from xproof import XProofClient, PolicyCheckResult
|
|
303
|
+
|
|
304
|
+
check: PolicyCheckResult = client.get_policy_check("trade-xyz-2026")
|
|
305
|
+
|
|
306
|
+
if not check.policy_compliant:
|
|
307
|
+
for v in check.policy_violations:
|
|
308
|
+
print(f"VIOLATION [{v.severity}] — {v.rule}")
|
|
309
|
+
print(f" {v.message}")
|
|
310
|
+
# → VIOLATION [error] — irreversible actions require confidence_level >= 0.95
|
|
311
|
+
# → confidence 0.72 is below the required threshold of 0.95
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Full confidence trail with policy result
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
trail = client.get_confidence_trail("trade-xyz-2026")
|
|
318
|
+
|
|
319
|
+
print(trail.policy_compliant) # False
|
|
320
|
+
print(len(trail.policy_violations)) # 1
|
|
321
|
+
print(trail.current_confidence) # 0.72
|
|
322
|
+
print(trail.is_finalized) # False — decision still open
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### End-to-end agent example: document deletion with compliance gate
|
|
326
|
+
|
|
327
|
+
This example shows a realistic governance loop for an autonomous agent that is
|
|
328
|
+
about to permanently delete customer records — an irreversible action that
|
|
329
|
+
requires a confidence level of at least 0.95 before proceeding.
|
|
330
|
+
|
|
331
|
+
#### Option A — One-line LangChain tool (recommended for LangChain / LCEL agents)
|
|
332
|
+
|
|
333
|
+
If you are already running a LangChain or LCEL agent, drop in `XProofCertifyTool`
|
|
334
|
+
to collapse the four-step hash → certify → check → gate loop into a single
|
|
335
|
+
`tool.run()` call.
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
import json
|
|
339
|
+
from xproof.langchain_tool import XProofCertifyTool
|
|
340
|
+
from xproof.exceptions import PolicyViolationError
|
|
341
|
+
|
|
342
|
+
certify = XProofCertifyTool(api_key="pm_...", author="data-hygiene-agent")
|
|
343
|
+
|
|
344
|
+
decision = {
|
|
345
|
+
"action": "delete_customer_records",
|
|
346
|
+
"scope": "inactive_accounts",
|
|
347
|
+
"records_affected": 4821,
|
|
348
|
+
"retention_policy_checked": True,
|
|
349
|
+
"legal_hold_clear": True,
|
|
350
|
+
}
|
|
351
|
+
decision_id = "del-run-2026-04-20"
|
|
352
|
+
|
|
353
|
+
try:
|
|
354
|
+
tx_hash = certify.run({
|
|
355
|
+
"decision_text": json.dumps(decision, sort_keys=True),
|
|
356
|
+
"confidence_level": 0.97,
|
|
357
|
+
"threshold_stage": "pre-commitment",
|
|
358
|
+
"decision_id": decision_id,
|
|
359
|
+
"reversibility_class": "irreversible",
|
|
360
|
+
"why": "Scheduled GDPR data-retention cleanup",
|
|
361
|
+
})
|
|
362
|
+
print(f"Policy compliant — proceeding (tx: {tx_hash})")
|
|
363
|
+
# delete_customer_records(decision["scope"]) # your execution here
|
|
364
|
+
except PolicyViolationError as exc:
|
|
365
|
+
for v in exc.violations:
|
|
366
|
+
print(f"BLOCKED [{v.severity.upper()}] {v.rule}: {v.message}")
|
|
367
|
+
raise RuntimeError("Deletion aborted: policy compliance check failed.") from exc
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
The tool hashes `decision_text` with SHA-256, calls
|
|
371
|
+
`certify_with_confidence`, and immediately runs `get_policy_check`.
|
|
372
|
+
If the check passes it returns the `transaction_hash`; if it fails it raises
|
|
373
|
+
`PolicyViolationError` with the full list of violations attached.
|
|
374
|
+
|
|
375
|
+
The tool accepts every parameter that `certify_with_confidence` does —
|
|
376
|
+
`who`, `what`, `when`, `why`, `reversibility_class`, `metadata`, and per-call
|
|
377
|
+
`author` — so you retain full provenance control. When `who`, `what`, or
|
|
378
|
+
`when` are omitted the tool supplies sensible defaults (resolved author, SHA-256
|
|
379
|
+
hash, current UTC timestamp respectively); explicitly passing any of them
|
|
380
|
+
overrides those defaults. You can also pass a pre-computed `file_hash` instead
|
|
381
|
+
of `decision_text` if you have already hashed the payload externally.
|
|
382
|
+
|
|
383
|
+
**Async support:** `XProofCertifyTool` fully supports async LangChain pipelines.
|
|
384
|
+
`_arun` is implemented via `asyncio.to_thread`, so it is safe to use in async
|
|
385
|
+
LCEL chains and async agent executors without blocking the event loop:
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
# Inside an async LCEL chain or async agent executor:
|
|
389
|
+
tx_hash = await certify.arun({
|
|
390
|
+
"decision_text": json.dumps(decision, sort_keys=True),
|
|
391
|
+
"confidence_level": 0.97,
|
|
392
|
+
"threshold_stage": "pre-commitment",
|
|
393
|
+
"decision_id": decision_id,
|
|
394
|
+
"reversibility_class": "irreversible",
|
|
395
|
+
"why": "Scheduled GDPR data-retention cleanup",
|
|
396
|
+
})
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
You can also bind the tool to a LangChain agent directly:
|
|
400
|
+
|
|
401
|
+
```python
|
|
402
|
+
from langchain.agents import initialize_agent, AgentType
|
|
403
|
+
from langchain_openai import ChatOpenAI
|
|
404
|
+
|
|
405
|
+
llm = ChatOpenAI(model="gpt-4o")
|
|
406
|
+
agent = initialize_agent(
|
|
407
|
+
tools=[certify],
|
|
408
|
+
llm=llm,
|
|
409
|
+
agent=AgentType.OPENAI_FUNCTIONS,
|
|
410
|
+
)
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
#### Option A — One-line CrewAI tool (recommended for CrewAI agents)
|
|
414
|
+
|
|
415
|
+
`XProofCrewCertifyTool` provides the same one-call certification loop for
|
|
416
|
+
CrewAI agents. `XProofNativeCrewCertifyTool` wraps it as a native
|
|
417
|
+
`BaseTool` subclass so it can be added directly to a CrewAI agent's
|
|
418
|
+
`tools` list.
|
|
419
|
+
|
|
420
|
+
```python
|
|
421
|
+
import json
|
|
422
|
+
from xproof.integrations.crewai import XProofCrewCertifyTool
|
|
423
|
+
from xproof.exceptions import PolicyViolationError
|
|
424
|
+
|
|
425
|
+
certify = XProofCrewCertifyTool(api_key="pm_...", author="data-hygiene-agent")
|
|
426
|
+
|
|
427
|
+
decision = {
|
|
428
|
+
"action": "delete_customer_records",
|
|
429
|
+
"scope": "inactive_accounts",
|
|
430
|
+
"records_affected": 4821,
|
|
431
|
+
"retention_policy_checked": True,
|
|
432
|
+
"legal_hold_clear": True,
|
|
433
|
+
}
|
|
434
|
+
decision_id = "del-run-2026-04-20"
|
|
435
|
+
|
|
436
|
+
try:
|
|
437
|
+
tx_hash = certify.run(
|
|
438
|
+
decision_text=json.dumps(decision, sort_keys=True),
|
|
439
|
+
confidence_level=0.97,
|
|
440
|
+
threshold_stage="pre-commitment",
|
|
441
|
+
decision_id=decision_id,
|
|
442
|
+
reversibility_class="irreversible",
|
|
443
|
+
why="Scheduled GDPR data-retention cleanup",
|
|
444
|
+
)
|
|
445
|
+
print(f"Policy compliant — proceeding (tx: {tx_hash})")
|
|
446
|
+
# delete_customer_records(decision["scope"]) # your execution here
|
|
447
|
+
except PolicyViolationError as exc:
|
|
448
|
+
for v in exc.violations:
|
|
449
|
+
print(f"BLOCKED [{v.severity.upper()}] {v.rule}: {v.message}")
|
|
450
|
+
raise RuntimeError("Deletion aborted: policy compliance check failed.") from exc
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
To attach it natively to a CrewAI `Agent`:
|
|
454
|
+
|
|
455
|
+
```python
|
|
456
|
+
from crewai import Agent
|
|
457
|
+
from xproof.integrations.crewai import XProofNativeCrewCertifyTool
|
|
458
|
+
|
|
459
|
+
certify_tool = XProofNativeCrewCertifyTool(
|
|
460
|
+
api_key="pm_...", author="data-hygiene-agent"
|
|
461
|
+
)
|
|
462
|
+
agent = Agent(role="analyst", tools=[certify_tool], ...)
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### Option A — One-line AutoGen tool (recommended for AutoGen agents)
|
|
466
|
+
|
|
467
|
+
`xproof_certify_decision` is a plain callable with the same full loop —
|
|
468
|
+
hash → certify → policy check → gate — designed to be registered as a
|
|
469
|
+
function tool on any AutoGen `ConversableAgent`.
|
|
470
|
+
|
|
471
|
+
```python
|
|
472
|
+
import json
|
|
473
|
+
from xproof.integrations.autogen import xproof_certify_decision
|
|
474
|
+
from xproof.exceptions import PolicyViolationError
|
|
475
|
+
|
|
476
|
+
decision = {
|
|
477
|
+
"action": "delete_customer_records",
|
|
478
|
+
"scope": "inactive_accounts",
|
|
479
|
+
"records_affected": 4821,
|
|
480
|
+
"retention_policy_checked": True,
|
|
481
|
+
"legal_hold_clear": True,
|
|
482
|
+
}
|
|
483
|
+
decision_id = "del-run-2026-04-20"
|
|
484
|
+
|
|
485
|
+
try:
|
|
486
|
+
tx_hash = xproof_certify_decision(
|
|
487
|
+
decision_text=json.dumps(decision, sort_keys=True),
|
|
488
|
+
confidence_level=0.97,
|
|
489
|
+
threshold_stage="pre-commitment",
|
|
490
|
+
decision_id=decision_id,
|
|
491
|
+
reversibility_class="irreversible",
|
|
492
|
+
why="Scheduled GDPR data-retention cleanup",
|
|
493
|
+
author="data-hygiene-agent",
|
|
494
|
+
api_key="pm_...",
|
|
495
|
+
)
|
|
496
|
+
print(f"Policy compliant — proceeding (tx: {tx_hash})")
|
|
497
|
+
# delete_customer_records(decision["scope"]) # your execution here
|
|
498
|
+
except PolicyViolationError as exc:
|
|
499
|
+
for v in exc.violations:
|
|
500
|
+
print(f"BLOCKED [{v.severity.upper()}] {v.rule}: {v.message}")
|
|
501
|
+
raise RuntimeError("Deletion aborted: policy compliance check failed.") from exc
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
You can also register it as a tool on an AutoGen agent so the LLM can
|
|
505
|
+
invoke it by name:
|
|
506
|
+
|
|
507
|
+
```python
|
|
508
|
+
from autogen import AssistantAgent, UserProxyAgent
|
|
509
|
+
from functools import partial
|
|
510
|
+
from xproof.integrations.autogen import xproof_certify_decision
|
|
511
|
+
|
|
512
|
+
# Bind api_key once; the agent passes the decision fields per call.
|
|
513
|
+
certify = partial(xproof_certify_decision, api_key="pm_...", author="analyst-agent")
|
|
514
|
+
|
|
515
|
+
assistant = AssistantAgent("analyst", llm_config={...})
|
|
516
|
+
user_proxy = UserProxyAgent("user_proxy", human_input_mode="NEVER")
|
|
517
|
+
|
|
518
|
+
assistant.register_for_llm(name="certify_decision", description="Certify a decision on-chain")(certify)
|
|
519
|
+
user_proxy.register_for_execution(name="certify_decision")(certify)
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
#### Option B — Manual four-step loop (framework-agnostic)
|
|
523
|
+
|
|
524
|
+
```python
|
|
525
|
+
import hashlib, json
|
|
526
|
+
from xproof import XProofClient
|
|
527
|
+
|
|
528
|
+
client = XProofClient(api_key="pm_...")
|
|
529
|
+
|
|
530
|
+
def hash_string(s: str) -> str:
|
|
531
|
+
return hashlib.sha256(s.encode()).hexdigest()
|
|
532
|
+
|
|
533
|
+
# ── Step 1: Agent produces its reasoning ─────────────────────────────────────
|
|
534
|
+
# (In a real LangChain / CrewAI / AutoGen agent, this would be the structured
|
|
535
|
+
# chain-of-thought or tool-call output produced just before execution.)
|
|
536
|
+
|
|
537
|
+
decision = {
|
|
538
|
+
"action": "delete_customer_records",
|
|
539
|
+
"scope": "inactive_accounts",
|
|
540
|
+
"records_affected": 4821,
|
|
541
|
+
"retention_policy_checked": True,
|
|
542
|
+
"legal_hold_clear": True,
|
|
543
|
+
"agent": "data-hygiene-agent",
|
|
544
|
+
"run_id": "del-run-2026-04-20",
|
|
545
|
+
}
|
|
546
|
+
decision_id = "del-run-2026-04-20"
|
|
547
|
+
reasoning_hash = hash_string(json.dumps(decision, sort_keys=True))
|
|
548
|
+
|
|
549
|
+
# ── Step 2: Certify BEFORE executing ─────────────────────────────────────────
|
|
550
|
+
# The agent self-assesses its confidence. Because the action is irreversible,
|
|
551
|
+
# the policy requires confidence_level >= 0.95.
|
|
552
|
+
|
|
553
|
+
cert = client.certify_with_confidence(
|
|
554
|
+
file_hash=reasoning_hash,
|
|
555
|
+
file_name="delete-decision.json",
|
|
556
|
+
author="data-hygiene-agent",
|
|
557
|
+
confidence_level=0.97, # Agent is highly confident
|
|
558
|
+
threshold_stage="pre-commitment", # valid: "initial", "partial", "pre-commitment", "final"
|
|
559
|
+
decision_id=decision_id,
|
|
560
|
+
reversibility_class="irreversible", # Deletion cannot be undone
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
# cert.transaction_hash is the on-chain anchor — written before any records move
|
|
564
|
+
|
|
565
|
+
# ── Step 3: Compliance gate ───────────────────────────────────────────────────
|
|
566
|
+
# Immediately check policy compliance. This is a lightweight call — it does NOT
|
|
567
|
+
# re-fetch the full trail. Gate the destructive action on the result.
|
|
568
|
+
|
|
569
|
+
check = client.get_policy_check(decision_id)
|
|
570
|
+
|
|
571
|
+
if not check.policy_compliant:
|
|
572
|
+
# Policy violated — log every violation and abort.
|
|
573
|
+
for v in check.policy_violations:
|
|
574
|
+
print(f"BLOCKED [{v.severity.upper()}] {v.rule}")
|
|
575
|
+
print(f" {v.message}")
|
|
576
|
+
raise RuntimeError("Deletion aborted: policy compliance check failed.")
|
|
577
|
+
|
|
578
|
+
# ── Step 4: Execute only when compliant ──────────────────────────────────────
|
|
579
|
+
print(f"Policy compliant — proceeding with deletion (cert: {cert.transaction_hash})")
|
|
580
|
+
# delete_customer_records(decision["scope"]) # your actual execution here
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
**What happens if the agent's confidence is too low?**
|
|
584
|
+
|
|
585
|
+
Drop `confidence_level` to `0.82` and the same gate blocks execution:
|
|
586
|
+
|
|
587
|
+
```python
|
|
588
|
+
cert = client.certify_with_confidence(
|
|
589
|
+
...
|
|
590
|
+
confidence_level=0.82, # Below the 0.95 irreversible threshold
|
|
591
|
+
reversibility_class="irreversible",
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
check = client.get_policy_check(decision_id)
|
|
595
|
+
|
|
596
|
+
if not check.policy_compliant:
|
|
597
|
+
for v in check.policy_violations:
|
|
598
|
+
print(f"BLOCKED [{v.severity.upper()}] {v.rule}")
|
|
599
|
+
# → BLOCKED [ERROR] irreversible actions require confidence_level >= 0.95
|
|
600
|
+
print(f" {v.message}")
|
|
601
|
+
# → confidence 0.82 is below the required threshold of 0.95
|
|
602
|
+
|
|
603
|
+
raise RuntimeError("Deletion aborted: policy compliance check failed.")
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
The violation is written on-chain at certification time — before your code
|
|
607
|
+
ever reaches the gate — so the audit trail exists even if your agent crashes
|
|
608
|
+
between `certify_with_confidence` and `get_policy_check`.
|
|
609
|
+
|
|
610
|
+
### Observability — surfacing violations in dashboards
|
|
611
|
+
|
|
612
|
+
Raising a `RuntimeError` is enough to halt execution, but it gives your
|
|
613
|
+
observability stack nothing structured to alert on. The pattern below emits a
|
|
614
|
+
machine-readable JSON log line for each violation and optionally fires a
|
|
615
|
+
webhook, so Datadog / Grafana / CloudWatch log-based alerts can pick up
|
|
616
|
+
violations without grepping free-form text.
|
|
617
|
+
|
|
618
|
+
```python
|
|
619
|
+
import json, logging, urllib.request
|
|
620
|
+
from xproof import XProofClient
|
|
621
|
+
|
|
622
|
+
logger = logging.getLogger("xproof.compliance")
|
|
623
|
+
logging.basicConfig(level=logging.INFO)
|
|
624
|
+
|
|
625
|
+
client = XProofClient(api_key="pm_...")
|
|
626
|
+
|
|
627
|
+
# Optional: set a webhook URL to receive violation payloads
|
|
628
|
+
VIOLATION_WEBHOOK_URL = None # e.g. "https://hooks.example.com/compliance"
|
|
629
|
+
|
|
630
|
+
def _emit_violation(decision_id: str, violation) -> None:
|
|
631
|
+
"""Emit one structured log line and, optionally, a webhook call."""
|
|
632
|
+
payload = {
|
|
633
|
+
"event": "policy_violation",
|
|
634
|
+
"decision_id": decision_id,
|
|
635
|
+
"rule": violation.rule,
|
|
636
|
+
"severity": violation.severity,
|
|
637
|
+
"message": violation.message,
|
|
638
|
+
}
|
|
639
|
+
# ── Structured JSON log (ingested by Datadog / CloudWatch / Loki) ─────────
|
|
640
|
+
logger.error(json.dumps(payload))
|
|
641
|
+
|
|
642
|
+
# ── Optional webhook / alerting callback (best-effort) ───────────────────
|
|
643
|
+
if VIOLATION_WEBHOOK_URL:
|
|
644
|
+
try:
|
|
645
|
+
body = json.dumps(payload).encode()
|
|
646
|
+
req = urllib.request.Request(
|
|
647
|
+
VIOLATION_WEBHOOK_URL,
|
|
648
|
+
data=body,
|
|
649
|
+
headers={"Content-Type": "application/json"},
|
|
650
|
+
method="POST",
|
|
651
|
+
)
|
|
652
|
+
with urllib.request.urlopen(req, timeout=5):
|
|
653
|
+
pass # fire-and-forget; add retry logic as needed
|
|
654
|
+
except Exception as exc:
|
|
655
|
+
# Best-effort delivery — a webhook failure must NOT swallow the
|
|
656
|
+
# compliance violation itself. Log and continue to the raise below.
|
|
657
|
+
logger.warning(json.dumps({"event": "webhook_error", "detail": str(exc)}))
|
|
658
|
+
|
|
659
|
+
check = client.get_policy_check(decision_id)
|
|
660
|
+
|
|
661
|
+
if not check.policy_compliant:
|
|
662
|
+
for v in check.policy_violations:
|
|
663
|
+
_emit_violation(decision_id, v)
|
|
664
|
+
|
|
665
|
+
# ── Full audit trail for post-mortem / SIEM export ────────────────────────
|
|
666
|
+
# get_confidence_trail() returns a ConfidenceTrail object containing every
|
|
667
|
+
# certification event — confidence levels, timestamps, transaction hashes —
|
|
668
|
+
# so you can attach the complete chain-of-evidence to an incident ticket or
|
|
669
|
+
# ship it to your SIEM without a separate lookup.
|
|
670
|
+
# trail.raw is the unmodified API response dict; use trail.stages for
|
|
671
|
+
# programmatic access to individual ConfidenceTrailStage entries.
|
|
672
|
+
# Note: redact sensitive fields from trail.raw before logging or exporting
|
|
673
|
+
# to centralised logs / SIEM in production environments.
|
|
674
|
+
trail = client.get_confidence_trail(decision_id)
|
|
675
|
+
logger.error(json.dumps({
|
|
676
|
+
"event": "audit_trail",
|
|
677
|
+
"decision_id": decision_id,
|
|
678
|
+
"trail": trail.raw,
|
|
679
|
+
}))
|
|
680
|
+
|
|
681
|
+
raise RuntimeError("Deletion aborted: policy compliance check failed.")
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
Each `logger.error(...)` call writes a single-line JSON object that log
|
|
685
|
+
shippers (Fluentd, the Datadog Agent, the CloudWatch agent) forward verbatim.
|
|
686
|
+
Create a log-based metric or alert on `event = "policy_violation"` to get
|
|
687
|
+
dashboard counts and threshold alerts with no extra instrumentation.
|
|
688
|
+
|
|
689
|
+
### Three classes, one parameter
|
|
690
|
+
|
|
691
|
+
| `reversibility_class` | What it means | Policy threshold |
|
|
692
|
+
|---|---|---|
|
|
693
|
+
| `"reversible"` | Action can be undone (e.g. draft, preview) | None — any confidence accepted |
|
|
694
|
+
| `"costly"` | Undoable but expensive (e.g. API call, DB write) | None — any confidence accepted |
|
|
695
|
+
| `"irreversible"` | Cannot be undone (e.g. trade, deletion, send) | `confidence_level >= 0.95` required |
|
|
696
|
+
|
|
697
|
+
> The threshold is configured server-side (`IRREVERSIBLE_CONFIDENCE_THRESHOLD=0.95`). All violations are written on-chain and cannot be amended.
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## Pricing
|
|
702
|
+
|
|
703
|
+
```python
|
|
704
|
+
pricing = client.get_pricing()
|
|
705
|
+
print(pricing.price_usd) # e.g. 0.05
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
## API Reference
|
|
709
|
+
|
|
710
|
+
### `XProofClient(api_key=None, base_url="https://xproof.app", timeout=30)`
|
|
711
|
+
|
|
712
|
+
| Parameter | Type | Default |
|
|
713
|
+
|------------|-------|------------------------|
|
|
714
|
+
| `api_key` | `str` | `None` |
|
|
715
|
+
| `base_url` | `str` | `"https://xproof.app"` |
|
|
716
|
+
| `timeout` | `int` | `30` (seconds) |
|
|
717
|
+
|
|
718
|
+
### Methods
|
|
719
|
+
|
|
720
|
+
| Method | Description |
|
|
721
|
+
|--------|-------------|
|
|
722
|
+
| `XProofClient.register(agent_name)` | Register agent, get trial key |
|
|
723
|
+
| `certify(path, author, *, reversibility_class?, **fourW)` | Certify file (hashes locally) |
|
|
724
|
+
| `certify_hash(file_hash, file_name, author, *, reversibility_class?, **fourW)` | Certify by pre-computed hash |
|
|
725
|
+
| `certify_with_confidence(hash, name, author, confidence_level, threshold_stage, decision_id, *, reversibility_class?, **fourW)` | Certify with confidence + governance class |
|
|
726
|
+
| `batch_certify(files)` | Batch certify (up to 50) |
|
|
727
|
+
| `verify(proof_id)` | Look up by proof ID |
|
|
728
|
+
| `verify_hash(file_hash)` | Look up by file hash |
|
|
729
|
+
| `get_confidence_trail(decision_id)` | Full trail with `policy_compliant` + violations |
|
|
730
|
+
| `get_policy_check(decision_id)` | Lightweight compliance check — no full trail |
|
|
731
|
+
| `get_pricing()` | Get current pricing |
|
|
732
|
+
|
|
733
|
+
## Development
|
|
734
|
+
|
|
735
|
+
Install dev dependencies and run the checks locally:
|
|
736
|
+
|
|
737
|
+
```bash
|
|
738
|
+
pip install -e ".[dev]"
|
|
739
|
+
|
|
740
|
+
# Lint (ruff — catches unused imports, duplicate class definitions, and more)
|
|
741
|
+
make lint
|
|
742
|
+
|
|
743
|
+
# Type-check with mypy
|
|
744
|
+
make typecheck
|
|
745
|
+
|
|
746
|
+
# Unit tests (excludes live-API integration tests)
|
|
747
|
+
make test
|
|
748
|
+
|
|
749
|
+
# Lint + typecheck + test together
|
|
750
|
+
make check
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### Pre-commit hooks
|
|
754
|
+
|
|
755
|
+
A pre-commit hook runs `mypy` automatically before every commit so type errors
|
|
756
|
+
are caught locally rather than in CI.
|
|
757
|
+
|
|
758
|
+
Run the one-time setup from the `python-sdk/` directory:
|
|
759
|
+
|
|
760
|
+
```bash
|
|
761
|
+
make install-hooks
|
|
762
|
+
# Equivalent (run from the repo root):
|
|
763
|
+
# pre-commit install --config python-sdk/.pre-commit-config.yaml
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
After that, every `git commit` will run `make typecheck`, which checks the
|
|
767
|
+
entire `xproof/` package with mypy. If mypy reports any errors the commit is
|
|
768
|
+
blocked until they are fixed.
|
|
769
|
+
|
|
770
|
+
To run the hooks manually against all files without committing (from the repo root):
|
|
771
|
+
|
|
772
|
+
```bash
|
|
773
|
+
pre-commit run --all-files --config python-sdk/.pre-commit-config.yaml
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
The linter is configured in `pyproject.toml` under `[tool.ruff]`. Rule `F811` will flag
|
|
777
|
+
duplicate top-level class definitions — the kind of silent overwrite that prompted this setup.
|
|
778
|
+
|
|
779
|
+
### VS Code setup
|
|
780
|
+
|
|
781
|
+
A `.vscode/settings.json` is included in this directory. It configures the
|
|
782
|
+
[Ruff extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff)
|
|
783
|
+
as the default Python formatter and enables **format on save**, so unused imports are
|
|
784
|
+
removed and imports are sorted automatically every time you save a file.
|
|
785
|
+
|
|
786
|
+
Install the extension once and the settings take effect immediately:
|
|
787
|
+
|
|
788
|
+
```bash
|
|
789
|
+
code --install-extension charliermarsh.ruff
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
## Links
|
|
793
|
+
|
|
794
|
+
- [xproof.app](https://xproof.app) — dashboard & docs
|
|
795
|
+
- [npm SDK](https://www.npmjs.com/package/@xproof/xproof) — `npm install @xproof/xproof`
|
|
796
|
+
- [Examples](https://github.com/jasonxkensei/xproof-examples) — LangChain, CrewAI, AutoGen, LlamaIndex
|
|
797
|
+
|
|
798
|
+
## License
|
|
799
|
+
|
|
800
|
+
MIT
|