moss-hermes 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,33 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+ *.egg
11
+
12
+ # Virtual environments
13
+ venv/
14
+ .venv/
15
+ env/
16
+
17
+ # IDE
18
+ .idea/
19
+ .vscode/
20
+ *.swp
21
+ *.swo
22
+
23
+ # Testing
24
+ .pytest_cache/
25
+ .coverage
26
+ htmlcov/
27
+
28
+ # Type checking
29
+ .mypy_cache/
30
+
31
+ # OS
32
+ .DS_Store
33
+ Thumbs.db
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MOSS Computing
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,93 @@
1
+ Metadata-Version: 2.4
2
+ Name: moss-hermes
3
+ Version: 0.1.0
4
+ Summary: MOSS integration for Nous Research Hermes agents - cryptographic signing for AI actions
5
+ Project-URL: Homepage, https://mosscomputing.com
6
+ Project-URL: Documentation, https://docs.mosscomputing.com/hermes
7
+ Project-URL: Repository, https://github.com/mosscomputing/moss-hermes
8
+ Project-URL: Changelog, https://github.com/mosscomputing/moss-hermes/blob/main/CHANGELOG.md
9
+ Author-email: MOSS Computing <support@mosscomputing.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai-agents,ai-governance,cryptographic-signing,hermes,llm,ml-dsa-44,moss,nous-research,post-quantum
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
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 :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: Security :: Cryptography
23
+ Requires-Python: >=3.9
24
+ Requires-Dist: moss-sdk>=0.2.0
25
+ Requires-Dist: requests>=2.28.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: black>=23.0.0; extra == 'dev'
28
+ Requires-Dist: mypy>=1.0.0; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
30
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
31
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # moss-hermes
35
+
36
+ MOSS integration for [Nous Research Hermes](https://nousresearch.com) agents.
37
+
38
+ ## Overview
39
+
40
+ Cryptographic signing for Hermes AI agent actions using ML-DSA-44 (NIST FIPS 204) post-quantum signatures.
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install moss-hermes
46
+ ```
47
+
48
+ ## Quick Start
49
+
50
+ ```python
51
+ from moss_hermes import sign_tool_call
52
+
53
+ signed = sign_tool_call(
54
+ tool_name="send_email",
55
+ tool_input={"to": "user@example.com", "body": "Hello"},
56
+ tool_output={"status": "sent"},
57
+ agent_id="hermes-assistant"
58
+ )
59
+ ```
60
+
61
+ ## What Gets Signed
62
+
63
+ | Function | Use Case |
64
+ |----------|----------|
65
+ | `sign_tool_call` | Tool executions |
66
+ | `sign_function_call` | OpenAI-compatible function calls |
67
+ | `sign_reasoning_chain` | Chain-of-thought reasoning |
68
+ | `sign_memory_retrieval` | Memory/RAG retrieval events |
69
+
70
+ ## Configuration
71
+
72
+ | Variable | Description | Default |
73
+ |----------|-------------|---------|
74
+ | `MOSS_API_KEY` | Enterprise API key | None (local mode) |
75
+ | `MOSS_API_URL` | API endpoint | `https://api.mosscomputing.com` |
76
+
77
+ ## Features
78
+
79
+ - **ML-DSA-44 Signatures**: Post-quantum cryptographic signing
80
+ - **Causal Linking**: Link actions via `parent_sig` parameter
81
+ - **Kill-Switch**: Monitor for agent revocation
82
+ - **Async Support**: All functions have async versions
83
+
84
+ ## Links
85
+
86
+ - [Documentation](https://docs.mosscomputing.com/sdks/hermes)
87
+ - [Nous Research](https://nousresearch.com)
88
+ - [Hermes Models](https://huggingface.co/NousResearch)
89
+ - [PyPI](https://pypi.org/project/moss-hermes/)
90
+
91
+ ## License
92
+
93
+ MIT - See [LICENSE](LICENSE) for details.
@@ -0,0 +1,60 @@
1
+ # moss-hermes
2
+
3
+ MOSS integration for [Nous Research Hermes](https://nousresearch.com) agents.
4
+
5
+ ## Overview
6
+
7
+ Cryptographic signing for Hermes AI agent actions using ML-DSA-44 (NIST FIPS 204) post-quantum signatures.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install moss-hermes
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```python
18
+ from moss_hermes import sign_tool_call
19
+
20
+ signed = sign_tool_call(
21
+ tool_name="send_email",
22
+ tool_input={"to": "user@example.com", "body": "Hello"},
23
+ tool_output={"status": "sent"},
24
+ agent_id="hermes-assistant"
25
+ )
26
+ ```
27
+
28
+ ## What Gets Signed
29
+
30
+ | Function | Use Case |
31
+ |----------|----------|
32
+ | `sign_tool_call` | Tool executions |
33
+ | `sign_function_call` | OpenAI-compatible function calls |
34
+ | `sign_reasoning_chain` | Chain-of-thought reasoning |
35
+ | `sign_memory_retrieval` | Memory/RAG retrieval events |
36
+
37
+ ## Configuration
38
+
39
+ | Variable | Description | Default |
40
+ |----------|-------------|---------|
41
+ | `MOSS_API_KEY` | Enterprise API key | None (local mode) |
42
+ | `MOSS_API_URL` | API endpoint | `https://api.mosscomputing.com` |
43
+
44
+ ## Features
45
+
46
+ - **ML-DSA-44 Signatures**: Post-quantum cryptographic signing
47
+ - **Causal Linking**: Link actions via `parent_sig` parameter
48
+ - **Kill-Switch**: Monitor for agent revocation
49
+ - **Async Support**: All functions have async versions
50
+
51
+ ## Links
52
+
53
+ - [Documentation](https://docs.mosscomputing.com/sdks/hermes)
54
+ - [Nous Research](https://nousresearch.com)
55
+ - [Hermes Models](https://huggingface.co/NousResearch)
56
+ - [PyPI](https://pypi.org/project/moss-hermes/)
57
+
58
+ ## License
59
+
60
+ MIT - See [LICENSE](LICENSE) for details.
@@ -0,0 +1,81 @@
1
+ """
2
+ MOSS Hermes Integration - Cryptographic Signing for Hermes Agent Outputs
3
+
4
+ Sign tool calls, reasoning chains, and agent actions from Nous Research's Hermes models.
5
+
6
+ Quick Start:
7
+ from moss_hermes import sign_tool_call, sign_reasoning_chain
8
+
9
+ # After Hermes executes a tool
10
+ signed = sign_tool_call(
11
+ tool_name="send_email",
12
+ tool_input={"to": "user@example.com", "body": "Hello"},
13
+ tool_output={"status": "sent", "message_id": "msg_123"},
14
+ agent_id="hermes-assistant"
15
+ )
16
+ print(f"Signature: {signed.signature}")
17
+
18
+ Enterprise Mode:
19
+ Set MOSS_API_KEY environment variable to enable:
20
+ - Policy evaluation (allow/block/hold)
21
+ - Evidence retention
22
+ - Kill-switch monitoring
23
+ - Registry certification
24
+ """
25
+
26
+ __version__ = "0.1.0"
27
+
28
+ from .signing import (
29
+ sign_tool_call,
30
+ sign_tool_call_async,
31
+ sign_reasoning_chain,
32
+ sign_reasoning_chain_async,
33
+ sign_memory_retrieval,
34
+ sign_memory_retrieval_async,
35
+ sign_agent_action,
36
+ sign_agent_action_async,
37
+ sign_function_call,
38
+ sign_function_call_async,
39
+ verify_envelope,
40
+ )
41
+
42
+ from .wrapper import (
43
+ MossHermesWrapper,
44
+ moss_signed,
45
+ )
46
+
47
+ from .kill_switch import (
48
+ KillSwitchMonitor,
49
+ check_kill_switch,
50
+ )
51
+
52
+ from moss import SignResult, VerifyResult, Envelope, enterprise_enabled
53
+
54
+ __all__ = [
55
+ # Explicit signing functions
56
+ "sign_tool_call",
57
+ "sign_tool_call_async",
58
+ "sign_reasoning_chain",
59
+ "sign_reasoning_chain_async",
60
+ "sign_memory_retrieval",
61
+ "sign_memory_retrieval_async",
62
+ "sign_agent_action",
63
+ "sign_agent_action_async",
64
+ "sign_function_call",
65
+ "sign_function_call_async",
66
+ "verify_envelope",
67
+
68
+ # Wrapper for automatic signing
69
+ "MossHermesWrapper",
70
+ "moss_signed",
71
+
72
+ # Kill-switch
73
+ "KillSwitchMonitor",
74
+ "check_kill_switch",
75
+
76
+ # Core types
77
+ "SignResult",
78
+ "VerifyResult",
79
+ "Envelope",
80
+ "enterprise_enabled",
81
+ ]
@@ -0,0 +1,178 @@
1
+ """
2
+ MOSS Hermes Kill-Switch Integration
3
+
4
+ Monitors the MOSS Kill-Switch API and terminates agent capabilities if revoked.
5
+
6
+ Example:
7
+ from moss_hermes import KillSwitchMonitor
8
+
9
+ monitor = KillSwitchMonitor(
10
+ agent_id="hermes-trading-bot",
11
+ moss_api_key=os.environ["MOSS_API_KEY"],
12
+ on_revoked=lambda: sys.exit(1)
13
+ )
14
+
15
+ # Start monitoring in background
16
+ monitor.start()
17
+
18
+ # Run your agent
19
+ agent.run()
20
+ """
21
+
22
+ import os
23
+ import time
24
+ import threading
25
+ from typing import Callable, Optional
26
+ import requests
27
+
28
+
29
+ class KillSwitchMonitor:
30
+ """
31
+ Monitor MOSS kill-switch status and respond to revocations.
32
+
33
+ When an agent is revoked via the MOSS console or API, this monitor
34
+ will detect it and call the on_revoked callback.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ agent_id: str,
40
+ *,
41
+ moss_api_key: Optional[str] = None,
42
+ moss_api_url: str = "https://api.mosscomputing.com",
43
+ check_interval_seconds: int = 10,
44
+ on_revoked: Optional[Callable[[], None]] = None,
45
+ on_error: Optional[Callable[[Exception], None]] = None,
46
+ ):
47
+ """
48
+ Initialize the kill-switch monitor.
49
+
50
+ Args:
51
+ agent_id: The agent ID to monitor
52
+ moss_api_key: MOSS API key (or set MOSS_API_KEY env var)
53
+ moss_api_url: MOSS API base URL
54
+ check_interval_seconds: How often to check status (default 10s)
55
+ on_revoked: Callback when agent is revoked
56
+ on_error: Callback on API errors
57
+ """
58
+ self.agent_id = agent_id
59
+ self.moss_api_key = moss_api_key or os.environ.get("MOSS_API_KEY")
60
+ self.moss_api_url = moss_api_url.rstrip("/")
61
+ self.check_interval = check_interval_seconds
62
+ self.on_revoked = on_revoked or self._default_on_revoked
63
+ self.on_error = on_error
64
+
65
+ self._running = False
66
+ self._thread: Optional[threading.Thread] = None
67
+ self._revoked = False
68
+
69
+ @property
70
+ def is_revoked(self) -> bool:
71
+ """Check if agent has been revoked."""
72
+ return self._revoked
73
+
74
+ @property
75
+ def is_running(self) -> bool:
76
+ """Check if monitor is running."""
77
+ return self._running
78
+
79
+ def start(self):
80
+ """Start the kill-switch monitor in a background thread."""
81
+ if self._running:
82
+ return
83
+
84
+ self._running = True
85
+ self._thread = threading.Thread(target=self._monitor_loop, daemon=True)
86
+ self._thread.start()
87
+
88
+ def stop(self):
89
+ """Stop the kill-switch monitor."""
90
+ self._running = False
91
+ if self._thread:
92
+ self._thread.join(timeout=5)
93
+
94
+ def check_status(self) -> bool:
95
+ """
96
+ Check if agent is still active (not revoked).
97
+
98
+ Returns:
99
+ True if active, False if revoked
100
+ """
101
+ if not self.moss_api_key:
102
+ return True # No API key = assume active
103
+
104
+ try:
105
+ response = requests.get(
106
+ f"{self.moss_api_url}/v1/agents/{self.agent_id}",
107
+ headers={"Authorization": f"Bearer {self.moss_api_key}"},
108
+ timeout=5,
109
+ )
110
+
111
+ if response.status_code == 404:
112
+ # Agent not found
113
+ return False
114
+
115
+ if response.status_code == 200:
116
+ data = response.json()
117
+ status = data.get("status", "active")
118
+ return status == "active"
119
+
120
+ return True # On error, assume active
121
+
122
+ except Exception as e:
123
+ if self.on_error:
124
+ self.on_error(e)
125
+ return True # On error, assume active
126
+
127
+ def _monitor_loop(self):
128
+ """Background monitoring loop."""
129
+ while self._running:
130
+ if not self.check_status():
131
+ self._revoked = True
132
+ self._running = False
133
+ self.on_revoked()
134
+ return
135
+
136
+ time.sleep(self.check_interval)
137
+
138
+ @staticmethod
139
+ def _default_on_revoked():
140
+ """Default revocation handler - raises exception."""
141
+ raise AgentRevokedException("Agent has been revoked via MOSS kill-switch")
142
+
143
+
144
+ class AgentRevokedException(Exception):
145
+ """Raised when an agent is revoked via MOSS kill-switch."""
146
+ pass
147
+
148
+
149
+ def check_kill_switch(
150
+ agent_id: str,
151
+ *,
152
+ moss_api_key: Optional[str] = None,
153
+ moss_api_url: str = "https://api.mosscomputing.com",
154
+ ) -> bool:
155
+ """
156
+ One-time check if agent is revoked.
157
+
158
+ Args:
159
+ agent_id: The agent ID to check
160
+ moss_api_key: MOSS API key
161
+ moss_api_url: MOSS API base URL
162
+
163
+ Returns:
164
+ True if agent is active, False if revoked
165
+
166
+ Example:
167
+ from moss_hermes import check_kill_switch
168
+
169
+ if not check_kill_switch("my-agent"):
170
+ print("Agent has been revoked!")
171
+ sys.exit(1)
172
+ """
173
+ monitor = KillSwitchMonitor(
174
+ agent_id=agent_id,
175
+ moss_api_key=moss_api_key,
176
+ moss_api_url=moss_api_url,
177
+ )
178
+ return monitor.check_status()
@@ -0,0 +1,439 @@
1
+ """
2
+ MOSS Hermes Integration - Signing Functions
3
+
4
+ Provides explicit signing functions for Hermes agent outputs.
5
+ Compatible with Nous Research Hermes models (Hermes-2, Hermes-3, etc.)
6
+
7
+ Example:
8
+ from moss_hermes import sign_tool_call
9
+
10
+ # After Hermes executes a tool
11
+ signed = sign_tool_call(
12
+ tool_name="execute_code",
13
+ tool_input={"code": "print('hello')"},
14
+ tool_output={"result": "hello", "exit_code": 0},
15
+ agent_id="hermes-coder"
16
+ )
17
+ """
18
+
19
+ from typing import Any, Dict, List, Optional
20
+ from datetime import datetime, timezone
21
+
22
+ from moss import sign, sign_async, verify, SignResult, VerifyResult
23
+
24
+
25
+ def sign_tool_call(
26
+ tool_name: str,
27
+ tool_input: Dict[str, Any],
28
+ tool_output: Any,
29
+ agent_id: str,
30
+ *,
31
+ parent_sig: Optional[str] = None,
32
+ context: Optional[Dict[str, Any]] = None,
33
+ ) -> SignResult:
34
+ """
35
+ Sign a Hermes tool call execution.
36
+
37
+ Args:
38
+ tool_name: Name of the tool executed (e.g., "send_email", "execute_code")
39
+ tool_input: Parameters passed to the tool
40
+ tool_output: Result returned by the tool
41
+ agent_id: Identifier for the Hermes agent
42
+ parent_sig: Optional parent signature for causal linking
43
+ context: Optional context (user_id, session_id, etc.)
44
+
45
+ Returns:
46
+ SignResult with envelope and enterprise info
47
+
48
+ Example:
49
+ signed = sign_tool_call(
50
+ tool_name="web_search",
51
+ tool_input={"query": "latest AI news"},
52
+ tool_output={"results": [...]},
53
+ agent_id="hermes-researcher"
54
+ )
55
+ """
56
+ payload = {
57
+ "type": "hermes_tool_call",
58
+ "tool_name": tool_name,
59
+ "tool_input": tool_input,
60
+ "tool_output": _serialize_output(tool_output),
61
+ "timestamp": datetime.now(timezone.utc).isoformat(),
62
+ }
63
+
64
+ if parent_sig:
65
+ payload["parent_sig"] = parent_sig
66
+
67
+ return sign(
68
+ output=payload,
69
+ agent_id=agent_id,
70
+ action=f"hermes_tool:{tool_name}",
71
+ context=context,
72
+ )
73
+
74
+
75
+ async def sign_tool_call_async(
76
+ tool_name: str,
77
+ tool_input: Dict[str, Any],
78
+ tool_output: Any,
79
+ agent_id: str,
80
+ *,
81
+ parent_sig: Optional[str] = None,
82
+ context: Optional[Dict[str, Any]] = None,
83
+ ) -> SignResult:
84
+ """Async version of sign_tool_call."""
85
+ payload = {
86
+ "type": "hermes_tool_call",
87
+ "tool_name": tool_name,
88
+ "tool_input": tool_input,
89
+ "tool_output": _serialize_output(tool_output),
90
+ "timestamp": datetime.now(timezone.utc).isoformat(),
91
+ }
92
+
93
+ if parent_sig:
94
+ payload["parent_sig"] = parent_sig
95
+
96
+ return await sign_async(
97
+ output=payload,
98
+ agent_id=agent_id,
99
+ action=f"hermes_tool:{tool_name}",
100
+ context=context,
101
+ )
102
+
103
+
104
+ def sign_reasoning_chain(
105
+ reasoning_steps: List[Dict[str, Any]],
106
+ final_conclusion: str,
107
+ agent_id: str,
108
+ *,
109
+ parent_sig: Optional[str] = None,
110
+ context: Optional[Dict[str, Any]] = None,
111
+ ) -> SignResult:
112
+ """
113
+ Sign a Hermes reasoning chain (chain-of-thought).
114
+
115
+ Args:
116
+ reasoning_steps: List of reasoning steps with 'thought' and 'action'
117
+ final_conclusion: The final conclusion or answer
118
+ agent_id: Identifier for the Hermes agent
119
+ parent_sig: Optional parent signature for causal linking
120
+ context: Optional context
121
+
122
+ Returns:
123
+ SignResult with envelope and enterprise info
124
+
125
+ Example:
126
+ signed = sign_reasoning_chain(
127
+ reasoning_steps=[
128
+ {"thought": "I need to search for...", "action": "web_search"},
129
+ {"thought": "Based on results...", "action": "summarize"}
130
+ ],
131
+ final_conclusion="The answer is...",
132
+ agent_id="hermes-analyst"
133
+ )
134
+ """
135
+ payload = {
136
+ "type": "hermes_reasoning_chain",
137
+ "steps": reasoning_steps,
138
+ "step_count": len(reasoning_steps),
139
+ "conclusion": final_conclusion,
140
+ "timestamp": datetime.now(timezone.utc).isoformat(),
141
+ }
142
+
143
+ if parent_sig:
144
+ payload["parent_sig"] = parent_sig
145
+
146
+ return sign(
147
+ output=payload,
148
+ agent_id=agent_id,
149
+ action="hermes_reasoning",
150
+ context=context,
151
+ )
152
+
153
+
154
+ async def sign_reasoning_chain_async(
155
+ reasoning_steps: List[Dict[str, Any]],
156
+ final_conclusion: str,
157
+ agent_id: str,
158
+ *,
159
+ parent_sig: Optional[str] = None,
160
+ context: Optional[Dict[str, Any]] = None,
161
+ ) -> SignResult:
162
+ """Async version of sign_reasoning_chain."""
163
+ payload = {
164
+ "type": "hermes_reasoning_chain",
165
+ "steps": reasoning_steps,
166
+ "step_count": len(reasoning_steps),
167
+ "conclusion": final_conclusion,
168
+ "timestamp": datetime.now(timezone.utc).isoformat(),
169
+ }
170
+
171
+ if parent_sig:
172
+ payload["parent_sig"] = parent_sig
173
+
174
+ return await sign_async(
175
+ output=payload,
176
+ agent_id=agent_id,
177
+ action="hermes_reasoning",
178
+ context=context,
179
+ )
180
+
181
+
182
+ def sign_memory_retrieval(
183
+ query: str,
184
+ retrieved_memories: List[Dict[str, Any]],
185
+ agent_id: str,
186
+ *,
187
+ memory_type: str = "episodic",
188
+ context: Optional[Dict[str, Any]] = None,
189
+ ) -> SignResult:
190
+ """
191
+ Sign a Hermes memory retrieval event.
192
+
193
+ Args:
194
+ query: The query used for memory retrieval
195
+ retrieved_memories: List of retrieved memory items
196
+ agent_id: Identifier for the Hermes agent
197
+ memory_type: Type of memory (episodic, semantic, procedural)
198
+ context: Optional context
199
+
200
+ Returns:
201
+ SignResult with envelope and enterprise info
202
+
203
+ Example:
204
+ signed = sign_memory_retrieval(
205
+ query="previous conversations about project X",
206
+ retrieved_memories=[{"content": "...", "timestamp": "..."}],
207
+ agent_id="hermes-assistant",
208
+ memory_type="episodic"
209
+ )
210
+ """
211
+ payload = {
212
+ "type": "hermes_memory_retrieval",
213
+ "query": query,
214
+ "memory_type": memory_type,
215
+ "retrieved_count": len(retrieved_memories),
216
+ "memories": retrieved_memories,
217
+ "timestamp": datetime.now(timezone.utc).isoformat(),
218
+ }
219
+
220
+ return sign(
221
+ output=payload,
222
+ agent_id=agent_id,
223
+ action=f"hermes_memory:{memory_type}",
224
+ context=context,
225
+ )
226
+
227
+
228
+ async def sign_memory_retrieval_async(
229
+ query: str,
230
+ retrieved_memories: List[Dict[str, Any]],
231
+ agent_id: str,
232
+ *,
233
+ memory_type: str = "episodic",
234
+ context: Optional[Dict[str, Any]] = None,
235
+ ) -> SignResult:
236
+ """Async version of sign_memory_retrieval."""
237
+ payload = {
238
+ "type": "hermes_memory_retrieval",
239
+ "query": query,
240
+ "memory_type": memory_type,
241
+ "retrieved_count": len(retrieved_memories),
242
+ "memories": retrieved_memories,
243
+ "timestamp": datetime.now(timezone.utc).isoformat(),
244
+ }
245
+
246
+ return await sign_async(
247
+ output=payload,
248
+ agent_id=agent_id,
249
+ action=f"hermes_memory:{memory_type}",
250
+ context=context,
251
+ )
252
+
253
+
254
+ def sign_agent_action(
255
+ action_type: str,
256
+ action_input: Dict[str, Any],
257
+ action_output: Any,
258
+ agent_id: str,
259
+ *,
260
+ parent_sig: Optional[str] = None,
261
+ reasoning: Optional[str] = None,
262
+ context: Optional[Dict[str, Any]] = None,
263
+ ) -> SignResult:
264
+ """
265
+ Sign a generic Hermes agent action.
266
+
267
+ Args:
268
+ action_type: Type of action performed
269
+ action_input: Input parameters for the action
270
+ action_output: Output of the action
271
+ agent_id: Identifier for the Hermes agent
272
+ parent_sig: Optional parent signature for causal linking
273
+ reasoning: Optional reasoning that led to this action
274
+ context: Optional context
275
+
276
+ Returns:
277
+ SignResult with envelope and enterprise info
278
+ """
279
+ payload = {
280
+ "type": "hermes_agent_action",
281
+ "action_type": action_type,
282
+ "input": action_input,
283
+ "output": _serialize_output(action_output),
284
+ "timestamp": datetime.now(timezone.utc).isoformat(),
285
+ }
286
+
287
+ if parent_sig:
288
+ payload["parent_sig"] = parent_sig
289
+ if reasoning:
290
+ payload["reasoning"] = reasoning
291
+
292
+ return sign(
293
+ output=payload,
294
+ agent_id=agent_id,
295
+ action=f"hermes_action:{action_type}",
296
+ context=context,
297
+ )
298
+
299
+
300
+ async def sign_agent_action_async(
301
+ action_type: str,
302
+ action_input: Dict[str, Any],
303
+ action_output: Any,
304
+ agent_id: str,
305
+ *,
306
+ parent_sig: Optional[str] = None,
307
+ reasoning: Optional[str] = None,
308
+ context: Optional[Dict[str, Any]] = None,
309
+ ) -> SignResult:
310
+ """Async version of sign_agent_action."""
311
+ payload = {
312
+ "type": "hermes_agent_action",
313
+ "action_type": action_type,
314
+ "input": action_input,
315
+ "output": _serialize_output(action_output),
316
+ "timestamp": datetime.now(timezone.utc).isoformat(),
317
+ }
318
+
319
+ if parent_sig:
320
+ payload["parent_sig"] = parent_sig
321
+ if reasoning:
322
+ payload["reasoning"] = reasoning
323
+
324
+ return await sign_async(
325
+ output=payload,
326
+ agent_id=agent_id,
327
+ action=f"hermes_action:{action_type}",
328
+ context=context,
329
+ )
330
+
331
+
332
+ def sign_function_call(
333
+ function_name: str,
334
+ arguments: Dict[str, Any],
335
+ result: Any,
336
+ agent_id: str,
337
+ *,
338
+ parent_sig: Optional[str] = None,
339
+ context: Optional[Dict[str, Any]] = None,
340
+ ) -> SignResult:
341
+ """
342
+ Sign a Hermes function call (OpenAI-compatible function calling).
343
+
344
+ Args:
345
+ function_name: Name of the function called
346
+ arguments: Arguments passed to the function
347
+ result: Result of the function call
348
+ agent_id: Identifier for the Hermes agent
349
+ parent_sig: Optional parent signature for causal linking
350
+ context: Optional context
351
+
352
+ Returns:
353
+ SignResult with envelope and enterprise info
354
+
355
+ Example:
356
+ signed = sign_function_call(
357
+ function_name="get_weather",
358
+ arguments={"city": "San Francisco"},
359
+ result={"temp": 65, "condition": "sunny"},
360
+ agent_id="hermes-weather-bot"
361
+ )
362
+ """
363
+ payload = {
364
+ "type": "hermes_function_call",
365
+ "function_name": function_name,
366
+ "arguments": arguments,
367
+ "result": _serialize_output(result),
368
+ "timestamp": datetime.now(timezone.utc).isoformat(),
369
+ }
370
+
371
+ if parent_sig:
372
+ payload["parent_sig"] = parent_sig
373
+
374
+ return sign(
375
+ output=payload,
376
+ agent_id=agent_id,
377
+ action=f"hermes_function:{function_name}",
378
+ context=context,
379
+ )
380
+
381
+
382
+ async def sign_function_call_async(
383
+ function_name: str,
384
+ arguments: Dict[str, Any],
385
+ result: Any,
386
+ agent_id: str,
387
+ *,
388
+ parent_sig: Optional[str] = None,
389
+ context: Optional[Dict[str, Any]] = None,
390
+ ) -> SignResult:
391
+ """Async version of sign_function_call."""
392
+ payload = {
393
+ "type": "hermes_function_call",
394
+ "function_name": function_name,
395
+ "arguments": arguments,
396
+ "result": _serialize_output(result),
397
+ "timestamp": datetime.now(timezone.utc).isoformat(),
398
+ }
399
+
400
+ if parent_sig:
401
+ payload["parent_sig"] = parent_sig
402
+
403
+ return await sign_async(
404
+ output=payload,
405
+ agent_id=agent_id,
406
+ action=f"hermes_function:{function_name}",
407
+ context=context,
408
+ )
409
+
410
+
411
+ def verify_envelope(envelope: Any, payload: Any = None) -> VerifyResult:
412
+ """
413
+ Verify a signed envelope.
414
+
415
+ Args:
416
+ envelope: MOSS Envelope or dict
417
+ payload: Original payload for hash verification (optional)
418
+
419
+ Returns:
420
+ VerifyResult with valid=True/False and details
421
+ """
422
+ return verify(envelope, payload)
423
+
424
+
425
+ def _serialize_output(output: Any) -> Any:
426
+ """Serialize output to JSON-compatible format."""
427
+ if output is None:
428
+ return None
429
+ if isinstance(output, (str, int, float, bool)):
430
+ return output
431
+ if isinstance(output, dict):
432
+ return output
433
+ if isinstance(output, list):
434
+ return output
435
+ if hasattr(output, "model_dump"):
436
+ return output.model_dump()
437
+ if hasattr(output, "__dict__"):
438
+ return output.__dict__
439
+ return str(output)
@@ -0,0 +1,231 @@
1
+ """
2
+ MOSS Hermes Wrapper - Automatic signing for Hermes agents
3
+
4
+ Provides a wrapper class and decorator for automatic signing of all tool calls.
5
+
6
+ Example:
7
+ from moss_hermes import MossHermesWrapper
8
+
9
+ # Wrap any Hermes-compatible agent
10
+ agent = MossHermesWrapper(
11
+ agent=my_hermes_agent,
12
+ agent_id="hermes-assistant",
13
+ moss_api_key=os.environ["MOSS_API_KEY"]
14
+ )
15
+
16
+ # All tool calls are now automatically signed
17
+ result = agent.run("Search for AI news and summarize")
18
+ """
19
+
20
+ from typing import Any, Callable, Dict, Optional, List
21
+ from functools import wraps
22
+ import os
23
+
24
+ from .signing import sign_tool_call, sign_function_call, sign_agent_action
25
+
26
+
27
+ class MossHermesWrapper:
28
+ """
29
+ Wrapper for Hermes agents that automatically signs all tool/function calls.
30
+
31
+ Example:
32
+ from transformers import AutoModelForCausalLM
33
+ from moss_hermes import MossHermesWrapper
34
+
35
+ model = AutoModelForCausalLM.from_pretrained("NousResearch/Hermes-3-Llama-3.1-8B")
36
+
37
+ wrapped = MossHermesWrapper(
38
+ agent=model,
39
+ agent_id="hermes-3-assistant",
40
+ moss_api_key=os.environ["MOSS_API_KEY"]
41
+ )
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ agent: Any,
47
+ agent_id: str,
48
+ *,
49
+ moss_api_key: Optional[str] = None,
50
+ auto_sign_tools: bool = True,
51
+ auto_sign_functions: bool = True,
52
+ context: Optional[Dict[str, Any]] = None,
53
+ ):
54
+ """
55
+ Initialize the MOSS Hermes wrapper.
56
+
57
+ Args:
58
+ agent: The Hermes agent/model to wrap
59
+ agent_id: Identifier for this agent in MOSS
60
+ moss_api_key: MOSS API key (or set MOSS_API_KEY env var)
61
+ auto_sign_tools: Automatically sign tool calls
62
+ auto_sign_functions: Automatically sign function calls
63
+ context: Default context for all signatures
64
+ """
65
+ self.agent = agent
66
+ self.agent_id = agent_id
67
+ self.moss_api_key = moss_api_key or os.environ.get("MOSS_API_KEY")
68
+ self.auto_sign_tools = auto_sign_tools
69
+ self.auto_sign_functions = auto_sign_functions
70
+ self.context = context or {}
71
+ self._last_signature: Optional[str] = None
72
+ self._signature_chain: List[str] = []
73
+
74
+ @property
75
+ def last_signature(self) -> Optional[str]:
76
+ """Get the last signature generated."""
77
+ return self._last_signature
78
+
79
+ @property
80
+ def signature_chain(self) -> List[str]:
81
+ """Get all signatures in the current chain."""
82
+ return self._signature_chain.copy()
83
+
84
+ def clear_chain(self):
85
+ """Clear the signature chain (start fresh)."""
86
+ self._signature_chain = []
87
+ self._last_signature = None
88
+
89
+ def execute_tool(
90
+ self,
91
+ tool_name: str,
92
+ tool_input: Dict[str, Any],
93
+ tool_executor: Callable,
94
+ ) -> Any:
95
+ """
96
+ Execute a tool and sign the result.
97
+
98
+ Args:
99
+ tool_name: Name of the tool
100
+ tool_input: Input parameters
101
+ tool_executor: Function that executes the tool
102
+
103
+ Returns:
104
+ Tool output (with signature attached if enterprise mode)
105
+ """
106
+ # Execute the tool
107
+ output = tool_executor(tool_input)
108
+
109
+ # Sign if enabled
110
+ if self.auto_sign_tools:
111
+ signed = sign_tool_call(
112
+ tool_name=tool_name,
113
+ tool_input=tool_input,
114
+ tool_output=output,
115
+ agent_id=self.agent_id,
116
+ parent_sig=self._last_signature,
117
+ context=self.context,
118
+ )
119
+ self._last_signature = signed.signature
120
+ self._signature_chain.append(signed.signature)
121
+
122
+ # Check if blocked
123
+ if signed.blocked:
124
+ raise ToolBlockedError(
125
+ f"Tool '{tool_name}' blocked by policy: {signed.enterprise.policy.reason}"
126
+ )
127
+
128
+ return output
129
+
130
+ def execute_function(
131
+ self,
132
+ function_name: str,
133
+ arguments: Dict[str, Any],
134
+ function_executor: Callable,
135
+ ) -> Any:
136
+ """
137
+ Execute a function call and sign the result.
138
+
139
+ Args:
140
+ function_name: Name of the function
141
+ arguments: Function arguments
142
+ function_executor: Function that executes the call
143
+
144
+ Returns:
145
+ Function result (with signature attached if enterprise mode)
146
+ """
147
+ # Execute the function
148
+ result = function_executor(arguments)
149
+
150
+ # Sign if enabled
151
+ if self.auto_sign_functions:
152
+ signed = sign_function_call(
153
+ function_name=function_name,
154
+ arguments=arguments,
155
+ result=result,
156
+ agent_id=self.agent_id,
157
+ parent_sig=self._last_signature,
158
+ context=self.context,
159
+ )
160
+ self._last_signature = signed.signature
161
+ self._signature_chain.append(signed.signature)
162
+
163
+ # Check if blocked
164
+ if signed.blocked:
165
+ raise FunctionBlockedError(
166
+ f"Function '{function_name}' blocked by policy: {signed.enterprise.policy.reason}"
167
+ )
168
+
169
+ return result
170
+
171
+ def __getattr__(self, name: str) -> Any:
172
+ """Delegate attribute access to wrapped agent."""
173
+ return getattr(self.agent, name)
174
+
175
+
176
+ class ToolBlockedError(Exception):
177
+ """Raised when a tool call is blocked by MOSS policy."""
178
+ pass
179
+
180
+
181
+ class FunctionBlockedError(Exception):
182
+ """Raised when a function call is blocked by MOSS policy."""
183
+ pass
184
+
185
+
186
+ def moss_signed(
187
+ agent_id: str,
188
+ *,
189
+ action_type: str = "tool_call",
190
+ context: Optional[Dict[str, Any]] = None,
191
+ ):
192
+ """
193
+ Decorator to automatically sign function/tool outputs.
194
+
195
+ Example:
196
+ @moss_signed(agent_id="hermes-bot", action_type="web_search")
197
+ def search_web(query: str) -> dict:
198
+ # ... perform search ...
199
+ return results
200
+
201
+ # Function output is automatically signed
202
+ results = search_web("latest AI news")
203
+ """
204
+ def decorator(func: Callable) -> Callable:
205
+ @wraps(func)
206
+ def wrapper(*args, **kwargs):
207
+ # Extract input
208
+ tool_input = {"args": args, "kwargs": kwargs}
209
+
210
+ # Execute function
211
+ result = func(*args, **kwargs)
212
+
213
+ # Sign the result
214
+ signed = sign_agent_action(
215
+ action_type=action_type,
216
+ action_input=tool_input,
217
+ action_output=result,
218
+ agent_id=agent_id,
219
+ context=context,
220
+ )
221
+
222
+ # Check if blocked
223
+ if signed.blocked:
224
+ raise ToolBlockedError(
225
+ f"Action blocked by policy: {signed.enterprise.policy.reason}"
226
+ )
227
+
228
+ return result
229
+
230
+ return wrapper
231
+ return decorator
@@ -0,0 +1,75 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "moss-hermes"
7
+ version = "0.1.0"
8
+ description = "MOSS integration for Nous Research Hermes agents - cryptographic signing for AI actions"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [
12
+ { name = "MOSS Computing", email = "support@mosscomputing.com" }
13
+ ]
14
+ keywords = [
15
+ "moss",
16
+ "hermes",
17
+ "nous-research",
18
+ "ai-governance",
19
+ "cryptographic-signing",
20
+ "ml-dsa-44",
21
+ "post-quantum",
22
+ "ai-agents",
23
+ "llm",
24
+ ]
25
+ classifiers = [
26
+ "Development Status :: 4 - Beta",
27
+ "Intended Audience :: Developers",
28
+ "License :: OSI Approved :: MIT License",
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3.9",
31
+ "Programming Language :: Python :: 3.10",
32
+ "Programming Language :: Python :: 3.11",
33
+ "Programming Language :: Python :: 3.12",
34
+ "Topic :: Security :: Cryptography",
35
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
36
+ ]
37
+ requires-python = ">=3.9"
38
+ dependencies = [
39
+ "moss-sdk>=0.2.0",
40
+ "requests>=2.28.0",
41
+ ]
42
+
43
+ [project.optional-dependencies]
44
+ dev = [
45
+ "pytest>=7.0.0",
46
+ "pytest-asyncio>=0.21.0",
47
+ "black>=23.0.0",
48
+ "mypy>=1.0.0",
49
+ "ruff>=0.1.0",
50
+ ]
51
+
52
+ [project.urls]
53
+ Homepage = "https://mosscomputing.com"
54
+ Documentation = "https://docs.mosscomputing.com/hermes"
55
+ Repository = "https://github.com/mosscomputing/moss-hermes"
56
+ Changelog = "https://github.com/mosscomputing/moss-hermes/blob/main/CHANGELOG.md"
57
+
58
+ [tool.hatch.build.targets.wheel]
59
+ packages = ["moss_hermes"]
60
+
61
+ [tool.pytest.ini_options]
62
+ asyncio_mode = "auto"
63
+
64
+ [tool.black]
65
+ line-length = 100
66
+ target-version = ["py39", "py310", "py311", "py312"]
67
+
68
+ [tool.ruff]
69
+ line-length = 100
70
+ select = ["E", "F", "I", "W"]
71
+
72
+ [tool.mypy]
73
+ python_version = "3.9"
74
+ warn_return_any = true
75
+ warn_unused_ignores = true
@@ -0,0 +1 @@
1
+ # MOSS Hermes Tests
@@ -0,0 +1,140 @@
1
+ """Tests for MOSS Hermes signing functions."""
2
+
3
+ import pytest
4
+ from unittest.mock import patch, MagicMock
5
+
6
+
7
+ class TestSignToolCall:
8
+ """Tests for sign_tool_call function."""
9
+
10
+ def test_sign_tool_call_basic(self):
11
+ """Test basic tool call signing."""
12
+ with patch("moss_hermes.signing.sign") as mock_sign:
13
+ mock_sign.return_value = MagicMock(
14
+ signature="test_sig",
15
+ blocked=False,
16
+ )
17
+
18
+ from moss_hermes import sign_tool_call
19
+
20
+ result = sign_tool_call(
21
+ tool_name="test_tool",
22
+ tool_input={"key": "value"},
23
+ tool_output={"result": "success"},
24
+ agent_id="test-agent"
25
+ )
26
+
27
+ assert result.signature == "test_sig"
28
+ assert not result.blocked
29
+ mock_sign.assert_called_once()
30
+
31
+ def test_sign_tool_call_with_parent(self):
32
+ """Test tool call signing with parent signature."""
33
+ with patch("moss_hermes.signing.sign") as mock_sign:
34
+ mock_sign.return_value = MagicMock(
35
+ signature="child_sig",
36
+ blocked=False,
37
+ )
38
+
39
+ from moss_hermes import sign_tool_call
40
+
41
+ result = sign_tool_call(
42
+ tool_name="child_tool",
43
+ tool_input={},
44
+ tool_output={},
45
+ agent_id="test-agent",
46
+ parent_sig="parent_sig_123"
47
+ )
48
+
49
+ call_args = mock_sign.call_args
50
+ assert call_args[1]["output"]["parent_sig"] == "parent_sig_123"
51
+
52
+
53
+ class TestSignReasoningChain:
54
+ """Tests for sign_reasoning_chain function."""
55
+
56
+ def test_sign_reasoning_chain(self):
57
+ """Test reasoning chain signing."""
58
+ with patch("moss_hermes.signing.sign") as mock_sign:
59
+ mock_sign.return_value = MagicMock(
60
+ signature="reasoning_sig",
61
+ blocked=False,
62
+ )
63
+
64
+ from moss_hermes import sign_reasoning_chain
65
+
66
+ result = sign_reasoning_chain(
67
+ reasoning_steps=[
68
+ {"thought": "Step 1", "action": "search"},
69
+ {"thought": "Step 2", "action": "summarize"}
70
+ ],
71
+ final_conclusion="The answer is 42",
72
+ agent_id="test-agent"
73
+ )
74
+
75
+ assert result.signature == "reasoning_sig"
76
+ call_args = mock_sign.call_args
77
+ assert call_args[1]["output"]["step_count"] == 2
78
+
79
+
80
+ class TestSignFunctionCall:
81
+ """Tests for sign_function_call function."""
82
+
83
+ def test_sign_function_call(self):
84
+ """Test function call signing."""
85
+ with patch("moss_hermes.signing.sign") as mock_sign:
86
+ mock_sign.return_value = MagicMock(
87
+ signature="func_sig",
88
+ blocked=False,
89
+ )
90
+
91
+ from moss_hermes import sign_function_call
92
+
93
+ result = sign_function_call(
94
+ function_name="get_weather",
95
+ arguments={"city": "NYC"},
96
+ result={"temp": 72},
97
+ agent_id="test-agent"
98
+ )
99
+
100
+ assert result.signature == "func_sig"
101
+ call_args = mock_sign.call_args
102
+ assert call_args[1]["action"] == "hermes_function:get_weather"
103
+
104
+
105
+ class TestKillSwitch:
106
+ """Tests for kill-switch functionality."""
107
+
108
+ def test_check_kill_switch_active(self):
109
+ """Test kill-switch check returns True for active agent."""
110
+ with patch("moss_hermes.kill_switch.requests.get") as mock_get:
111
+ mock_get.return_value = MagicMock(
112
+ status_code=200,
113
+ json=lambda: {"status": "active"}
114
+ )
115
+
116
+ from moss_hermes import check_kill_switch
117
+
118
+ result = check_kill_switch(
119
+ "test-agent",
120
+ moss_api_key="test_key"
121
+ )
122
+
123
+ assert result is True
124
+
125
+ def test_check_kill_switch_revoked(self):
126
+ """Test kill-switch check returns False for revoked agent."""
127
+ with patch("moss_hermes.kill_switch.requests.get") as mock_get:
128
+ mock_get.return_value = MagicMock(
129
+ status_code=200,
130
+ json=lambda: {"status": "revoked"}
131
+ )
132
+
133
+ from moss_hermes import check_kill_switch
134
+
135
+ result = check_kill_switch(
136
+ "test-agent",
137
+ moss_api_key="test_key"
138
+ )
139
+
140
+ assert result is False