agentguard-spend-hermes 0.1.4__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,70 @@
1
+ .vercel
2
+ .next/
3
+ node_modules/
4
+
5
+ /generated/prisma
6
+
7
+ # clerk configuration (can include secrets)
8
+ /.clerk/
9
+
10
+ # Environment files (NEVER commit secrets!)
11
+ .env
12
+ .env.local
13
+ .env.*.local
14
+ agent/.env
15
+ *.pem
16
+
17
+ # Hardhat artifacts
18
+ cache/
19
+ artifacts/
20
+ .env*.local
21
+ .env.production
22
+ supabase/.temp/
23
+
24
+ # Git worktrees
25
+ .worktrees/
26
+
27
+ # Sentinela ML model artifacts (large binary files — stored in GCS, not git)
28
+ models/sentinela/l1-xlmr-v1/
29
+ models/sentinela/l2-xgboost-v1/
30
+ models/sentinela/training_data.jsonl
31
+ !models/sentinela/.gitkeep
32
+ *.onnx
33
+ *.ubj
34
+
35
+ # Sentinela L1 Cloud Run -- large artifacts (downloaded in CI, never committed)
36
+ agents/sentinela-l1/models/*.onnx
37
+ agents/sentinela-l1/tokenizer/
38
+ !agents/sentinela-l1/models/.gitkeep
39
+ !agents/sentinela-l1/tokenizer/.gitkeep
40
+
41
+ # Python artifacts
42
+ __pycache__/
43
+ *.egg-info/
44
+ dist/
45
+
46
+ # Playwright MCP cache
47
+ .playwright-mcp/
48
+
49
+ # Vercel check artifacts
50
+ .env.vercel-check
51
+
52
+ # Build/test artifacts
53
+ *.tsbuildinfo
54
+ .turbo/
55
+
56
+ # Debug/log files
57
+ firebase-debug.log
58
+
59
+ # Script output artifacts
60
+ scripts/*.json
61
+ list-all-products.js
62
+ find-starter-product.js
63
+ create-starter-product.js
64
+ test-stripe-key.js
65
+ broadcast/
66
+ .DS_Store
67
+
68
+ # TypeScript build cache (auto-generated)
69
+ tsconfig.tsbuildinfo
70
+ *.tsbuildinfo
@@ -0,0 +1,87 @@
1
+ AgentGuard(TM) Spend SDK — Alpha License
2
+ Copyright (c) 2026 Dunecrest Ventures Inc.
3
+
4
+ 1. SCOPE.
5
+ This software, including all files under packages/agentguard-spend-python/agentguard_spend/, is
6
+ licensed by Dunecrest Ventures Inc. ("Licensor") subject to the following
7
+ thresholds:
8
+
9
+ (a) Evaluation Use. Internal evaluation, prototyping, and non-commercial
10
+ development at any call volume.
11
+
12
+ (b) Free Production Threshold. Production deployments processing 10,000
13
+ or fewer enforcement calls per calendar month, in aggregate across
14
+ all instances operated by the licensee, are permitted under this
15
+ License without additional fee.
16
+
17
+ (c) Commercial License Required. Production deployments processing more
18
+ than 10,000 enforcement calls per calendar month, deployments
19
+ operated for the benefit of third parties as a service, redistribution,
20
+ sublicensing, public hosting, and republication each require a
21
+ separate commercial license agreement with Licensor.
22
+
23
+ Commercial-license inquiries: invest@agentguard.run
24
+
25
+ 2. NO PATENT LICENSE GRANTED.
26
+ Nothing in this License grants, expressly or by implication, any patent license
27
+ to any patent, patent application, or other intellectual property right of
28
+ Licensor. All patent rights are expressly reserved. The patent applications
29
+ identified in Section 7 are not licensed by this License.
30
+
31
+ 3. SEPARATE GRANT FOR DEMONSTRATION ASSETS.
32
+ The following assets, and ONLY these assets, are released under the Apache
33
+ License, Version 2.0, the text of which is reproduced or available at
34
+ https://www.apache.org/licenses/LICENSE-2.0:
35
+
36
+ - The test vectors under packages/agentguard-spend-python/test_vectors/
37
+ - The documentation examples under packages/agentguard-spend-python/examples/
38
+ - The contents of packages/agentguard-spend-python/README.md
39
+
40
+ The source code under packages/agentguard-spend-python/agentguard_spend/ is NOT included in this
41
+ Apache License 2.0 grant. The Python type definitions, policy engine,
42
+ decision log, store implementation, cost table, and wrapper code under
43
+ agentguard_spend/ are licensed only under the alpha evaluation terms of
44
+ Section 1 above.
45
+
46
+ 4. WARRANTY DISCLAIMER.
47
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
48
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
49
+ FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL
50
+ DUNECREST VENTURES INC. BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY
51
+ ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
52
+ DEALINGS IN THE SOFTWARE.
53
+
54
+ 5. SUCCESSORS AND ASSIGNS.
55
+ This License binds and benefits the parties' respective successors and assigns.
56
+ In the event of an asset sale, merger, change of control, or other transfer of
57
+ the Licensor's rights in this software, all rights and obligations under this
58
+ License inure to the benefit of and are binding upon Licensor's successor or
59
+ assignee. Outstanding evaluation grants survive change-of-control, but the
60
+ successor or assignee may, upon thirty (30) days' written notice, terminate
61
+ ongoing evaluation grants in favor of a commercial-license requirement.
62
+
63
+ 6. TERMINATION.
64
+ Licensor may terminate this License with thirty (30) days' written notice for
65
+ any reason or no reason. Upon termination, Licensee shall cease all use of the
66
+ software under agentguard_spend/ and shall destroy all copies in Licensee's possession.
67
+
68
+ 7. PATENT NOTICE (35 U.S.C. § 287).
69
+ Protected by U.S. patent-pending technology, including the following
70
+ provisional patent applications filed with the United States Patent and
71
+ Trademark Office:
72
+
73
+ - Application No. 63/983,615 (filed February 15, 2026)
74
+ - Application No. 63/983,621 (filed February 15, 2026)
75
+ - Application No. 63/983,843 (filed February 16, 2026)
76
+ - Application No. 63/984,626 (filed February 17, 2026)
77
+ - Application No. 64/071,781 (filed May 21, 2026)
78
+ - Application No. 64/071,789 (filed May 21, 2026)
79
+
80
+ Additional patents pending. All patent rights expressly reserved per
81
+ Section 2 above.
82
+
83
+ AgentGuard(TM) is a trademark of Dunecrest Ventures Inc. (USPTO Serial
84
+ No. 99462472, pending). MerchantGuard(TM) is a trademark of Dunecrest
85
+ Ventures Inc. (USPTO Serial No. 99051215, pending).
86
+
87
+ For commercial licensing: invest@agentguard.run
@@ -0,0 +1,275 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentguard-spend-hermes
3
+ Version: 0.1.4
4
+ Summary: AgentGuard Spend plugin for Hermes Agent (Nous Research). One-step install of local spend caps + signed audit logs for every Anthropic/OpenAI call your Hermes agent makes. No proxy, prompts never leave your machine.
5
+ Project-URL: Homepage, https://agentguard.run
6
+ Project-URL: AgentGuard SDK (PyPI), https://pypi.org/project/agentguard-spend/
7
+ Project-URL: AgentGuard SDK (npm), https://www.npmjs.com/package/@agentguard-run/spend
8
+ Project-URL: Hermes Agent, https://github.com/NousResearch/hermes-agent
9
+ Project-URL: Source, https://github.com/MerchantGuard/merchantguard-website/tree/main/packages/agentguard-spend-hermes
10
+ Author-email: "Dunecrest Ventures Inc." <hello@agentguard.run>
11
+ License: AgentGuard(TM) Spend SDK — Alpha License
12
+ Copyright (c) 2026 Dunecrest Ventures Inc.
13
+
14
+ 1. SCOPE.
15
+ This software, including all files under packages/agentguard-spend-python/agentguard_spend/, is
16
+ licensed by Dunecrest Ventures Inc. ("Licensor") subject to the following
17
+ thresholds:
18
+
19
+ (a) Evaluation Use. Internal evaluation, prototyping, and non-commercial
20
+ development at any call volume.
21
+
22
+ (b) Free Production Threshold. Production deployments processing 10,000
23
+ or fewer enforcement calls per calendar month, in aggregate across
24
+ all instances operated by the licensee, are permitted under this
25
+ License without additional fee.
26
+
27
+ (c) Commercial License Required. Production deployments processing more
28
+ than 10,000 enforcement calls per calendar month, deployments
29
+ operated for the benefit of third parties as a service, redistribution,
30
+ sublicensing, public hosting, and republication each require a
31
+ separate commercial license agreement with Licensor.
32
+
33
+ Commercial-license inquiries: invest@agentguard.run
34
+
35
+ 2. NO PATENT LICENSE GRANTED.
36
+ Nothing in this License grants, expressly or by implication, any patent license
37
+ to any patent, patent application, or other intellectual property right of
38
+ Licensor. All patent rights are expressly reserved. The patent applications
39
+ identified in Section 7 are not licensed by this License.
40
+
41
+ 3. SEPARATE GRANT FOR DEMONSTRATION ASSETS.
42
+ The following assets, and ONLY these assets, are released under the Apache
43
+ License, Version 2.0, the text of which is reproduced or available at
44
+ https://www.apache.org/licenses/LICENSE-2.0:
45
+
46
+ - The test vectors under packages/agentguard-spend-python/test_vectors/
47
+ - The documentation examples under packages/agentguard-spend-python/examples/
48
+ - The contents of packages/agentguard-spend-python/README.md
49
+
50
+ The source code under packages/agentguard-spend-python/agentguard_spend/ is NOT included in this
51
+ Apache License 2.0 grant. The Python type definitions, policy engine,
52
+ decision log, store implementation, cost table, and wrapper code under
53
+ agentguard_spend/ are licensed only under the alpha evaluation terms of
54
+ Section 1 above.
55
+
56
+ 4. WARRANTY DISCLAIMER.
57
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
58
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
59
+ FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL
60
+ DUNECREST VENTURES INC. BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY
61
+ ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
62
+ DEALINGS IN THE SOFTWARE.
63
+
64
+ 5. SUCCESSORS AND ASSIGNS.
65
+ This License binds and benefits the parties' respective successors and assigns.
66
+ In the event of an asset sale, merger, change of control, or other transfer of
67
+ the Licensor's rights in this software, all rights and obligations under this
68
+ License inure to the benefit of and are binding upon Licensor's successor or
69
+ assignee. Outstanding evaluation grants survive change-of-control, but the
70
+ successor or assignee may, upon thirty (30) days' written notice, terminate
71
+ ongoing evaluation grants in favor of a commercial-license requirement.
72
+
73
+ 6. TERMINATION.
74
+ Licensor may terminate this License with thirty (30) days' written notice for
75
+ any reason or no reason. Upon termination, Licensee shall cease all use of the
76
+ software under agentguard_spend/ and shall destroy all copies in Licensee's possession.
77
+
78
+ 7. PATENT NOTICE (35 U.S.C. § 287).
79
+ Protected by U.S. patent-pending technology, including the following
80
+ provisional patent applications filed with the United States Patent and
81
+ Trademark Office:
82
+
83
+ - Application No. 63/983,615 (filed February 15, 2026)
84
+ - Application No. 63/983,621 (filed February 15, 2026)
85
+ - Application No. 63/983,843 (filed February 16, 2026)
86
+ - Application No. 63/984,626 (filed February 17, 2026)
87
+ - Application No. 64/071,781 (filed May 21, 2026)
88
+ - Application No. 64/071,789 (filed May 21, 2026)
89
+
90
+ Additional patents pending. All patent rights expressly reserved per
91
+ Section 2 above.
92
+
93
+ AgentGuard(TM) is a trademark of Dunecrest Ventures Inc. (USPTO Serial
94
+ No. 99462472, pending). MerchantGuard(TM) is a trademark of Dunecrest
95
+ Ventures Inc. (USPTO Serial No. 99051215, pending).
96
+
97
+ For commercial licensing: invest@agentguard.run
98
+ License-File: LICENSE
99
+ Keywords: agent-plugin,agentguard,ai-agent-security,hermes,hermes-agent,llm-governance,nous-research,spend-control
100
+ Classifier: Development Status :: 4 - Beta
101
+ Classifier: Intended Audience :: Developers
102
+ Classifier: License :: Other/Proprietary License
103
+ Classifier: Programming Language :: Python :: 3
104
+ Classifier: Programming Language :: Python :: 3.10
105
+ Classifier: Programming Language :: Python :: 3.11
106
+ Classifier: Programming Language :: Python :: 3.12
107
+ Classifier: Topic :: Security
108
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
109
+ Requires-Python: >=3.10
110
+ Requires-Dist: agentguard-spend>=0.1.4
111
+ Requires-Dist: pyyaml>=6.0
112
+ Description-Content-Type: text/markdown
113
+
114
+ # agentguard-spend-hermes
115
+
116
+ > One-step install of [AgentGuard™ Spend](https://pypi.org/project/agentguard-spend/) into [Hermes Agent](https://github.com/NousResearch/hermes-agent) (Nous Research).
117
+
118
+ Adds **local spend caps + Ed25519-signed audit logs** to every Anthropic and OpenAI call your Hermes agent makes. No proxy. No cloud component. Prompts and provider keys never leave your machine.
119
+
120
+ Built for Hermes's unattended-by-default workflow: cron jobs, sleep loops, multi-platform messaging bridges (Telegram, Discord, Slack, WhatsApp, Signal).
121
+
122
+ ## Install
123
+
124
+ ```bash
125
+ # In the same virtualenv as Hermes
126
+ uv pip install agentguard-spend-hermes
127
+ ```
128
+
129
+ That's it. Hermes's plugin manager auto-discovers the plugin via the `hermes_agent.plugins` entry point on next startup.
130
+
131
+ ## First-run auto-bootstrap
132
+
133
+ On first Hermes startup after install, the plugin:
134
+
135
+ 1. Generates `~/.hermes/agentguard/private.key` + `public.key` (Ed25519)
136
+ 2. Writes a default `policy.yaml` ($5/day soft cap downgrades Opus → Sonnet, $20/day hard cap blocks, $2/min burst guard)
137
+ 3. Monkey-patches the Anthropic and OpenAI client constructors so every LLM call is policy-checked + signed
138
+
139
+ You'll see in the Hermes log:
140
+
141
+ ```
142
+ agentguard-spend: wrote default policy to ~/.hermes/agentguard/policy.yaml (locale: en-US)
143
+ agentguard-spend: generated signing keypair (private=..., public=...)
144
+ agentguard-spend: active (anthropic=True, openai=early, policy=...)
145
+ ```
146
+
147
+ The policy file is localized to your system locale at first-write (`en-US`, `es-419`, or `pt-BR`).
148
+
149
+ ## When a cap fires
150
+
151
+ **Downgrade (soft cap):**
152
+
153
+ ```
154
+ [hermes] anthropic.messages.create model=claude-opus-4-7
155
+ [agentguard] downgrade: claude-opus-4-7 -> claude-sonnet-4-6 (daily soft cap)
156
+ [hermes] continuing with sonnet
157
+ ```
158
+
159
+ **Block (hard cap):**
160
+
161
+ ```
162
+ [hermes] anthropic.messages.create model=claude-opus-4-7
163
+ [agentguard] block: hard daily ceiling exceeded
164
+ [hermes] AgentGuardBlockedError raised — agent halts this turn
165
+ ```
166
+
167
+ ## Configure
168
+
169
+ Edit `~/.hermes/agentguard/policy.yaml` and restart Hermes. Example:
170
+
171
+ ```yaml
172
+ id: hermes-default-v1
173
+ name: Hermes default caps
174
+ version: 1
175
+ effectiveFrom: "2026-01-01T00:00:00Z"
176
+ mode: enforce
177
+
178
+ scope:
179
+ tenantId: hermes-personal
180
+
181
+ caps:
182
+ - amountCents: 500 # $5/day soft cap
183
+ window: per_day
184
+ action: downgrade
185
+ downgradeTo: claude-sonnet-4-6
186
+ - amountCents: 2000 # $20/day hard cap
187
+ window: per_day
188
+ action: block
189
+ - amountCents: 200 # $2/minute burst guard
190
+ window: per_minute
191
+ action: block
192
+ ```
193
+
194
+ ## Disable temporarily
195
+
196
+ ```bash
197
+ export AGENTGUARD_DISABLE=1 # plugin loads but does nothing
198
+ ```
199
+
200
+ ## Per-platform spend caps
201
+
202
+ Set `HERMES_AGENT_ID` differently in each Hermes process to scope caps:
203
+
204
+ ```bash
205
+ # Telegram bridge process
206
+ export HERMES_AGENT_ID=hermes-telegram
207
+
208
+ # Discord bridge process
209
+ export HERMES_AGENT_ID=hermes-discord
210
+
211
+ # Cron job
212
+ export HERMES_AGENT_ID=hermes-cron
213
+ ```
214
+
215
+ Then write per-agent caps in `policy.yaml`:
216
+
217
+ ```yaml
218
+ caps:
219
+ - amountCents: 1000
220
+ window: per_day
221
+ scope: {agentId: hermes-telegram}
222
+ action: block
223
+ - amountCents: 500
224
+ window: per_day
225
+ scope: {agentId: hermes-cron}
226
+ action: block
227
+ ```
228
+
229
+ ## Verify the audit log
230
+
231
+ ```python
232
+ from agentguard_spend import verify_chain
233
+ import pathlib, json
234
+
235
+ public_key = pathlib.Path("~/.hermes/agentguard/public.key").expanduser().read_bytes()
236
+ entries_path = pathlib.Path("~/.hermes/agentguard/decisions.ndjson").expanduser()
237
+ entries = [json.loads(line) for line in entries_path.read_text().splitlines()]
238
+ result = verify_chain(entries, public_key)
239
+ print("OK" if result.ok else f"Chain broken at #{result.sequence}: {result.reason}")
240
+ ```
241
+
242
+ ## What this plugin does NOT change in Hermes
243
+
244
+ - Hermes's request retry logic
245
+ - Hermes's credential refresh flow
246
+ - Hermes's interrupt / cancel pattern
247
+ - Hermes's MCP / tool-call layer
248
+ - Any of the existing skills
249
+
250
+ Strictly an LLM-call-intercept layer between Hermes and the Anthropic/OpenAI wire.
251
+
252
+ ## Localization
253
+
254
+ Block traces render in the active locale:
255
+ - 🇺🇸 `en-US` (default)
256
+ - 🇲🇽 🇦🇷 🇨🇴 🇨🇱 `es-419` (Latin American Spanish)
257
+ - 🇧🇷 `pt-BR` (Brazilian Portuguese)
258
+
259
+ Resolution chain: explicit `AGENTGUARD_LOCALE` env var → `LC_ALL` / `LC_MESSAGES` / `LANG` → system locale → en-US fallback.
260
+
261
+ ## License
262
+
263
+ BUSL-1.1. Free for production deployments processing **up to 10,000 enforcement calls per calendar month**. Commercial licensing for higher volumes or service redistribution: `invest@agentguard.run`.
264
+
265
+ ## Patents
266
+
267
+ Protected by 7 U.S. patent-pending applications (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626; 64/071,781; 64/071,789; plus DV-2026-007 in active filing). See https://agentguard.run/patents.
268
+
269
+ ## Links
270
+
271
+ - **Homepage:** https://agentguard.run
272
+ - **Underlying SDK:** [`agentguard-spend`](https://pypi.org/project/agentguard-spend/) (PyPI)
273
+ - **TypeScript counterpart:** [`@agentguard-run/spend`](https://www.npmjs.com/package/@agentguard-run/spend) (npm)
274
+ - **Hermes Agent:** https://github.com/NousResearch/hermes-agent
275
+ - **Contact:** `invest@agentguard.run`
@@ -0,0 +1,162 @@
1
+ # agentguard-spend-hermes
2
+
3
+ > One-step install of [AgentGuard™ Spend](https://pypi.org/project/agentguard-spend/) into [Hermes Agent](https://github.com/NousResearch/hermes-agent) (Nous Research).
4
+
5
+ Adds **local spend caps + Ed25519-signed audit logs** to every Anthropic and OpenAI call your Hermes agent makes. No proxy. No cloud component. Prompts and provider keys never leave your machine.
6
+
7
+ Built for Hermes's unattended-by-default workflow: cron jobs, sleep loops, multi-platform messaging bridges (Telegram, Discord, Slack, WhatsApp, Signal).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ # In the same virtualenv as Hermes
13
+ uv pip install agentguard-spend-hermes
14
+ ```
15
+
16
+ That's it. Hermes's plugin manager auto-discovers the plugin via the `hermes_agent.plugins` entry point on next startup.
17
+
18
+ ## First-run auto-bootstrap
19
+
20
+ On first Hermes startup after install, the plugin:
21
+
22
+ 1. Generates `~/.hermes/agentguard/private.key` + `public.key` (Ed25519)
23
+ 2. Writes a default `policy.yaml` ($5/day soft cap downgrades Opus → Sonnet, $20/day hard cap blocks, $2/min burst guard)
24
+ 3. Monkey-patches the Anthropic and OpenAI client constructors so every LLM call is policy-checked + signed
25
+
26
+ You'll see in the Hermes log:
27
+
28
+ ```
29
+ agentguard-spend: wrote default policy to ~/.hermes/agentguard/policy.yaml (locale: en-US)
30
+ agentguard-spend: generated signing keypair (private=..., public=...)
31
+ agentguard-spend: active (anthropic=True, openai=early, policy=...)
32
+ ```
33
+
34
+ The policy file is localized to your system locale at first-write (`en-US`, `es-419`, or `pt-BR`).
35
+
36
+ ## When a cap fires
37
+
38
+ **Downgrade (soft cap):**
39
+
40
+ ```
41
+ [hermes] anthropic.messages.create model=claude-opus-4-7
42
+ [agentguard] downgrade: claude-opus-4-7 -> claude-sonnet-4-6 (daily soft cap)
43
+ [hermes] continuing with sonnet
44
+ ```
45
+
46
+ **Block (hard cap):**
47
+
48
+ ```
49
+ [hermes] anthropic.messages.create model=claude-opus-4-7
50
+ [agentguard] block: hard daily ceiling exceeded
51
+ [hermes] AgentGuardBlockedError raised — agent halts this turn
52
+ ```
53
+
54
+ ## Configure
55
+
56
+ Edit `~/.hermes/agentguard/policy.yaml` and restart Hermes. Example:
57
+
58
+ ```yaml
59
+ id: hermes-default-v1
60
+ name: Hermes default caps
61
+ version: 1
62
+ effectiveFrom: "2026-01-01T00:00:00Z"
63
+ mode: enforce
64
+
65
+ scope:
66
+ tenantId: hermes-personal
67
+
68
+ caps:
69
+ - amountCents: 500 # $5/day soft cap
70
+ window: per_day
71
+ action: downgrade
72
+ downgradeTo: claude-sonnet-4-6
73
+ - amountCents: 2000 # $20/day hard cap
74
+ window: per_day
75
+ action: block
76
+ - amountCents: 200 # $2/minute burst guard
77
+ window: per_minute
78
+ action: block
79
+ ```
80
+
81
+ ## Disable temporarily
82
+
83
+ ```bash
84
+ export AGENTGUARD_DISABLE=1 # plugin loads but does nothing
85
+ ```
86
+
87
+ ## Per-platform spend caps
88
+
89
+ Set `HERMES_AGENT_ID` differently in each Hermes process to scope caps:
90
+
91
+ ```bash
92
+ # Telegram bridge process
93
+ export HERMES_AGENT_ID=hermes-telegram
94
+
95
+ # Discord bridge process
96
+ export HERMES_AGENT_ID=hermes-discord
97
+
98
+ # Cron job
99
+ export HERMES_AGENT_ID=hermes-cron
100
+ ```
101
+
102
+ Then write per-agent caps in `policy.yaml`:
103
+
104
+ ```yaml
105
+ caps:
106
+ - amountCents: 1000
107
+ window: per_day
108
+ scope: {agentId: hermes-telegram}
109
+ action: block
110
+ - amountCents: 500
111
+ window: per_day
112
+ scope: {agentId: hermes-cron}
113
+ action: block
114
+ ```
115
+
116
+ ## Verify the audit log
117
+
118
+ ```python
119
+ from agentguard_spend import verify_chain
120
+ import pathlib, json
121
+
122
+ public_key = pathlib.Path("~/.hermes/agentguard/public.key").expanduser().read_bytes()
123
+ entries_path = pathlib.Path("~/.hermes/agentguard/decisions.ndjson").expanduser()
124
+ entries = [json.loads(line) for line in entries_path.read_text().splitlines()]
125
+ result = verify_chain(entries, public_key)
126
+ print("OK" if result.ok else f"Chain broken at #{result.sequence}: {result.reason}")
127
+ ```
128
+
129
+ ## What this plugin does NOT change in Hermes
130
+
131
+ - Hermes's request retry logic
132
+ - Hermes's credential refresh flow
133
+ - Hermes's interrupt / cancel pattern
134
+ - Hermes's MCP / tool-call layer
135
+ - Any of the existing skills
136
+
137
+ Strictly an LLM-call-intercept layer between Hermes and the Anthropic/OpenAI wire.
138
+
139
+ ## Localization
140
+
141
+ Block traces render in the active locale:
142
+ - 🇺🇸 `en-US` (default)
143
+ - 🇲🇽 🇦🇷 🇨🇴 🇨🇱 `es-419` (Latin American Spanish)
144
+ - 🇧🇷 `pt-BR` (Brazilian Portuguese)
145
+
146
+ Resolution chain: explicit `AGENTGUARD_LOCALE` env var → `LC_ALL` / `LC_MESSAGES` / `LANG` → system locale → en-US fallback.
147
+
148
+ ## License
149
+
150
+ BUSL-1.1. Free for production deployments processing **up to 10,000 enforcement calls per calendar month**. Commercial licensing for higher volumes or service redistribution: `invest@agentguard.run`.
151
+
152
+ ## Patents
153
+
154
+ Protected by 7 U.S. patent-pending applications (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626; 64/071,781; 64/071,789; plus DV-2026-007 in active filing). See https://agentguard.run/patents.
155
+
156
+ ## Links
157
+
158
+ - **Homepage:** https://agentguard.run
159
+ - **Underlying SDK:** [`agentguard-spend`](https://pypi.org/project/agentguard-spend/) (PyPI)
160
+ - **TypeScript counterpart:** [`@agentguard-run/spend`](https://www.npmjs.com/package/@agentguard-run/spend) (npm)
161
+ - **Hermes Agent:** https://github.com/NousResearch/hermes-agent
162
+ - **Contact:** `invest@agentguard.run`
@@ -0,0 +1,349 @@
1
+ """
2
+ AgentGuard Spend — Hermes plugin.
3
+
4
+ Adds local spend cap enforcement + Ed25519-signed audit logs to every
5
+ Anthropic / OpenAI call this Hermes agent makes.
6
+
7
+ Activation model
8
+ ----------------
9
+ On register() we monkey-patch the two LLM client construction sites Hermes
10
+ uses:
11
+
12
+ 1. agent.anthropic_adapter.build_anthropic_client (Anthropic chokepoint)
13
+ 2. run_agent.AIAgent._create_request_openai_client (OpenAI chokepoint)
14
+
15
+ Both patched functions wrap the freshly-constructed client with AgentGuard's
16
+ spend guard before returning it. Hermes's normal call sites (messages.create,
17
+ chat.completions.create) are untouched.
18
+
19
+ No-op when AGENTGUARD_POLICY_PATH is not set, so the plugin is safe to ship
20
+ by default.
21
+
22
+ Auto-bootstrap on first use
23
+ ---------------------------
24
+ If the user installs the plugin but hasn't configured a policy:
25
+ - Generate ~/.hermes/agentguard/private.key + public.key (one-time)
26
+ - Write ~/.hermes/agentguard/policy.yaml with conservative defaults
27
+ ($5/day soft cap downgrades Opus to Sonnet, $20/day hard cap blocks)
28
+ - Log where everything was written, then continue running
29
+
30
+ That way the user goes from `hermes plugins install agentguard-spend` to
31
+ working spend caps in one step with sensible defaults.
32
+ """
33
+ from __future__ import annotations
34
+
35
+ import logging
36
+ import os
37
+ import pathlib
38
+ from typing import Any
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+ _DEFAULT_HERMES_HOME = "~/.hermes"
43
+ _AG_DIR_NAME = "agentguard"
44
+ _POLICY_FILENAME = "policy.yaml"
45
+ _PRIVATE_KEY_FILENAME = "private.key"
46
+ _PUBLIC_KEY_FILENAME = "public.key"
47
+
48
+ _POLICY_YAML_TEMPLATE = """\
49
+ # AgentGuard Spend default policy (auto-generated on first install).
50
+ # {edit_hint}
51
+ id: hermes-default-v1
52
+ name: {policy_name}
53
+ version: 1
54
+ effectiveFrom: "2026-01-01T00:00:00Z"
55
+ mode: enforce
56
+
57
+ scope:
58
+ tenantId: hermes-personal
59
+
60
+ caps:
61
+ - amountCents: 500
62
+ window: per_day
63
+ action: downgrade
64
+ downgradeTo: claude-sonnet-4-6
65
+ reason: "{soft_cap_reason}"
66
+
67
+ - amountCents: 2000
68
+ window: per_day
69
+ action: block
70
+ reason: "{hard_cap_reason}"
71
+
72
+ - amountCents: 200
73
+ window: per_minute
74
+ action: block
75
+ reason: "{burst_reason}"
76
+ """
77
+
78
+
79
+ _POLICY_LOCALE_STRINGS = {
80
+ "en-US": {
81
+ "edit_hint": "Edit this file to change your caps. All amounts are integer cents.",
82
+ "policy_name": "Hermes default caps",
83
+ "soft_cap_reason": "Soft daily cap ($5/day) — downgrading Opus to Sonnet",
84
+ "hard_cap_reason": "Hard daily ceiling ($20/day)",
85
+ "burst_reason": "Per-minute burst guard ($2/min)",
86
+ },
87
+ "es-419": {
88
+ "edit_hint": "Edite este archivo para cambiar sus límites. Todos los montos son enteros en centavos.",
89
+ "policy_name": "Límites predeterminados de Hermes",
90
+ "soft_cap_reason": "Límite diario blando ($5/día) — degradando Opus a Sonnet",
91
+ "hard_cap_reason": "Tope diario duro ($20/día)",
92
+ "burst_reason": "Protección de ráfaga por minuto ($2/min)",
93
+ },
94
+ "pt-BR": {
95
+ "edit_hint": "Edite este arquivo para alterar seus limites. Todos os valores são números inteiros em centavos.",
96
+ "policy_name": "Limites padrão do Hermes",
97
+ "soft_cap_reason": "Limite diário leve ($5/dia) — rebaixando Opus para Sonnet",
98
+ "hard_cap_reason": "Teto diário rígido ($20/dia)",
99
+ "burst_reason": "Proteção de pico por minuto ($2/min)",
100
+ },
101
+ }
102
+
103
+
104
+ def _resolve_install_locale() -> str:
105
+ """Resolve locale for the auto-bootstrap policy at install time.
106
+
107
+ Uses the same priority chain as the agentguard-spend SDK runtime,
108
+ so the bootstrapped policy reasons match the locale the runtime
109
+ will later render block traces in.
110
+ """
111
+ try:
112
+ from agentguard_spend import resolve_locale
113
+ return resolve_locale()
114
+ except ImportError:
115
+ return "en-US"
116
+
117
+
118
+ def _build_default_policy_yaml() -> str:
119
+ locale = _resolve_install_locale()
120
+ strings = _POLICY_LOCALE_STRINGS.get(locale, _POLICY_LOCALE_STRINGS["en-US"])
121
+ return _POLICY_YAML_TEMPLATE.format(**strings)
122
+
123
+
124
+ def _ag_dir() -> pathlib.Path:
125
+ home = os.environ.get("HERMES_HOME", os.path.expanduser(_DEFAULT_HERMES_HOME))
126
+ return pathlib.Path(home).expanduser() / _AG_DIR_NAME
127
+
128
+
129
+ def _bootstrap_if_needed() -> dict[str, pathlib.Path] | None:
130
+ """Create policy + keys on first run if absent. Returns paths dict or None on failure."""
131
+ d = _ag_dir()
132
+ try:
133
+ d.mkdir(parents=True, exist_ok=True)
134
+ except OSError as exc:
135
+ logger.warning("agentguard-spend: cannot create %s (%s); plugin disabled", d, exc)
136
+ return None
137
+
138
+ policy_path = d / _POLICY_FILENAME
139
+ private_key_path = d / _PRIVATE_KEY_FILENAME
140
+ public_key_path = d / _PUBLIC_KEY_FILENAME
141
+
142
+ if not policy_path.exists():
143
+ policy_path.write_text(_build_default_policy_yaml())
144
+ logger.info(
145
+ "agentguard-spend: wrote default policy to %s (locale: %s)",
146
+ policy_path,
147
+ _resolve_install_locale(),
148
+ )
149
+
150
+ if not private_key_path.exists() or not public_key_path.exists():
151
+ try:
152
+ from agentguard_spend import generate_keypair
153
+ except ImportError:
154
+ logger.warning(
155
+ "agentguard-spend: package not installed — run `uv pip install agentguard-spend` "
156
+ "in the Hermes virtualenv to enable. Plugin disabled until then."
157
+ )
158
+ return None
159
+ priv, pub = generate_keypair()
160
+ private_key_path.write_bytes(priv)
161
+ public_key_path.write_bytes(pub)
162
+ logger.info(
163
+ "agentguard-spend: generated signing keypair (private=%s, public=%s)",
164
+ private_key_path,
165
+ public_key_path,
166
+ )
167
+
168
+ return {
169
+ "policy": policy_path,
170
+ "private_key": private_key_path,
171
+ "public_key": public_key_path,
172
+ }
173
+
174
+
175
+ def _load_policy(policy_path: pathlib.Path) -> Any | None:
176
+ try:
177
+ import yaml
178
+ from agentguard_spend import SpendPolicy, SpendScope, SpendCap
179
+ except ImportError:
180
+ return None
181
+
182
+ raw = yaml.safe_load(policy_path.read_text())
183
+ return SpendPolicy(
184
+ id=raw["id"],
185
+ name=raw.get("name", raw["id"]),
186
+ scope=SpendScope(**raw.get("scope", {})),
187
+ caps=[SpendCap(**c) for c in raw.get("caps", [])],
188
+ mode=raw.get("mode", "enforce"),
189
+ version=raw.get("version", 1),
190
+ effectiveFrom=raw["effectiveFrom"],
191
+ requiredCapability=raw.get("requiredCapability"),
192
+ )
193
+
194
+
195
+ def _build_config(policy: Any, private_key: bytes, public_key: bytes) -> Any:
196
+ from agentguard_spend import (
197
+ SpendGuardConfig,
198
+ SigningKeys,
199
+ InMemorySpendStore,
200
+ InMemoryDecisionLogStore,
201
+ )
202
+
203
+ return SpendGuardConfig(
204
+ policy=policy,
205
+ spendStore=InMemorySpendStore(),
206
+ logStore=InMemoryDecisionLogStore(),
207
+ signingKeys=SigningKeys(privateKey=private_key, publicKey=public_key),
208
+ )
209
+
210
+
211
+ def _runtime_scope() -> Any:
212
+ from agentguard_spend import SpendScope
213
+ return SpendScope(
214
+ tenantId=os.environ.get("HERMES_TENANT", "hermes-personal"),
215
+ agentId=os.environ.get("HERMES_AGENT_ID", "hermes-agent"),
216
+ userId=os.environ.get("HERMES_USER_ID") or None,
217
+ )
218
+
219
+
220
+ def _install_anthropic_patch(policy: Any, private_key: bytes, public_key: bytes) -> bool:
221
+ """Patch agent.anthropic_adapter.build_anthropic_client to wrap on construction."""
222
+ try:
223
+ from agent import anthropic_adapter
224
+ from agentguard_spend.bindings import with_anthropic_spend_guard
225
+ except ImportError as exc:
226
+ logger.warning("agentguard-spend: anthropic patch unavailable (%s)", exc)
227
+ return False
228
+
229
+ original = anthropic_adapter.build_anthropic_client
230
+
231
+ def patched(api_key: str, base_url: str = None):
232
+ client = original(api_key, base_url)
233
+ if client is None:
234
+ return client
235
+ try:
236
+ return with_anthropic_spend_guard(
237
+ client,
238
+ policy=policy,
239
+ scope=_runtime_scope(),
240
+ config=_build_config(policy, private_key, public_key),
241
+ )
242
+ except Exception as exc:
243
+ logger.warning(
244
+ "agentguard-spend: anthropic wrap failed (%s) — returning raw client",
245
+ exc,
246
+ )
247
+ return client
248
+
249
+ anthropic_adapter.build_anthropic_client = patched
250
+ return True
251
+
252
+
253
+ _openai_patch_installed = False
254
+
255
+
256
+ def _install_openai_patch(policy: Any, private_key: bytes, public_key: bytes) -> bool:
257
+ """Patch AIAgent._create_request_openai_client to wrap on construction.
258
+
259
+ Called both from register() (early-load attempt) and from on_session_start
260
+ (deferred fallback). Early-load may fail with AIAgent=None because
261
+ run_agent.py is mid-import when register() runs; the deferred call
262
+ succeeds because by then run_agent has fully loaded.
263
+ """
264
+ global _openai_patch_installed
265
+ if _openai_patch_installed:
266
+ return True
267
+
268
+ try:
269
+ import sys
270
+ run_agent = sys.modules.get("run_agent")
271
+ if run_agent is None:
272
+ import run_agent # noqa: F811
273
+ from agentguard_spend.bindings import with_openai_spend_guard
274
+ except ImportError as exc:
275
+ logger.warning("agentguard-spend: openai patch unavailable (%s)", exc)
276
+ return False
277
+
278
+ AIAgent = getattr(run_agent, "AIAgent", None)
279
+ if AIAgent is None:
280
+ # Silent during register() because run_agent is mid-import; the
281
+ # deferred on_session_start call will install successfully.
282
+ return False
283
+
284
+ original = AIAgent._create_request_openai_client
285
+
286
+ def patched(self, *, reason: str):
287
+ client = original(self, reason=reason)
288
+ if client is None:
289
+ return client
290
+ try:
291
+ return with_openai_spend_guard(
292
+ client,
293
+ policy=policy,
294
+ scope=_runtime_scope(),
295
+ config=_build_config(policy, private_key, public_key),
296
+ )
297
+ except Exception as exc:
298
+ logger.warning(
299
+ "agentguard-spend: openai wrap failed (%s) — returning raw client",
300
+ exc,
301
+ )
302
+ return client
303
+
304
+ AIAgent._create_request_openai_client = patched
305
+ _openai_patch_installed = True
306
+ return True
307
+
308
+
309
+ def _log_completed_call(session_id: str = "", **kwargs) -> None:
310
+ """Optional post-llm telemetry hook. Currently a noop — reserved for v0.2."""
311
+ return None
312
+
313
+
314
+ def register(ctx) -> None:
315
+ """Called by Hermes plugin manager when the plugin is loaded."""
316
+ if os.environ.get("AGENTGUARD_DISABLE", "").lower() in {"1", "true", "yes"}:
317
+ logger.info("agentguard-spend: disabled via AGENTGUARD_DISABLE env var")
318
+ return
319
+
320
+ paths = _bootstrap_if_needed()
321
+ if paths is None:
322
+ return
323
+
324
+ policy = _load_policy(paths["policy"])
325
+ if policy is None:
326
+ logger.warning("agentguard-spend: policy load failed; plugin disabled")
327
+ return
328
+
329
+ private_key = paths["private_key"].read_bytes()
330
+ public_key = paths["public_key"].read_bytes()
331
+
332
+ anthropic_ok = _install_anthropic_patch(policy, private_key, public_key)
333
+
334
+ # Try the openai patch now; if AIAgent isn't yet defined (partial import),
335
+ # the on_session_start hook will retry once run_agent has fully loaded.
336
+ openai_ok_early = _install_openai_patch(policy, private_key, public_key)
337
+
338
+ def _retry_openai(**_):
339
+ _install_openai_patch(policy, private_key, public_key)
340
+
341
+ ctx.register_hook("on_session_start", _retry_openai)
342
+ ctx.register_hook("post_llm_call", _log_completed_call)
343
+
344
+ logger.info(
345
+ "agentguard-spend: active (anthropic=%s, openai=%s, policy=%s)",
346
+ anthropic_ok,
347
+ "early" if openai_ok_early else "deferred-to-session-start",
348
+ paths["policy"],
349
+ )
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.18"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "agentguard-spend-hermes"
7
+ version = "0.1.4"
8
+ description = "AgentGuard Spend plugin for Hermes Agent (Nous Research). One-step install of local spend caps + signed audit logs for every Anthropic/OpenAI call your Hermes agent makes. No proxy, prompts never leave your machine."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { file = "LICENSE" }
12
+ authors = [{ name = "Dunecrest Ventures Inc.", email = "hello@agentguard.run" }]
13
+ keywords = [
14
+ "hermes",
15
+ "hermes-agent",
16
+ "nous-research",
17
+ "agentguard",
18
+ "ai-agent-security",
19
+ "spend-control",
20
+ "llm-governance",
21
+ "agent-plugin",
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 4 - Beta",
25
+ "Intended Audience :: Developers",
26
+ "License :: Other/Proprietary License",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.10",
29
+ "Programming Language :: Python :: 3.11",
30
+ "Programming Language :: Python :: 3.12",
31
+ "Topic :: Security",
32
+ "Topic :: Software Development :: Libraries :: Python Modules",
33
+ ]
34
+ dependencies = [
35
+ "agentguard-spend>=0.1.4",
36
+ "pyyaml>=6.0",
37
+ ]
38
+
39
+ [project.urls]
40
+ Homepage = "https://agentguard.run"
41
+ "AgentGuard SDK (PyPI)" = "https://pypi.org/project/agentguard-spend/"
42
+ "AgentGuard SDK (npm)" = "https://www.npmjs.com/package/@agentguard-run/spend"
43
+ "Hermes Agent" = "https://github.com/NousResearch/hermes-agent"
44
+ "Source" = "https://github.com/MerchantGuard/merchantguard-website/tree/main/packages/agentguard-spend-hermes"
45
+
46
+ # Hermes plugin auto-discovery entry point.
47
+ # When agentguard-spend-hermes is pip-installed in the same Python
48
+ # environment as Hermes Agent, Hermes's plugin manager finds the
49
+ # `register` callable via this entry point and loads the plugin
50
+ # automatically on next startup.
51
+ [project.entry-points."hermes_agent.plugins"]
52
+ agentguard-spend = "agentguard_spend_hermes:register"
53
+
54
+ [tool.hatch.build.targets.wheel]
55
+ packages = ["agentguard_spend_hermes"]