hermes-spend 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,221 @@
1
+ Metadata-Version: 2.4
2
+ Name: hermes-spend
3
+ Version: 0.1.0
4
+ Summary: Native wallet, service registration, and reputation ledger for Hermes Agent. Implements Hermes issue #38280.
5
+ License: Apache-2.0
6
+ Project-URL: Homepage, https://bonanza-labs.com
7
+ Project-URL: Repository, https://github.com/c6zks4gssn-droid/hermes-spend
8
+ Project-URL: Issues, https://github.com/c6zks4gssn-droid/hermes-spend/issues
9
+ Keywords: hermes,agent,wallet,x402,payments,reputation,service-registry,ai,spending
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ Provides-Extra: x402
18
+ Requires-Dist: x402-firewall>=0.1.0; extra == "x402"
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=8; extra == "dev"
21
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
22
+
23
+ # hermes-spend
24
+
25
+ **Native wallet, service registry, and reputation ledger for Hermes Agent.**
26
+
27
+ Directly implements [Hermes Agent GitHub issue #38280](https://github.com/NousResearch/hermes/issues/38280):
28
+ > *"[Feature Request] Native Wallet, Service Registration, Reputation Ledger"*
29
+
30
+ ```bash
31
+ pip install hermes-spend
32
+ ```
33
+
34
+ ---
35
+
36
+ ## What this gives Hermes agents
37
+
38
+ | Feature | Description |
39
+ |---|---|
40
+ | **Wallet** | Pay x402 endpoints with hard budget caps, vendor lists, and approval callbacks |
41
+ | **Service Registry** | Discover and register paid services by query, category, or price |
42
+ | **Reputation Ledger** | Track vendor trustworthiness — auto-updates on every payment |
43
+
44
+ ---
45
+
46
+ ## Quickstart
47
+
48
+ ```python
49
+ from hermes_spend import HermesSpendSkill
50
+
51
+ skill = HermesSpendSkill.from_config({
52
+ "budget_per_session": 10.00,
53
+ "require_approval_above": 2.00,
54
+ "block_vendors": ["untrusted.com"],
55
+ })
56
+
57
+ # Pay an x402 endpoint
58
+ result = await skill.wallet_pay(
59
+ vendor_url="https://data.example.com/report",
60
+ amount_usd=1.50,
61
+ chain="base",
62
+ )
63
+ # → {"status": "ok", "receipt": {"vendor": "data.example.com", ...}}
64
+
65
+ # Check balance
66
+ bal = skill.wallet_balance()
67
+ # → {"available_usd": 8.50, "spent_usd": 1.50, "payment_count": 1}
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Wallet
73
+
74
+ ```python
75
+ # Pay with budget enforcement
76
+ result = await skill.wallet_pay("https://api.example.com/data", 1.50)
77
+
78
+ # Check balance
79
+ bal = skill.wallet_balance()
80
+ print(f"${bal['available_usd']:.2f} remaining")
81
+
82
+ # Full payment history
83
+ history = skill.wallet_history()
84
+ ```
85
+
86
+ **Config options:**
87
+
88
+ | Key | Default | Description |
89
+ |---|---|---|
90
+ | `budget_per_session` | `∞` | Max total spend per session |
91
+ | `require_approval_above` | `None` | Payments ≥ this USD need human approval |
92
+ | `block_vendors` | `[]` | Blocked hostnames |
93
+ | `allow_vendors` | `None` | Allowlist (blocks all others if set) |
94
+ | `max_single_payment` | `∞` | Hard cap per payment |
95
+ | `webhook_url` | `""` | Webhook for approval requests |
96
+
97
+ ---
98
+
99
+ ## Service Registry
100
+
101
+ Agents can register services they provide and discover services provided by others.
102
+
103
+ ```python
104
+ # Register a service you offer
105
+ skill.service_register(
106
+ service_id="weather-api-v1",
107
+ name="Real-time Weather",
108
+ endpoint_url="https://weather.example.com/v1",
109
+ price_per_call_usd=0.002,
110
+ chain="base",
111
+ category="data",
112
+ tags=["weather", "realtime"],
113
+ )
114
+
115
+ # Discover services
116
+ results = skill.service_search("weather", max_price_usd=0.01)
117
+ for svc in results:
118
+ print(f"{svc['name']} — ${svc['price_per_call_usd']:.4f}/call at {svc['endpoint_url']}")
119
+
120
+ # Pay and use a discovered service
121
+ service = results[0]
122
+ receipt = await skill.wallet_pay(service["endpoint_url"], service["price_per_call_usd"])
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Reputation Ledger
128
+
129
+ The reputation ledger auto-updates on every successful payment. Agents can also record data quality events.
130
+
131
+ ```python
132
+ # Automatic — every wallet_pay() records "payment_success"
133
+ await skill.wallet_pay("https://good-vendor.com/data", 1.00)
134
+
135
+ # Manual events
136
+ skill.reputation_record("good-vendor.com", "data_quality_good", note="accurate and fast")
137
+ skill.reputation_record("bad-vendor.com", "fraud_detected", note="returned fake data")
138
+
139
+ # Check before paying
140
+ score = skill.reputation_score("shady.example.com")
141
+ if not score["is_trusted"]:
142
+ raise Exception(f"Vendor not trusted: {score['trust_level']}")
143
+
144
+ # Top vendors
145
+ top = skill.reputation_top(limit=5)
146
+ ```
147
+
148
+ **Event types and their impact:**
149
+
150
+ | Event | Score delta | Use case |
151
+ |---|---|---|
152
+ | `data_quality_good` | +1.0 | Service returned accurate data |
153
+ | `payment_success` | +0.5 | Payment completed successfully |
154
+ | `payment_failed` | -1.0 | Payment failed or bounced |
155
+ | `data_quality_bad` | -1.5 | Service returned bad/stale data |
156
+ | `service_unavailable` | -0.5 | Service was down |
157
+ | `payment_disputed` | -2.0 | Payment disputed |
158
+ | `fraud_detected` | -10.0 | Confirmed fraud |
159
+
160
+ **Trust levels:** `high` (score ≥ 1.0) · `medium` (≥ 0) · `low` (> -20) · `blocked` (≤ -20)
161
+
162
+ Scores decay over time (30-day half-life) so recent events matter more than old ones.
163
+
164
+ ---
165
+
166
+ ## Direct module usage
167
+
168
+ ```python
169
+ from hermes_spend import Wallet, ServiceRegistry, ReputationLedger
170
+
171
+ # Wallet
172
+ wallet = Wallet(
173
+ session_budget=10.00,
174
+ require_approval_above=2.00,
175
+ on_approval_needed=lambda receipt: my_approval_fn(receipt),
176
+ )
177
+ receipt = await wallet.pay("https://data.example.com", 1.50)
178
+
179
+ # Service Registry
180
+ registry = ServiceRegistry()
181
+ registry.register(ServiceRecord(
182
+ service_id="svc-1",
183
+ name="My API",
184
+ endpoint_url="https://my-api.com",
185
+ price_per_call_usd=0.001,
186
+ ))
187
+ results = registry.search("my")
188
+
189
+ # Reputation Ledger
190
+ ledger = ReputationLedger()
191
+ ledger.record("vendor.com", "payment_success")
192
+ score = ledger.score("vendor.com")
193
+ ```
194
+
195
+ ---
196
+
197
+ ## Hermes skill config (hermes-spend.yaml)
198
+
199
+ Place in your Hermes skills directory:
200
+
201
+ ```yaml
202
+ name: hermes-spend
203
+ version: "0.1.0"
204
+ description: "Native wallet, service registration, and reputation ledger"
205
+ author: bonanza-labs
206
+
207
+ config:
208
+ budget_per_session: 10.00
209
+ require_approval_above: 2.00
210
+ block_vendors: []
211
+ webhook_url: "${BONANZA_WEBHOOK_URL}"
212
+ ```
213
+
214
+ ---
215
+
216
+ ## License
217
+
218
+ Apache 2.0. Built by [Bonanza Labs](https://bonanza-labs.com).
219
+
220
+ For a managed dashboard with approval UI, policy editor, and audit reports:
221
+ → **[bonanza-labs.com/firewall](https://bonanza-labs.com/firewall)**
@@ -0,0 +1,199 @@
1
+ # hermes-spend
2
+
3
+ **Native wallet, service registry, and reputation ledger for Hermes Agent.**
4
+
5
+ Directly implements [Hermes Agent GitHub issue #38280](https://github.com/NousResearch/hermes/issues/38280):
6
+ > *"[Feature Request] Native Wallet, Service Registration, Reputation Ledger"*
7
+
8
+ ```bash
9
+ pip install hermes-spend
10
+ ```
11
+
12
+ ---
13
+
14
+ ## What this gives Hermes agents
15
+
16
+ | Feature | Description |
17
+ |---|---|
18
+ | **Wallet** | Pay x402 endpoints with hard budget caps, vendor lists, and approval callbacks |
19
+ | **Service Registry** | Discover and register paid services by query, category, or price |
20
+ | **Reputation Ledger** | Track vendor trustworthiness — auto-updates on every payment |
21
+
22
+ ---
23
+
24
+ ## Quickstart
25
+
26
+ ```python
27
+ from hermes_spend import HermesSpendSkill
28
+
29
+ skill = HermesSpendSkill.from_config({
30
+ "budget_per_session": 10.00,
31
+ "require_approval_above": 2.00,
32
+ "block_vendors": ["untrusted.com"],
33
+ })
34
+
35
+ # Pay an x402 endpoint
36
+ result = await skill.wallet_pay(
37
+ vendor_url="https://data.example.com/report",
38
+ amount_usd=1.50,
39
+ chain="base",
40
+ )
41
+ # → {"status": "ok", "receipt": {"vendor": "data.example.com", ...}}
42
+
43
+ # Check balance
44
+ bal = skill.wallet_balance()
45
+ # → {"available_usd": 8.50, "spent_usd": 1.50, "payment_count": 1}
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Wallet
51
+
52
+ ```python
53
+ # Pay with budget enforcement
54
+ result = await skill.wallet_pay("https://api.example.com/data", 1.50)
55
+
56
+ # Check balance
57
+ bal = skill.wallet_balance()
58
+ print(f"${bal['available_usd']:.2f} remaining")
59
+
60
+ # Full payment history
61
+ history = skill.wallet_history()
62
+ ```
63
+
64
+ **Config options:**
65
+
66
+ | Key | Default | Description |
67
+ |---|---|---|
68
+ | `budget_per_session` | `∞` | Max total spend per session |
69
+ | `require_approval_above` | `None` | Payments ≥ this USD need human approval |
70
+ | `block_vendors` | `[]` | Blocked hostnames |
71
+ | `allow_vendors` | `None` | Allowlist (blocks all others if set) |
72
+ | `max_single_payment` | `∞` | Hard cap per payment |
73
+ | `webhook_url` | `""` | Webhook for approval requests |
74
+
75
+ ---
76
+
77
+ ## Service Registry
78
+
79
+ Agents can register services they provide and discover services provided by others.
80
+
81
+ ```python
82
+ # Register a service you offer
83
+ skill.service_register(
84
+ service_id="weather-api-v1",
85
+ name="Real-time Weather",
86
+ endpoint_url="https://weather.example.com/v1",
87
+ price_per_call_usd=0.002,
88
+ chain="base",
89
+ category="data",
90
+ tags=["weather", "realtime"],
91
+ )
92
+
93
+ # Discover services
94
+ results = skill.service_search("weather", max_price_usd=0.01)
95
+ for svc in results:
96
+ print(f"{svc['name']} — ${svc['price_per_call_usd']:.4f}/call at {svc['endpoint_url']}")
97
+
98
+ # Pay and use a discovered service
99
+ service = results[0]
100
+ receipt = await skill.wallet_pay(service["endpoint_url"], service["price_per_call_usd"])
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Reputation Ledger
106
+
107
+ The reputation ledger auto-updates on every successful payment. Agents can also record data quality events.
108
+
109
+ ```python
110
+ # Automatic — every wallet_pay() records "payment_success"
111
+ await skill.wallet_pay("https://good-vendor.com/data", 1.00)
112
+
113
+ # Manual events
114
+ skill.reputation_record("good-vendor.com", "data_quality_good", note="accurate and fast")
115
+ skill.reputation_record("bad-vendor.com", "fraud_detected", note="returned fake data")
116
+
117
+ # Check before paying
118
+ score = skill.reputation_score("shady.example.com")
119
+ if not score["is_trusted"]:
120
+ raise Exception(f"Vendor not trusted: {score['trust_level']}")
121
+
122
+ # Top vendors
123
+ top = skill.reputation_top(limit=5)
124
+ ```
125
+
126
+ **Event types and their impact:**
127
+
128
+ | Event | Score delta | Use case |
129
+ |---|---|---|
130
+ | `data_quality_good` | +1.0 | Service returned accurate data |
131
+ | `payment_success` | +0.5 | Payment completed successfully |
132
+ | `payment_failed` | -1.0 | Payment failed or bounced |
133
+ | `data_quality_bad` | -1.5 | Service returned bad/stale data |
134
+ | `service_unavailable` | -0.5 | Service was down |
135
+ | `payment_disputed` | -2.0 | Payment disputed |
136
+ | `fraud_detected` | -10.0 | Confirmed fraud |
137
+
138
+ **Trust levels:** `high` (score ≥ 1.0) · `medium` (≥ 0) · `low` (> -20) · `blocked` (≤ -20)
139
+
140
+ Scores decay over time (30-day half-life) so recent events matter more than old ones.
141
+
142
+ ---
143
+
144
+ ## Direct module usage
145
+
146
+ ```python
147
+ from hermes_spend import Wallet, ServiceRegistry, ReputationLedger
148
+
149
+ # Wallet
150
+ wallet = Wallet(
151
+ session_budget=10.00,
152
+ require_approval_above=2.00,
153
+ on_approval_needed=lambda receipt: my_approval_fn(receipt),
154
+ )
155
+ receipt = await wallet.pay("https://data.example.com", 1.50)
156
+
157
+ # Service Registry
158
+ registry = ServiceRegistry()
159
+ registry.register(ServiceRecord(
160
+ service_id="svc-1",
161
+ name="My API",
162
+ endpoint_url="https://my-api.com",
163
+ price_per_call_usd=0.001,
164
+ ))
165
+ results = registry.search("my")
166
+
167
+ # Reputation Ledger
168
+ ledger = ReputationLedger()
169
+ ledger.record("vendor.com", "payment_success")
170
+ score = ledger.score("vendor.com")
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Hermes skill config (hermes-spend.yaml)
176
+
177
+ Place in your Hermes skills directory:
178
+
179
+ ```yaml
180
+ name: hermes-spend
181
+ version: "0.1.0"
182
+ description: "Native wallet, service registration, and reputation ledger"
183
+ author: bonanza-labs
184
+
185
+ config:
186
+ budget_per_session: 10.00
187
+ require_approval_above: 2.00
188
+ block_vendors: []
189
+ webhook_url: "${BONANZA_WEBHOOK_URL}"
190
+ ```
191
+
192
+ ---
193
+
194
+ ## License
195
+
196
+ Apache 2.0. Built by [Bonanza Labs](https://bonanza-labs.com).
197
+
198
+ For a managed dashboard with approval UI, policy editor, and audit reports:
199
+ → **[bonanza-labs.com/firewall](https://bonanza-labs.com/firewall)**
@@ -0,0 +1,35 @@
1
+ """
2
+ hermes-spend: Native wallet, service registration, and reputation ledger for Hermes Agent.
3
+
4
+ Directly addresses Hermes Agent GitHub issue #38280:
5
+ "[Feature Request] Native Wallet, Service Registration, Reputation Ledger"
6
+
7
+ Install as a Hermes skill:
8
+ hermes skill install bonanza/hermes-spend
9
+
10
+ Or manually:
11
+ pip install hermes-spend
12
+ # Add to your hermes config:
13
+ # skills:
14
+ # - name: hermes-spend
15
+ # config:
16
+ # api_key: "${BONANZA_API_KEY}"
17
+ # budget_per_session: 10.00
18
+ """
19
+
20
+ from .wallet import Wallet, WalletBalance, PaymentReceipt
21
+ from .registration import ServiceRegistry, ServiceRecord
22
+ from .reputation import ReputationLedger, ReputationScore
23
+ from .skill import HermesSpendSkill
24
+
25
+ __version__ = "0.1.0"
26
+ __all__ = [
27
+ "HermesSpendSkill",
28
+ "Wallet",
29
+ "WalletBalance",
30
+ "PaymentReceipt",
31
+ "ServiceRegistry",
32
+ "ServiceRecord",
33
+ "ReputationLedger",
34
+ "ReputationScore",
35
+ ]
@@ -0,0 +1,122 @@
1
+ """
2
+ Service Registry: lets Hermes agents discover and register paid services.
3
+ Addresses Hermes issue #38280 — "Service Registration".
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import threading
9
+ import time
10
+ from dataclasses import dataclass, field
11
+ from typing import Literal
12
+
13
+
14
+ ServiceCategory = Literal["data", "compute", "storage", "api", "media", "other"]
15
+
16
+
17
+ @dataclass
18
+ class ServiceRecord:
19
+ """A registered service that agents can discover and pay to use."""
20
+ service_id: str
21
+ name: str
22
+ endpoint_url: str
23
+ price_per_call_usd: float
24
+ chain: str = "base"
25
+ category: ServiceCategory = "other"
26
+ description: str = ""
27
+ tags: list[str] = field(default_factory=list)
28
+ registered_at: float = field(default_factory=time.time)
29
+ calls_total: int = 0
30
+ revenue_total_usd: float = 0.0
31
+
32
+ def matches(self, query: str) -> bool:
33
+ q = query.lower()
34
+ return (
35
+ q in self.name.lower()
36
+ or q in self.description.lower()
37
+ or q in self.category.lower()
38
+ or any(q in t.lower() for t in self.tags)
39
+ )
40
+
41
+
42
+ class ServiceRegistry:
43
+ """
44
+ In-memory service registry for Hermes agents.
45
+
46
+ Agents can register services they provide and discover services
47
+ provided by others. Combined with Wallet, agents can pay for
48
+ discovered services automatically.
49
+
50
+ Example:
51
+ registry = ServiceRegistry()
52
+
53
+ # Register your service
54
+ registry.register(ServiceRecord(
55
+ service_id="my-data-api",
56
+ name="Real-time Market Data",
57
+ endpoint_url="https://market.example.com/v1",
58
+ price_per_call_usd=0.001,
59
+ category="data",
60
+ tags=["finance", "realtime"],
61
+ ))
62
+
63
+ # Discover services
64
+ results = registry.search("market data")
65
+ service = results[0]
66
+
67
+ # Pay and call
68
+ receipt = await wallet.pay(service.endpoint_url, service.price_per_call_usd)
69
+ """
70
+
71
+ def __init__(self) -> None:
72
+ self._services: dict[str, ServiceRecord] = {}
73
+ self._lock = threading.Lock()
74
+
75
+ def register(self, service: ServiceRecord) -> ServiceRecord:
76
+ with self._lock:
77
+ self._services[service.service_id] = service
78
+ return service
79
+
80
+ def unregister(self, service_id: str) -> bool:
81
+ with self._lock:
82
+ return self._services.pop(service_id, None) is not None
83
+
84
+ def get(self, service_id: str) -> ServiceRecord | None:
85
+ with self._lock:
86
+ return self._services.get(service_id)
87
+
88
+ def search(
89
+ self,
90
+ query: str = "",
91
+ category: ServiceCategory | None = None,
92
+ max_price_usd: float | None = None,
93
+ chain: str | None = None,
94
+ limit: int = 20,
95
+ ) -> list[ServiceRecord]:
96
+ with self._lock:
97
+ results = list(self._services.values())
98
+
99
+ if query:
100
+ results = [s for s in results if s.matches(query)]
101
+ if category:
102
+ results = [s for s in results if s.category == category]
103
+ if max_price_usd is not None:
104
+ results = [s for s in results if s.price_per_call_usd <= max_price_usd]
105
+ if chain:
106
+ results = [s for s in results if s.chain == chain]
107
+
108
+ return sorted(results, key=lambda s: s.price_per_call_usd)[:limit]
109
+
110
+ def list_all(self) -> list[ServiceRecord]:
111
+ with self._lock:
112
+ return list(self._services.values())
113
+
114
+ def record_call(self, service_id: str, revenue_usd: float) -> None:
115
+ with self._lock:
116
+ svc = self._services.get(service_id)
117
+ if svc:
118
+ svc.calls_total += 1
119
+ svc.revenue_total_usd += revenue_usd
120
+
121
+ def __len__(self) -> int:
122
+ return len(self._services)