mint-attest 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FoundryNet
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,160 @@
1
+ Metadata-Version: 2.4
2
+ Name: mint-attest
3
+ Version: 0.1.0
4
+ Summary: Universal work attestation for AI agents. Prove your agent did the work.
5
+ Author: FoundryNet
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/FoundryNet/mint-attest
8
+ Project-URL: Documentation, https://mint.foundrynet.io
9
+ Project-URL: Source, https://github.com/FoundryNet/mint-attest
10
+ Keywords: ai,agents,attestation,trust,solana,verification,mint,langchain,crewai,autogen
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Libraries
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: httpx>=0.24
24
+ Provides-Extra: langchain
25
+ Requires-Dist: langchain>=0.1; extra == "langchain"
26
+ Provides-Extra: crewai
27
+ Requires-Dist: crewai>=0.1; extra == "crewai"
28
+ Provides-Extra: autogen
29
+ Requires-Dist: pyautogen>=0.2; extra == "autogen"
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7; extra == "dev"
32
+ Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # mint-attest
36
+
37
+ **Universal work attestation for AI agents.** Prove your agent did the work. Build verifiable trust. Settled on Solana.
38
+
39
+ ```bash
40
+ pip install mint-attest
41
+ ```
42
+
43
+ ## Quick start (3 lines)
44
+
45
+ ```python
46
+ from mint_attest import attest
47
+
48
+ @attest(work_type="code_review")
49
+ def review(files):
50
+ return do_review(files)
51
+ ```
52
+
53
+ Every call to `review()` is now attested on Solana mainnet with a tamper-evident work record — input/output hashed, duration recorded, trust score updated, Solscan verify URL minted. Your function's return value is unchanged.
54
+
55
+ Set your key once (mirrors `OPENAI_API_KEY` / `ANTHROPIC_API_KEY`):
56
+
57
+ ```bash
58
+ export MINT_API_KEY=fnet_… # free key at foundrynet.io
59
+ ```
60
+
61
+ ## Why?
62
+
63
+ 1.3 billion AI agents by 2028. No way to verify which ones actually do good work.
64
+
65
+ mint-attest gives your agent a verifiable track record. Register once. Attest every task. Build trust that any other agent or human can verify on-chain — no wallet, no keys, no blockchain code on your side. It's just an API call.
66
+
67
+ ## Two ways to use it
68
+
69
+ ### 1 — Decorator (zero friction)
70
+
71
+ ```python
72
+ from mint_attest import attest
73
+
74
+ @attest(work_type="code_review")
75
+ def review_code(files):
76
+ return do_the_review(files)
77
+ # auto-registers the agent once, hashes input/output, records duration,
78
+ # attests on MINT, and returns results unchanged.
79
+ ```
80
+
81
+ Attestation never breaks your function: if the network hiccups or no key is set, it logs and returns your result anyway. Pass `@attest(..., strict=True)` to opt into raising.
82
+
83
+ ### 2 — Explicit client (more control)
84
+
85
+ ```python
86
+ from mint_attest import MintClient
87
+
88
+ mint = MintClient(api_key="fnet_…") # or MINT_API_KEY env
89
+
90
+ actor = mint.register(
91
+ name="CodeReviewBot",
92
+ actor_type="ai_agent",
93
+ capabilities=["code_review", "security_audit"],
94
+ )
95
+
96
+ receipt = mint.attest(
97
+ work_type="code_review",
98
+ input_data=files, # hashed for you (SHA-256)
99
+ output_data=results,
100
+ duration_seconds=elapsed,
101
+ )
102
+ print(receipt.verify_url) # https://solscan.io/tx/…
103
+
104
+ trust = mint.verify(actor.mint_id)
105
+ print(trust.score, trust.total_attestations)
106
+ ```
107
+
108
+ ## Works with your framework
109
+
110
+ ```python
111
+ # LangChain — every chain.run() attests
112
+ from mint_attest.langchain import MintAttestCallback
113
+ chain = LLMChain(llm=llm, prompt=prompt, callbacks=[MintAttestCallback(api_key="fnet_…")])
114
+
115
+ # CrewAI — give the crew an attest tool (or use mint_attest_step_callback)
116
+ from mint_attest.crewai import MintAttestTool
117
+ crew = Crew(agents=[researcher], tools=[MintAttestTool(api_key="fnet_…")])
118
+
119
+ # AutoGen — attest every reply
120
+ from mint_attest.autogen import MintAttestHook
121
+ MintAttestHook(api_key="fnet_…").attach(agent)
122
+ ```
123
+
124
+ Install the extra you need: `pip install mint-attest[langchain]` · `[crewai]` · `[autogen]`.
125
+
126
+ ## What happens on each attestation
127
+
128
+ 1. Input/output hashed (SHA-256) — your data never leaves; only the hash does
129
+ 2. Duration recorded
130
+ 3. Work record settled on Solana mainnet
131
+ 4. Trust score updated
132
+ 5. Verify URL returned (Solscan)
133
+
134
+ Your agent's work history is permanent, tamper-evident, and publicly verifiable.
135
+
136
+ ## Pricing
137
+
138
+ | Action | Price |
139
+ |--------|-------|
140
+ | Register | **FREE** |
141
+ | Verify | **FREE** |
142
+ | Attest | **$0.02** per attestation |
143
+
144
+ ## Configuration
145
+
146
+ | Env var | Purpose |
147
+ |---------|---------|
148
+ | `MINT_API_KEY` | your `fnet_` key (required for register/attest) |
149
+ | `MINT_AGENT_NAME` | default agent name (optional) |
150
+ | `MINT_ENDPOINT` | override the MINT server (default `https://mint-mcp-production.up.railway.app`) |
151
+
152
+ No Solana dependency. No wallet. No transaction signing. The SDK calls the MINT server's HTTPS API; the server handles all on-chain interaction.
153
+
154
+ ## Links
155
+
156
+ - Explorer: https://mint.foundrynet.io
157
+ - MCP server: https://github.com/FoundryNet/mint-mcp
158
+ - Protocol: https://foundrynet.io
159
+
160
+ MIT licensed.
@@ -0,0 +1,126 @@
1
+ # mint-attest
2
+
3
+ **Universal work attestation for AI agents.** Prove your agent did the work. Build verifiable trust. Settled on Solana.
4
+
5
+ ```bash
6
+ pip install mint-attest
7
+ ```
8
+
9
+ ## Quick start (3 lines)
10
+
11
+ ```python
12
+ from mint_attest import attest
13
+
14
+ @attest(work_type="code_review")
15
+ def review(files):
16
+ return do_review(files)
17
+ ```
18
+
19
+ Every call to `review()` is now attested on Solana mainnet with a tamper-evident work record — input/output hashed, duration recorded, trust score updated, Solscan verify URL minted. Your function's return value is unchanged.
20
+
21
+ Set your key once (mirrors `OPENAI_API_KEY` / `ANTHROPIC_API_KEY`):
22
+
23
+ ```bash
24
+ export MINT_API_KEY=fnet_… # free key at foundrynet.io
25
+ ```
26
+
27
+ ## Why?
28
+
29
+ 1.3 billion AI agents by 2028. No way to verify which ones actually do good work.
30
+
31
+ mint-attest gives your agent a verifiable track record. Register once. Attest every task. Build trust that any other agent or human can verify on-chain — no wallet, no keys, no blockchain code on your side. It's just an API call.
32
+
33
+ ## Two ways to use it
34
+
35
+ ### 1 — Decorator (zero friction)
36
+
37
+ ```python
38
+ from mint_attest import attest
39
+
40
+ @attest(work_type="code_review")
41
+ def review_code(files):
42
+ return do_the_review(files)
43
+ # auto-registers the agent once, hashes input/output, records duration,
44
+ # attests on MINT, and returns results unchanged.
45
+ ```
46
+
47
+ Attestation never breaks your function: if the network hiccups or no key is set, it logs and returns your result anyway. Pass `@attest(..., strict=True)` to opt into raising.
48
+
49
+ ### 2 — Explicit client (more control)
50
+
51
+ ```python
52
+ from mint_attest import MintClient
53
+
54
+ mint = MintClient(api_key="fnet_…") # or MINT_API_KEY env
55
+
56
+ actor = mint.register(
57
+ name="CodeReviewBot",
58
+ actor_type="ai_agent",
59
+ capabilities=["code_review", "security_audit"],
60
+ )
61
+
62
+ receipt = mint.attest(
63
+ work_type="code_review",
64
+ input_data=files, # hashed for you (SHA-256)
65
+ output_data=results,
66
+ duration_seconds=elapsed,
67
+ )
68
+ print(receipt.verify_url) # https://solscan.io/tx/…
69
+
70
+ trust = mint.verify(actor.mint_id)
71
+ print(trust.score, trust.total_attestations)
72
+ ```
73
+
74
+ ## Works with your framework
75
+
76
+ ```python
77
+ # LangChain — every chain.run() attests
78
+ from mint_attest.langchain import MintAttestCallback
79
+ chain = LLMChain(llm=llm, prompt=prompt, callbacks=[MintAttestCallback(api_key="fnet_…")])
80
+
81
+ # CrewAI — give the crew an attest tool (or use mint_attest_step_callback)
82
+ from mint_attest.crewai import MintAttestTool
83
+ crew = Crew(agents=[researcher], tools=[MintAttestTool(api_key="fnet_…")])
84
+
85
+ # AutoGen — attest every reply
86
+ from mint_attest.autogen import MintAttestHook
87
+ MintAttestHook(api_key="fnet_…").attach(agent)
88
+ ```
89
+
90
+ Install the extra you need: `pip install mint-attest[langchain]` · `[crewai]` · `[autogen]`.
91
+
92
+ ## What happens on each attestation
93
+
94
+ 1. Input/output hashed (SHA-256) — your data never leaves; only the hash does
95
+ 2. Duration recorded
96
+ 3. Work record settled on Solana mainnet
97
+ 4. Trust score updated
98
+ 5. Verify URL returned (Solscan)
99
+
100
+ Your agent's work history is permanent, tamper-evident, and publicly verifiable.
101
+
102
+ ## Pricing
103
+
104
+ | Action | Price |
105
+ |--------|-------|
106
+ | Register | **FREE** |
107
+ | Verify | **FREE** |
108
+ | Attest | **$0.02** per attestation |
109
+
110
+ ## Configuration
111
+
112
+ | Env var | Purpose |
113
+ |---------|---------|
114
+ | `MINT_API_KEY` | your `fnet_` key (required for register/attest) |
115
+ | `MINT_AGENT_NAME` | default agent name (optional) |
116
+ | `MINT_ENDPOINT` | override the MINT server (default `https://mint-mcp-production.up.railway.app`) |
117
+
118
+ No Solana dependency. No wallet. No transaction signing. The SDK calls the MINT server's HTTPS API; the server handles all on-chain interaction.
119
+
120
+ ## Links
121
+
122
+ - Explorer: https://mint.foundrynet.io
123
+ - MCP server: https://github.com/FoundryNet/mint-mcp
124
+ - Protocol: https://foundrynet.io
125
+
126
+ MIT licensed.
@@ -0,0 +1,29 @@
1
+ """mint-attest — universal work attestation for AI agents.
2
+
3
+ from mint_attest import attest
4
+
5
+ @attest(work_type="code_review")
6
+ def review(files):
7
+ return do_review(files)
8
+
9
+ Register once, attest every task, build a verifiable on-chain track record. The
10
+ SDK is a thin HTTPS client to the MINT server; all Solana interaction happens
11
+ server-side. Framework integrations live in mint_attest.langchain / .crewai /
12
+ .autogen (imported only when you use them).
13
+ """
14
+ from __future__ import annotations
15
+
16
+ __version__ = "0.1.0"
17
+
18
+ from .client import MintClient, hash_data
19
+ from .decorator import attest, configure, get_default_client, set_default_client
20
+ from .exceptions import MintAPIError, MintAuthError, MintConfigError, MintError
21
+ from .models import Actor, Receipt, TrustScore
22
+
23
+ __all__ = [
24
+ "__version__",
25
+ "attest", "configure", "MintClient",
26
+ "get_default_client", "set_default_client", "hash_data",
27
+ "Actor", "Receipt", "TrustScore",
28
+ "MintError", "MintAuthError", "MintAPIError", "MintConfigError",
29
+ ]
@@ -0,0 +1,5 @@
1
+ """Convenience shim so `from mint_attest.autogen import MintAttestHook` works
2
+ (the implementation lives in mint_attest.integrations.autogen)."""
3
+ from .integrations.autogen import MintAttestHook
4
+
5
+ __all__ = ["MintAttestHook"]
@@ -0,0 +1,176 @@
1
+ """MintClient — the core: register, attest, verify against the MINT REST surface.
2
+
3
+ Talks plain HTTPS to the MINT server (default mint-mcp); the server handles all
4
+ Solana interaction. The developer never touches a wallet, signs a transaction, or
5
+ imports a blockchain library — it's just an API call. The developer's fnet_ key
6
+ (arg or MINT_API_KEY env) authenticates every request, so the agent and its
7
+ attestations belong to their account.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import json
13
+ import math
14
+ import os
15
+ import time
16
+ from typing import Any, Optional
17
+
18
+ import httpx
19
+
20
+ from . import __version__
21
+ from .exceptions import MintAPIError, MintAuthError, MintConfigError
22
+ from .models import Actor, Receipt, TrustScore
23
+
24
+ DEFAULT_ENDPOINT = "https://mint-mcp-production.up.railway.app"
25
+
26
+
27
+ def hash_data(obj: Any) -> str:
28
+ """Deterministic SHA-256 of any value. bytes as-is, str as utf-8, everything
29
+ else as canonical (sorted-key) JSON so the same input always hashes the same."""
30
+ if isinstance(obj, (bytes, bytearray)):
31
+ b = bytes(obj)
32
+ elif isinstance(obj, str):
33
+ b = obj.encode("utf-8")
34
+ else:
35
+ b = json.dumps(obj, sort_keys=True, default=str).encode("utf-8")
36
+ return hashlib.sha256(b).hexdigest()
37
+
38
+
39
+ def _strip_none(d: dict) -> dict:
40
+ return {k: v for k, v in d.items() if v is not None}
41
+
42
+
43
+ class MintClient:
44
+ """One client per agent process. Register once (or let attest auto-register),
45
+ then attest each unit of work."""
46
+
47
+ def __init__(self, api_key: Optional[str] = None, endpoint: Optional[str] = None,
48
+ *, name: Optional[str] = None, actor_type: str = "ai_agent",
49
+ capabilities: Optional[list] = None, operator: Optional[str] = None,
50
+ timeout: float = 30.0, transport: Optional[httpx.BaseTransport] = None):
51
+ self.api_key = api_key or os.environ.get("MINT_API_KEY")
52
+ self.endpoint = (endpoint or os.environ.get("MINT_ENDPOINT") or DEFAULT_ENDPOINT).rstrip("/")
53
+ self.timeout = timeout
54
+ # defaults used by auto-register; name also fills from MINT_AGENT_NAME
55
+ self._name = name or os.environ.get("MINT_AGENT_NAME")
56
+ self._actor_type = actor_type
57
+ self._capabilities = capabilities
58
+ self._operator = operator or os.environ.get("MINT_OPERATOR")
59
+ self._actor: Optional[Actor] = None
60
+ self._http = httpx.Client(timeout=timeout, transport=transport)
61
+
62
+ # ── identity helpers ──────────────────────────────────────────────────────
63
+ @property
64
+ def mint_id(self) -> Optional[str]:
65
+ return self._actor.mint_id if self._actor else None
66
+
67
+ @property
68
+ def actor(self) -> Optional[Actor]:
69
+ return self._actor
70
+
71
+ def _headers(self) -> dict:
72
+ if not self.api_key:
73
+ raise MintAuthError(
74
+ "No API key. Pass MintClient(api_key='fnet_…') or set MINT_API_KEY.")
75
+ return {"Authorization": f"Bearer {self.api_key}",
76
+ "Content-Type": "application/json",
77
+ "User-Agent": f"mint-attest/{__version__}"}
78
+
79
+ def _post(self, path: str, body: dict) -> dict:
80
+ try:
81
+ r = self._http.post(self.endpoint + path, json=body, headers=self._headers())
82
+ except httpx.HTTPError as e:
83
+ raise MintAPIError(f"network error calling {path}: {e}") from e
84
+ try:
85
+ data = r.json()
86
+ except Exception:
87
+ raise MintAPIError(f"non-JSON response from {path} (HTTP {r.status_code})",
88
+ status=r.status_code, detail=r.text[:300])
89
+ if r.status_code >= 400 or (isinstance(data, dict) and data.get("error")):
90
+ detail = data.get("detail") if isinstance(data, dict) else data
91
+ code = data.get("error") if isinstance(data, dict) else f"http_{r.status_code}"
92
+ if r.status_code in (401, 403):
93
+ raise MintAuthError(f"{code}: {detail}")
94
+ raise MintAPIError(f"{code}: {detail}", status=r.status_code, detail=detail)
95
+ return data
96
+
97
+ # ── register ──────────────────────────────────────────────────────────────
98
+ def register(self, name: Optional[str] = None, actor_type: Optional[str] = None,
99
+ capabilities: Optional[list] = None, operator: Optional[str] = None,
100
+ metadata: Optional[dict] = None) -> Actor:
101
+ """Provision (or look up — idempotent) this agent's MINT identity. Caches
102
+ the mint_id so later attest() calls reuse it. FREE."""
103
+ resolved_name = name or self._name
104
+ if not resolved_name:
105
+ raise MintConfigError(
106
+ "An agent name is required to register. Pass name=… (or set "
107
+ "MINT_AGENT_NAME / use @attest, which names the agent after the function).")
108
+ body = _strip_none({
109
+ "name": resolved_name,
110
+ "actor_type": actor_type or self._actor_type,
111
+ "capabilities": capabilities if capabilities is not None else self._capabilities,
112
+ "operator": operator or self._operator,
113
+ "metadata": metadata,
114
+ })
115
+ actor = Actor.from_dict(self._post("/v1/register", body))
116
+ self._actor = actor
117
+ # remember resolved defaults for any future auto-register
118
+ self._name = self._name or resolved_name
119
+ return actor
120
+
121
+ def _ensure_actor(self) -> Actor:
122
+ if self._actor is None:
123
+ self.register()
124
+ return self._actor # type: ignore[return-value]
125
+
126
+ # ── attest ─────────────────────────────────────────────────────────────────
127
+ def attest(self, work_type: str, input_data: Any = None, output_data: Any = None,
128
+ duration_seconds: Optional[float] = None, summary: Optional[str] = None,
129
+ metadata: Optional[dict] = None, *, mint_id: Optional[str] = None,
130
+ input_hash: Optional[str] = None, output_hash: Optional[str] = None) -> Receipt:
131
+ """Attest a completed unit of work — anchors a tamper-evident record on
132
+ Solana mainnet and returns a Receipt (with verify_url). 2¢ per attestation.
133
+
134
+ Pass input_data/output_data (any value — hashed for you) OR precomputed
135
+ input_hash/output_hash. Auto-registers the agent on the first call if it
136
+ hasn't been registered yet.
137
+ """
138
+ mid = mint_id or self.mint_id or self._ensure_actor().mint_id
139
+ if input_hash is None and input_data is not None:
140
+ input_hash = hash_data(input_data)
141
+ if output_hash is None and output_data is not None:
142
+ output_hash = hash_data(output_data)
143
+ # Forge requires duration_seconds > 0; round sub-second work up to 1.
144
+ dur = max(1, int(math.ceil(duration_seconds))) if duration_seconds else 1
145
+ body = _strip_none({
146
+ "mint_id": mid, "work_type": work_type, "duration_seconds": dur,
147
+ "summary": summary or f"{work_type} completed",
148
+ "input_hash": input_hash, "output_hash": output_hash, "metadata": metadata,
149
+ })
150
+ return Receipt.from_dict(self._post("/v1/attest", body))
151
+
152
+ # ── verify ──────────────────────────────────────────────────────────────────
153
+ def verify(self, mint_id: Optional[str] = None, actor_name: Optional[str] = None,
154
+ actor_type: Optional[str] = None) -> TrustScore:
155
+ """Look up any actor's trust profile. FREE. Defaults to this client's own
156
+ agent when no identifier is given."""
157
+ body = _strip_none({
158
+ "mint_id": mint_id or (None if actor_name else self.mint_id),
159
+ "actor_name": actor_name, "actor_type": actor_type,
160
+ })
161
+ if not body:
162
+ raise MintConfigError("Provide mint_id or actor_name (or register first).")
163
+ return TrustScore.from_dict(self._post("/v1/verify", body))
164
+
165
+ # ── lifecycle ────────────────────────────────────────────────────────────────
166
+ def close(self) -> None:
167
+ try:
168
+ self._http.close()
169
+ except Exception:
170
+ pass
171
+
172
+ def __enter__(self) -> "MintClient":
173
+ return self
174
+
175
+ def __exit__(self, *exc) -> None:
176
+ self.close()
@@ -0,0 +1,5 @@
1
+ """Convenience shim so `from mint_attest.crewai import MintAttestTool` works
2
+ (the implementation lives in mint_attest.integrations.crewai)."""
3
+ from .integrations.crewai import MintAttestTool, mint_attest_step_callback
4
+
5
+ __all__ = ["MintAttestTool", "mint_attest_step_callback"]
@@ -0,0 +1,97 @@
1
+ """@attest — zero-friction attestation for any Python function, plus the
2
+ process-wide default client.
3
+
4
+ from mint_attest import attest
5
+
6
+ @attest(work_type="code_review")
7
+ def review_code(files):
8
+ return do_the_review(files)
9
+
10
+ Every call hashes the inputs/output, times the call, attests on MINT, and returns
11
+ the function's result UNCHANGED. Attestation failures (no key, network blip) are
12
+ logged and swallowed by default — instrumentation must never break the agent.
13
+ Set strict=True to raise instead.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ import math
19
+ import time
20
+ from functools import wraps
21
+ from typing import Callable, Optional
22
+
23
+ from .client import MintClient, hash_data
24
+ from .exceptions import MintError
25
+
26
+ log = logging.getLogger("mint_attest")
27
+
28
+ _default_client: Optional[MintClient] = None
29
+
30
+
31
+ def get_default_client() -> MintClient:
32
+ """The lazily-created process-wide client (uses MINT_API_KEY / env defaults)."""
33
+ global _default_client
34
+ if _default_client is None:
35
+ _default_client = MintClient()
36
+ return _default_client
37
+
38
+
39
+ def set_default_client(client: MintClient) -> None:
40
+ """Override the process-wide client (e.g. with a custom endpoint or key)."""
41
+ global _default_client
42
+ _default_client = client
43
+
44
+
45
+ def configure(api_key: Optional[str] = None, endpoint: Optional[str] = None,
46
+ name: Optional[str] = None, actor_type: str = "ai_agent",
47
+ capabilities: Optional[list] = None, operator: Optional[str] = None) -> MintClient:
48
+ """Set up the default client in one call. Optional — env vars work too."""
49
+ client = MintClient(api_key=api_key, endpoint=endpoint, name=name,
50
+ actor_type=actor_type, capabilities=capabilities, operator=operator)
51
+ set_default_client(client)
52
+ return client
53
+
54
+
55
+ def attest(work_type: str, name: Optional[str] = None,
56
+ capabilities: Optional[list] = None, client: Optional[MintClient] = None,
57
+ strict: bool = False) -> Callable:
58
+ """Decorator: attest every call of the wrapped function on MINT.
59
+
60
+ Args:
61
+ work_type: code_review|research|generation|analysis|delivery|
62
+ normalization|manufacturing|custom.
63
+ name: agent name to register under (defaults to the function's name; set
64
+ once for the process).
65
+ capabilities: capability tags recorded at registration.
66
+ client: a specific MintClient (defaults to the process-wide one).
67
+ strict: if True, re-raise attestation errors instead of swallowing them.
68
+ """
69
+ def wrapper(func: Callable) -> Callable:
70
+ @wraps(func)
71
+ def inner(*args, **kwargs):
72
+ c = client or get_default_client()
73
+ # name the agent once: explicit name > whatever's already set > func name
74
+ if not c._name:
75
+ c._name = name or func.__name__
76
+ if capabilities and c._capabilities is None:
77
+ c._capabilities = capabilities
78
+
79
+ start = time.time()
80
+ result = func(*args, **kwargs) # run the real work first
81
+ duration = max(1, int(math.ceil(time.time() - start)))
82
+
83
+ try:
84
+ c.attest(
85
+ work_type=work_type,
86
+ input_hash=hash_data({"args": args, "kwargs": kwargs}),
87
+ output_hash=hash_data(result),
88
+ duration_seconds=duration,
89
+ summary=f"{func.__name__} completed",
90
+ )
91
+ except MintError as e:
92
+ if strict:
93
+ raise
94
+ log.warning("mint-attest: attestation skipped (%s) — returning result anyway", e)
95
+ return result
96
+ return inner
97
+ return wrapper
@@ -0,0 +1,31 @@
1
+ """Exceptions for the mint-attest SDK.
2
+
3
+ All inherit from MintError so callers can `except MintError` to catch everything.
4
+ The @attest decorator swallows MintError by default (attestation must never break
5
+ the wrapped function); the explicit MintClient methods raise so callers can handle.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Optional
10
+
11
+
12
+ class MintError(Exception):
13
+ """Base class for all mint-attest errors."""
14
+
15
+
16
+ class MintAuthError(MintError):
17
+ """No API key configured, or the server rejected it."""
18
+
19
+
20
+ class MintConfigError(MintError):
21
+ """The client is missing required configuration (e.g. an agent name)."""
22
+
23
+
24
+ class MintAPIError(MintError):
25
+ """The MINT API returned an error or was unreachable."""
26
+
27
+ def __init__(self, message: str, *, status: Optional[int] = None,
28
+ detail: Any = None):
29
+ super().__init__(message)
30
+ self.status = status
31
+ self.detail = detail
@@ -0,0 +1,8 @@
1
+ """Framework integrations for mint-attest.
2
+
3
+ Each module wraps a MintClient in a framework's native hook/callback/tool pattern
4
+ so a developer adds ONE line to their existing agent and every task attests. The
5
+ frameworks are optional dependencies — these modules import them lazily and raise
6
+ a clear, install-hint error if the framework isn't present. Importing
7
+ `mint_attest` itself never imports any framework.
8
+ """