agentvault-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.
Files changed (138) hide show
  1. agentvault_hermes-0.1.0/.gitignore +24 -0
  2. agentvault_hermes-0.1.0/PKG-INFO +244 -0
  3. agentvault_hermes-0.1.0/README.md +208 -0
  4. agentvault_hermes-0.1.0/_spike/MLS_RS_PYTHON_AUDIT.md +152 -0
  5. agentvault_hermes-0.1.0/_spike/interop/README.md +59 -0
  6. agentvault_hermes-0.1.0/_spike/interop/mls-rs-uniffi-agentvault.patch +150 -0
  7. agentvault_hermes-0.1.0/_spike/interop/node_bob.mjs +61 -0
  8. agentvault_hermes-0.1.0/_spike/interop/python_alice.py +41 -0
  9. agentvault_hermes-0.1.0/_spike/interop/regen.sh +37 -0
  10. agentvault_hermes-0.1.0/_spike/phase3_e2e/SPIKE_FINDINGS.md +191 -0
  11. agentvault_hermes-0.1.0/_spike/phase3_e2e/phase3_pre_flight.md +459 -0
  12. agentvault_hermes-0.1.0/_spike/phase3_e2e/tier1_in_process_round_trip.py +218 -0
  13. agentvault_hermes-0.1.0/_spike/phase3_e2e/tier2_findings.md +254 -0
  14. agentvault_hermes-0.1.0/_spike/phase3_e2e/tier3_live_transport.py +266 -0
  15. agentvault_hermes-0.1.0/pyproject.toml +85 -0
  16. agentvault_hermes-0.1.0/src/agentvault_hermes/__init__.py +4 -0
  17. agentvault_hermes-0.1.0/src/agentvault_hermes/_spike/__init__.py +1 -0
  18. agentvault_hermes-0.1.0/src/agentvault_hermes/channel/__init__.py +47 -0
  19. agentvault_hermes-0.1.0/src/agentvault_hermes/channel/event_registry.py +133 -0
  20. agentvault_hermes-0.1.0/src/agentvault_hermes/channel/inbox.py +807 -0
  21. agentvault_hermes-0.1.0/src/agentvault_hermes/channel/lock_file.py +134 -0
  22. agentvault_hermes-0.1.0/src/agentvault_hermes/channel/loop_prevention.py +108 -0
  23. agentvault_hermes-0.1.0/src/agentvault_hermes/channel/mention.py +146 -0
  24. agentvault_hermes-0.1.0/src/agentvault_hermes/channel/outbox.py +164 -0
  25. agentvault_hermes-0.1.0/src/agentvault_hermes/channel/secure_channel.py +2201 -0
  26. agentvault_hermes-0.1.0/src/agentvault_hermes/channel/telemetry.py +97 -0
  27. agentvault_hermes-0.1.0/src/agentvault_hermes/cli/__init__.py +1 -0
  28. agentvault_hermes-0.1.0/src/agentvault_hermes/cli/__main__.py +63 -0
  29. agentvault_hermes-0.1.0/src/agentvault_hermes/cli/doctor.py +231 -0
  30. agentvault_hermes-0.1.0/src/agentvault_hermes/cli/setup.py +343 -0
  31. agentvault_hermes-0.1.0/src/agentvault_hermes/core/__init__.py +1 -0
  32. agentvault_hermes-0.1.0/src/agentvault_hermes/core/config/__init__.py +23 -0
  33. agentvault_hermes-0.1.0/src/agentvault_hermes/core/config/account.py +81 -0
  34. agentvault_hermes-0.1.0/src/agentvault_hermes/core/config/data_dir.py +60 -0
  35. agentvault_hermes-0.1.0/src/agentvault_hermes/core/detection/__init__.py +8 -0
  36. agentvault_hermes-0.1.0/src/agentvault_hermes/core/detection/openclaw.py +62 -0
  37. agentvault_hermes-0.1.0/src/agentvault_hermes/core/identity/__init__.py +25 -0
  38. agentvault_hermes-0.1.0/src/agentvault_hermes/core/identity/credentials.py +139 -0
  39. agentvault_hermes-0.1.0/src/agentvault_hermes/core/identity/device.py +45 -0
  40. agentvault_hermes-0.1.0/src/agentvault_hermes/core/identity/enrollment_payload.py +77 -0
  41. agentvault_hermes-0.1.0/src/agentvault_hermes/core/transport/__init__.py +19 -0
  42. agentvault_hermes-0.1.0/src/agentvault_hermes/core/transport/relay.py +243 -0
  43. agentvault_hermes-0.1.0/src/agentvault_hermes/core/transport/rest_client.py +160 -0
  44. agentvault_hermes-0.1.0/src/agentvault_hermes/core/transport/ws_client.py +141 -0
  45. agentvault_hermes-0.1.0/src/agentvault_hermes/crypto/__init__.py +34 -0
  46. agentvault_hermes-0.1.0/src/agentvault_hermes/crypto/mls_group.py +1017 -0
  47. agentvault_hermes-0.1.0/src/agentvault_hermes/crypto/persistence.py +171 -0
  48. agentvault_hermes-0.1.0/src/agentvault_hermes/crypto/primitives.py +112 -0
  49. agentvault_hermes-0.1.0/src/agentvault_hermes/crypto/ratchet.py +1043 -0
  50. agentvault_hermes-0.1.0/src/agentvault_hermes/crypto/x3dh.py +99 -0
  51. agentvault_hermes-0.1.0/src/agentvault_hermes/plugins/__init__.py +0 -0
  52. agentvault_hermes-0.1.0/src/agentvault_hermes/plugins/agentvault/__init__.py +80 -0
  53. agentvault_hermes-0.1.0/src/agentvault_hermes/plugins/agentvault/adapter.py +370 -0
  54. agentvault_hermes-0.1.0/src/agentvault_hermes/plugins/agentvault/plugin.yaml +7 -0
  55. agentvault_hermes-0.1.0/tests/__init__.py +0 -0
  56. agentvault_hermes-0.1.0/tests/_fixtures/__init__.py +0 -0
  57. agentvault_hermes-0.1.0/tests/_fixtures/gateway_runtime.py +155 -0
  58. agentvault_hermes-0.1.0/tests/conftest.py +23 -0
  59. agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/.gitattributes +1 -0
  60. agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/.gitkeep +0 -0
  61. agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/mls_group_add_member.json +15 -0
  62. agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/mls_group_create.json +14 -0
  63. agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/mls_group_encrypt_application.json +13 -0
  64. agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/mls_group_remove_member.json +14 -0
  65. agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/mls_group_welcome.json +14 -0
  66. agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/ratchet_5_message_chain.json +79 -0
  67. agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/ratchet_init.json +34 -0
  68. agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/ratchet_out_of_order.json +86 -0
  69. agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/ratchet_single_message.json +26 -0
  70. agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/ratchet_v2_header_encrypted.json +26 -0
  71. agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/x3dh_initiator.json +15 -0
  72. agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/x3dh_receiver.json +15 -0
  73. agentvault_hermes-0.1.0/tests/integration/__init__.py +0 -0
  74. agentvault_hermes-0.1.0/tests/integration/test_adapter_lifecycle.py +415 -0
  75. agentvault_hermes-0.1.0/tests/integration/test_crypto_mls_lifecycle.py +125 -0
  76. agentvault_hermes-0.1.0/tests/integration/test_crypto_persistence.py +209 -0
  77. agentvault_hermes-0.1.0/tests/integration/test_crypto_telemetry.py +155 -0
  78. agentvault_hermes-0.1.0/tests/integration/test_crypto_x3dh_to_ratchet.py +201 -0
  79. agentvault_hermes-0.1.0/tests/integration/test_doctor.py +375 -0
  80. agentvault_hermes-0.1.0/tests/integration/test_phase1_demo.py +131 -0
  81. agentvault_hermes-0.1.0/tests/integration/test_phase2_demo.py +176 -0
  82. agentvault_hermes-0.1.0/tests/integration/test_phase3_demo.py +409 -0
  83. agentvault_hermes-0.1.0/tests/integration/test_setup_flow.py +464 -0
  84. agentvault_hermes-0.1.0/tests/integration/test_welcome_to_message_roundtrip.py +531 -0
  85. agentvault_hermes-0.1.0/tests/interop/test_mls_group_interop.py +119 -0
  86. agentvault_hermes-0.1.0/tests/interop/vectors/mls_group/01_create_keypackage.bin +0 -0
  87. agentvault_hermes-0.1.0/tests/interop/vectors/mls_group/02_create_group.bin +0 -0
  88. agentvault_hermes-0.1.0/tests/interop/vectors/mls_group/03_application_message.bin +0 -0
  89. agentvault_hermes-0.1.0/tests/interop/vectors/mls_group/03_application_message.expected_plaintext +1 -0
  90. agentvault_hermes-0.1.0/tests/interop/vectors/mls_group/04_add_member_commit.bin +0 -0
  91. agentvault_hermes-0.1.0/tests/interop/vectors/mls_group/05_remove_member_commit.bin +0 -0
  92. agentvault_hermes-0.1.0/tests/interop/vectors/mls_group/README.md +116 -0
  93. agentvault_hermes-0.1.0/tests/unit/__init__.py +0 -0
  94. agentvault_hermes-0.1.0/tests/unit/channel/__init__.py +0 -0
  95. agentvault_hermes-0.1.0/tests/unit/channel/test_event_registry.py +251 -0
  96. agentvault_hermes-0.1.0/tests/unit/channel/test_inbox.py +882 -0
  97. agentvault_hermes-0.1.0/tests/unit/channel/test_inbox_mls_welcome.py +592 -0
  98. agentvault_hermes-0.1.0/tests/unit/channel/test_inbox_stale_kp_request_welcome.py +375 -0
  99. agentvault_hermes-0.1.0/tests/unit/channel/test_inbox_telemetry.py +292 -0
  100. agentvault_hermes-0.1.0/tests/unit/channel/test_inbox_welcome_group_id_utf8.py +153 -0
  101. agentvault_hermes-0.1.0/tests/unit/channel/test_lock_file.py +250 -0
  102. agentvault_hermes-0.1.0/tests/unit/channel/test_loop_prevention.py +227 -0
  103. agentvault_hermes-0.1.0/tests/unit/channel/test_mention.py +276 -0
  104. agentvault_hermes-0.1.0/tests/unit/channel/test_outbox.py +357 -0
  105. agentvault_hermes-0.1.0/tests/unit/channel/test_pull_delivery_queue.py +800 -0
  106. agentvault_hermes-0.1.0/tests/unit/channel/test_secure_channel.py +2963 -0
  107. agentvault_hermes-0.1.0/tests/unit/channel/test_secure_channel_error_event.py +325 -0
  108. agentvault_hermes-0.1.0/tests/unit/channel/test_secure_channel_kp.py +633 -0
  109. agentvault_hermes-0.1.0/tests/unit/channel/test_secure_channel_telemetry_wiring.py +379 -0
  110. agentvault_hermes-0.1.0/tests/unit/channel/test_telemetry.py +224 -0
  111. agentvault_hermes-0.1.0/tests/unit/config/__init__.py +0 -0
  112. agentvault_hermes-0.1.0/tests/unit/config/test_account.py +64 -0
  113. agentvault_hermes-0.1.0/tests/unit/config/test_data_dir.py +62 -0
  114. agentvault_hermes-0.1.0/tests/unit/core/transport/test_rest_client_mls.py +180 -0
  115. agentvault_hermes-0.1.0/tests/unit/crypto/__init__.py +0 -0
  116. agentvault_hermes-0.1.0/tests/unit/crypto/test_mls_group.py +497 -0
  117. agentvault_hermes-0.1.0/tests/unit/crypto/test_persistence.py +111 -0
  118. agentvault_hermes-0.1.0/tests/unit/crypto/test_primitives.py +155 -0
  119. agentvault_hermes-0.1.0/tests/unit/crypto/test_ratchet.py +680 -0
  120. agentvault_hermes-0.1.0/tests/unit/crypto/test_x3dh.py +228 -0
  121. agentvault_hermes-0.1.0/tests/unit/crypto/vectors/__init__.py +0 -0
  122. agentvault_hermes-0.1.0/tests/unit/crypto/vectors/conftest.py +182 -0
  123. agentvault_hermes-0.1.0/tests/unit/crypto/vectors/test_mls_group_vectors.py +240 -0
  124. agentvault_hermes-0.1.0/tests/unit/crypto/vectors/test_ratchet_vectors.py +315 -0
  125. agentvault_hermes-0.1.0/tests/unit/crypto/vectors/test_seam.py +147 -0
  126. agentvault_hermes-0.1.0/tests/unit/crypto/vectors/test_x3dh_vectors.py +67 -0
  127. agentvault_hermes-0.1.0/tests/unit/detection/test_openclaw.py +94 -0
  128. agentvault_hermes-0.1.0/tests/unit/identity/test_credentials.py +103 -0
  129. agentvault_hermes-0.1.0/tests/unit/identity/test_device.py +51 -0
  130. agentvault_hermes-0.1.0/tests/unit/identity/test_enrollment_payload.py +91 -0
  131. agentvault_hermes-0.1.0/tests/unit/plugins/__init__.py +0 -0
  132. agentvault_hermes-0.1.0/tests/unit/plugins/test_adapter.py +617 -0
  133. agentvault_hermes-0.1.0/tests/unit/plugins/test_register.py +111 -0
  134. agentvault_hermes-0.1.0/tests/unit/transport/test_relay.py +27 -0
  135. agentvault_hermes-0.1.0/tests/unit/transport/test_relay_receive.py +301 -0
  136. agentvault_hermes-0.1.0/tests/unit/transport/test_relay_send.py +417 -0
  137. agentvault_hermes-0.1.0/tests/unit/transport/test_rest_client.py +106 -0
  138. agentvault_hermes-0.1.0/tests/unit/transport/test_ws_client.py +177 -0
@@ -0,0 +1,24 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ .Python
6
+ build/
7
+ dist/
8
+ *.egg-info/
9
+ .eggs/
10
+ .pytest_cache/
11
+ .coverage
12
+ htmlcov/
13
+ .mypy_cache/
14
+ .ruff_cache/
15
+ .venv/
16
+ venv/
17
+ *.log
18
+
19
+ # Phase 0 spike build artifacts (regenerated via _spike/interop/regen.sh)
20
+ _spike/interop/libmls_rs_uniffi.dylib
21
+ _spike/interop/libmls_rs_uniffi.so
22
+ _spike/interop/mls_rs_uniffi.py
23
+ _spike/interop/artifacts/
24
+ _spike/interop/node_modules/
@@ -0,0 +1,244 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentvault-hermes
3
+ Version: 0.1.0
4
+ Summary: Native AgentVault plugin for the Hermes agent runtime — E2E encrypted owner-↔-agent and A2A messaging via MLS
5
+ Project-URL: Homepage, https://agentvault.chat
6
+ Project-URL: Documentation, https://agentvault.chat/docs/hermes
7
+ Project-URL: Repository, https://github.com/motiveflowllc/agentvault-gen2
8
+ Project-URL: Issues, https://github.com/motiveflowllc/agentvault-gen2/issues
9
+ Author-email: MotiveFlow LLC <support@agentvault.chat>
10
+ License: MIT
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: click>=8.1.0
20
+ Requires-Dist: httpx-ws>=0.6.0
21
+ Requires-Dist: httpx>=0.27.0
22
+ Requires-Dist: pynacl>=1.5.0
23
+ Requires-Dist: ruamel-yaml>=0.18.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: aiohttp>=3.9.0; extra == 'dev'
26
+ Requires-Dist: hypothesis>=6.100; extra == 'dev'
27
+ Requires-Dist: mypy>=1.10; extra == 'dev'
28
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
29
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
30
+ Requires-Dist: pytest>=8.0; extra == 'dev'
31
+ Requires-Dist: respx>=0.21; extra == 'dev'
32
+ Requires-Dist: ruff>=0.5; extra == 'dev'
33
+ Provides-Extra: mls
34
+ Requires-Dist: agentvault-mls-rs-uniffi==0.1.1; extra == 'mls'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # agentvault-hermes
38
+
39
+ Native AgentVault plugin for the Hermes agent runtime by Nous Research.
40
+
41
+ End-to-end encrypted, zero-knowledge messaging between AI agent owners and
42
+ their Hermes agents, plus A2A discovery + messaging across other
43
+ AgentVault-enabled frameworks.
44
+
45
+ ## Quickstart
46
+
47
+ The setup flow mirrors the OpenClaw plugin's: create an invite via the
48
+ AgentVault web app, enroll the device from the CLI, approve in the web
49
+ UI, and the plugin auto-activates.
50
+
51
+ ### 1. Install the package
52
+
53
+ ```bash
54
+ pip install -e ".[dev]"
55
+ ```
56
+
57
+ (Once published to PyPI, this becomes `pip install agentvault-hermes`.)
58
+
59
+ ### 2. Create an invite at agentvault.chat
60
+
61
+ 1. Log in to <https://agentvault.chat>.
62
+ 2. Open **My Agents → Quick Add** (or `/agents/quick-add`).
63
+ 3. Enter an agent name and click **Create**.
64
+ 4. The web UI displays a CLI command containing your invite token.
65
+ The token is the part after `--token=`. Copy that.
66
+
67
+ ### 3. Run the Hermes setup command on your machine
68
+
69
+ ```bash
70
+ agentvault-hermes setup --token=<paste-the-token>
71
+ ```
72
+
73
+ The plugin will:
74
+ - check your environment (`hermes` binary, `~/.hermes/config.yaml`)
75
+ - detect any OpenClaw coexistence and pick a non-colliding port
76
+ - generate Ed25519 + X25519 + MLS init keys
77
+ - POST `/api/v1/enroll` to the AV backend (your device is now PENDING)
78
+ - **wait** for you to approve in the web UI
79
+
80
+ ### 4. Approve the device in agentvault.chat
81
+
82
+ The web UI's pending-device card auto-detects your enrollment. Click
83
+ **Approve and Chat**. The CLI is polling `/api/v1/devices/{id}/status`
84
+ and will see the change within ~3 seconds.
85
+
86
+ ### 5. Plugin finishes automatically
87
+
88
+ After approval, the plugin:
89
+ - POSTs `/api/v1/devices/{id}/activate` to receive the device JWT
90
+ - writes `~/.hermes/agentvault/<account>/state.json` (mode 0o600)
91
+ - adds an `agentvault` MCP server entry to `~/.hermes/config.yaml`
92
+ (a timestamped backup of the original is written next to it)
93
+
94
+ ### 6. Start the Hermes gateway
95
+
96
+ > [!IMPORTANT]
97
+ > **This step is required.** Running the `hermes` TUI alone only registers
98
+ > plugins — it does NOT activate the AgentVault gateway platform that
99
+ > opens the WebSocket to AgentVault. Without a running gateway, your
100
+ > agent appears **Offline** in `agentvault.chat` indefinitely even though
101
+ > enrollment succeeded.
102
+
103
+ Pick one:
104
+
105
+ **Foreground (recommended for first run / debugging):**
106
+
107
+ ```bash
108
+ hermes gateway run
109
+ ```
110
+
111
+ You'll see `✓ agentvault connected` once the WS handshake completes.
112
+ `Ctrl-C` to stop. Logs land in `~/.hermes/logs/agent.log` and
113
+ `~/.hermes/logs/errors.log`.
114
+
115
+ **Background (launchd / systemd service):**
116
+
117
+ ```bash
118
+ hermes gateway install
119
+ hermes gateway start
120
+ ```
121
+
122
+ This installs a persistent service that auto-starts on login. Check
123
+ status with `hermes gateway status`; stop/restart with
124
+ `hermes gateway stop` / `hermes gateway restart`.
125
+
126
+ ### 7. Verify
127
+
128
+ ```bash
129
+ agentvault-hermes doctor
130
+ ```
131
+
132
+ Reports green on three checks:
133
+ - **Configuration** — state.json exists with a valid JWT
134
+ - **Connectivity** — AV backend reachable
135
+ - **OpenClaw coexistence** — neighbor framework detected (or not)
136
+
137
+ The doctor command does NOT yet verify the gateway is running —
138
+ [tracked as a follow-up](https://github.com/motiveflowllc/agentvault-gen2/issues).
139
+ For now confirm via `hermes gateway status` (or by sending a message
140
+ from agentvault.chat and seeing the reply land).
141
+
142
+ ## Troubleshooting
143
+
144
+ ### "I sent a message in agentvault.chat but the agent never replies"
145
+
146
+ 99% of first-run reports are a missing **Step 6**. Run
147
+ `hermes gateway status` — if it says **not running**, start it with
148
+ `hermes gateway run` (foreground) or `hermes gateway start` (background
149
+ service).
150
+
151
+ The TUI's `hermes` and `hermes chat` commands intentionally do NOT
152
+ start the gateway — they're chat surfaces, not daemon launchers. The
153
+ WebSocket that delivers your inbound messages to the agent runtime
154
+ lives in the gateway daemon process, not the TUI.
155
+
156
+ ### "Agent shows Offline in agentvault.chat even with the gateway running"
157
+
158
+ Check `~/.hermes/logs/agent.log` for the
159
+ `AV adapter connected: account=...` line. If absent, the gateway
160
+ either crashed at startup or never tried to activate the platform.
161
+ Common causes: stale `state.json` (re-enroll), wrong API base URL in
162
+ `~/.hermes/agentvault/<account>/state.json`, or a backend pairing
163
+ already mapped to a different device (delete the agent in AV-web and
164
+ re-enroll a fresh device).
165
+
166
+ ## Re-enrolling
167
+
168
+ `state.json` makes setup idempotent — re-running `agentvault-hermes setup`
169
+ with `state.json` present is a no-op. To re-enroll (new invite, new
170
+ device record):
171
+
172
+ ```bash
173
+ rm ~/.hermes/agentvault/default/state.json
174
+ agentvault-hermes setup --token=<new-token>
175
+ ```
176
+
177
+ Invite tokens are single-use, so each re-enrollment requires a fresh
178
+ invite from the web UI.
179
+
180
+ ## Multi-account
181
+
182
+ ```bash
183
+ agentvault-hermes setup --account=alice --token=<token-1>
184
+ agentvault-hermes setup --account=bob --token=<token-2>
185
+ ```
186
+
187
+ Each account gets its own `~/.hermes/agentvault/<account-id>/`
188
+ subdirectory with separate keys and JWT.
189
+
190
+ ## Status
191
+
192
+ This package is in active development. Phase 0 (mls-rs interop spike),
193
+ Phase 1 (foundation: identity, transport, CLI, integration tests, CI),
194
+ and Phase 2 (MLS crypto core) have shipped on `main`. Phase 3 (full
195
+ encrypted send/receive over the live transport) is next.
196
+
197
+ ## Phase 2 capabilities (shipped)
198
+
199
+ The crypto module under `src/agentvault_hermes/crypto/` is feature-complete
200
+ for the four primitives Hermes needs and is byte-compatible with the
201
+ OpenClaw plugin's TypeScript implementation where mathematically possible.
202
+
203
+ | Capability | Module | Tests |
204
+ |---|---|---|
205
+ | **X3DH** key agreement (Ed25519↔X25519, BLAKE2b-256) | `crypto/x3dh.py` | `tests/unit/crypto/test_x3dh.py` + `tests/unit/crypto/vectors/test_x3dh_vectors.py` (byte-equal vs OC) |
206
+ | **Double Ratchet** 1:1 messaging (XChaCha20-Poly1305 + Ed25519 header sigs + V2 header encryption) | `crypto/ratchet.py` | `tests/unit/crypto/test_ratchet.py` + `tests/unit/crypto/vectors/test_ratchet_vectors.py` (5 byte-equal scenarios) |
207
+ | **MLS group state** (mls-rs-uniffi 0.1.x wrapper, RFC 9420) | `crypto/mls_group.py` | `tests/unit/crypto/test_mls_group.py` + `tests/unit/crypto/vectors/test_mls_group_vectors.py` (parse-only structural) |
208
+ | **Persistence** (per-conversation directory layout, atomic meta writes) | `crypto/persistence.py` | `tests/unit/crypto/test_persistence.py` |
209
+
210
+ Cross-module flows (X3DH → DR bootstrap, MLS multi-member lifecycle,
211
+ state save/reload, telemetry hooks) are exercised in
212
+ `tests/integration/test_crypto_*.py`.
213
+
214
+ The Phase 2 acceptance smoke at
215
+ `tests/integration/test_phase2_demo.py` is the runnable summary — it
216
+ imports every public crypto surface and walks through the four primitives
217
+ in one test. When that file fails, expect a packaging-level break;
218
+ localized regressions surface in the deeper test suites listed above.
219
+
220
+ To run only the Phase 2 acceptance demo:
221
+
222
+ ```bash
223
+ cd packages/plugin-hermes
224
+ uv run pytest tests/integration/test_phase2_demo.py -v
225
+ ```
226
+
227
+ To run the full Phase 2 suite (~210 tests, runtime ~2s, includes 9
228
+ documented `xfail`s for cross-impl bridges that land in WS-25 follow-up
229
+ and Phase 6 telemetry):
230
+
231
+ ```bash
232
+ uv run pytest tests/ -v
233
+ ```
234
+
235
+ ## Plans + design docs
236
+
237
+ See in the AgentVault repo:
238
+
239
+ - `docs/plans/2026-05-05-hermes-native-integration-design.md` — original
240
+ design.
241
+ - `docs/plans/2026-05-05-hermes-native-integration-phase0-1-implementation.md`
242
+ — Phase 0/1 implementation plan.
243
+ - `docs/plans/2026-05-07-hermes-phase2-implementation.md` — Phase 2
244
+ work-stream breakdown (WS-22 through WS-27).
@@ -0,0 +1,208 @@
1
+ # agentvault-hermes
2
+
3
+ Native AgentVault plugin for the Hermes agent runtime by Nous Research.
4
+
5
+ End-to-end encrypted, zero-knowledge messaging between AI agent owners and
6
+ their Hermes agents, plus A2A discovery + messaging across other
7
+ AgentVault-enabled frameworks.
8
+
9
+ ## Quickstart
10
+
11
+ The setup flow mirrors the OpenClaw plugin's: create an invite via the
12
+ AgentVault web app, enroll the device from the CLI, approve in the web
13
+ UI, and the plugin auto-activates.
14
+
15
+ ### 1. Install the package
16
+
17
+ ```bash
18
+ pip install -e ".[dev]"
19
+ ```
20
+
21
+ (Once published to PyPI, this becomes `pip install agentvault-hermes`.)
22
+
23
+ ### 2. Create an invite at agentvault.chat
24
+
25
+ 1. Log in to <https://agentvault.chat>.
26
+ 2. Open **My Agents → Quick Add** (or `/agents/quick-add`).
27
+ 3. Enter an agent name and click **Create**.
28
+ 4. The web UI displays a CLI command containing your invite token.
29
+ The token is the part after `--token=`. Copy that.
30
+
31
+ ### 3. Run the Hermes setup command on your machine
32
+
33
+ ```bash
34
+ agentvault-hermes setup --token=<paste-the-token>
35
+ ```
36
+
37
+ The plugin will:
38
+ - check your environment (`hermes` binary, `~/.hermes/config.yaml`)
39
+ - detect any OpenClaw coexistence and pick a non-colliding port
40
+ - generate Ed25519 + X25519 + MLS init keys
41
+ - POST `/api/v1/enroll` to the AV backend (your device is now PENDING)
42
+ - **wait** for you to approve in the web UI
43
+
44
+ ### 4. Approve the device in agentvault.chat
45
+
46
+ The web UI's pending-device card auto-detects your enrollment. Click
47
+ **Approve and Chat**. The CLI is polling `/api/v1/devices/{id}/status`
48
+ and will see the change within ~3 seconds.
49
+
50
+ ### 5. Plugin finishes automatically
51
+
52
+ After approval, the plugin:
53
+ - POSTs `/api/v1/devices/{id}/activate` to receive the device JWT
54
+ - writes `~/.hermes/agentvault/<account>/state.json` (mode 0o600)
55
+ - adds an `agentvault` MCP server entry to `~/.hermes/config.yaml`
56
+ (a timestamped backup of the original is written next to it)
57
+
58
+ ### 6. Start the Hermes gateway
59
+
60
+ > [!IMPORTANT]
61
+ > **This step is required.** Running the `hermes` TUI alone only registers
62
+ > plugins — it does NOT activate the AgentVault gateway platform that
63
+ > opens the WebSocket to AgentVault. Without a running gateway, your
64
+ > agent appears **Offline** in `agentvault.chat` indefinitely even though
65
+ > enrollment succeeded.
66
+
67
+ Pick one:
68
+
69
+ **Foreground (recommended for first run / debugging):**
70
+
71
+ ```bash
72
+ hermes gateway run
73
+ ```
74
+
75
+ You'll see `✓ agentvault connected` once the WS handshake completes.
76
+ `Ctrl-C` to stop. Logs land in `~/.hermes/logs/agent.log` and
77
+ `~/.hermes/logs/errors.log`.
78
+
79
+ **Background (launchd / systemd service):**
80
+
81
+ ```bash
82
+ hermes gateway install
83
+ hermes gateway start
84
+ ```
85
+
86
+ This installs a persistent service that auto-starts on login. Check
87
+ status with `hermes gateway status`; stop/restart with
88
+ `hermes gateway stop` / `hermes gateway restart`.
89
+
90
+ ### 7. Verify
91
+
92
+ ```bash
93
+ agentvault-hermes doctor
94
+ ```
95
+
96
+ Reports green on three checks:
97
+ - **Configuration** — state.json exists with a valid JWT
98
+ - **Connectivity** — AV backend reachable
99
+ - **OpenClaw coexistence** — neighbor framework detected (or not)
100
+
101
+ The doctor command does NOT yet verify the gateway is running —
102
+ [tracked as a follow-up](https://github.com/motiveflowllc/agentvault-gen2/issues).
103
+ For now confirm via `hermes gateway status` (or by sending a message
104
+ from agentvault.chat and seeing the reply land).
105
+
106
+ ## Troubleshooting
107
+
108
+ ### "I sent a message in agentvault.chat but the agent never replies"
109
+
110
+ 99% of first-run reports are a missing **Step 6**. Run
111
+ `hermes gateway status` — if it says **not running**, start it with
112
+ `hermes gateway run` (foreground) or `hermes gateway start` (background
113
+ service).
114
+
115
+ The TUI's `hermes` and `hermes chat` commands intentionally do NOT
116
+ start the gateway — they're chat surfaces, not daemon launchers. The
117
+ WebSocket that delivers your inbound messages to the agent runtime
118
+ lives in the gateway daemon process, not the TUI.
119
+
120
+ ### "Agent shows Offline in agentvault.chat even with the gateway running"
121
+
122
+ Check `~/.hermes/logs/agent.log` for the
123
+ `AV adapter connected: account=...` line. If absent, the gateway
124
+ either crashed at startup or never tried to activate the platform.
125
+ Common causes: stale `state.json` (re-enroll), wrong API base URL in
126
+ `~/.hermes/agentvault/<account>/state.json`, or a backend pairing
127
+ already mapped to a different device (delete the agent in AV-web and
128
+ re-enroll a fresh device).
129
+
130
+ ## Re-enrolling
131
+
132
+ `state.json` makes setup idempotent — re-running `agentvault-hermes setup`
133
+ with `state.json` present is a no-op. To re-enroll (new invite, new
134
+ device record):
135
+
136
+ ```bash
137
+ rm ~/.hermes/agentvault/default/state.json
138
+ agentvault-hermes setup --token=<new-token>
139
+ ```
140
+
141
+ Invite tokens are single-use, so each re-enrollment requires a fresh
142
+ invite from the web UI.
143
+
144
+ ## Multi-account
145
+
146
+ ```bash
147
+ agentvault-hermes setup --account=alice --token=<token-1>
148
+ agentvault-hermes setup --account=bob --token=<token-2>
149
+ ```
150
+
151
+ Each account gets its own `~/.hermes/agentvault/<account-id>/`
152
+ subdirectory with separate keys and JWT.
153
+
154
+ ## Status
155
+
156
+ This package is in active development. Phase 0 (mls-rs interop spike),
157
+ Phase 1 (foundation: identity, transport, CLI, integration tests, CI),
158
+ and Phase 2 (MLS crypto core) have shipped on `main`. Phase 3 (full
159
+ encrypted send/receive over the live transport) is next.
160
+
161
+ ## Phase 2 capabilities (shipped)
162
+
163
+ The crypto module under `src/agentvault_hermes/crypto/` is feature-complete
164
+ for the four primitives Hermes needs and is byte-compatible with the
165
+ OpenClaw plugin's TypeScript implementation where mathematically possible.
166
+
167
+ | Capability | Module | Tests |
168
+ |---|---|---|
169
+ | **X3DH** key agreement (Ed25519↔X25519, BLAKE2b-256) | `crypto/x3dh.py` | `tests/unit/crypto/test_x3dh.py` + `tests/unit/crypto/vectors/test_x3dh_vectors.py` (byte-equal vs OC) |
170
+ | **Double Ratchet** 1:1 messaging (XChaCha20-Poly1305 + Ed25519 header sigs + V2 header encryption) | `crypto/ratchet.py` | `tests/unit/crypto/test_ratchet.py` + `tests/unit/crypto/vectors/test_ratchet_vectors.py` (5 byte-equal scenarios) |
171
+ | **MLS group state** (mls-rs-uniffi 0.1.x wrapper, RFC 9420) | `crypto/mls_group.py` | `tests/unit/crypto/test_mls_group.py` + `tests/unit/crypto/vectors/test_mls_group_vectors.py` (parse-only structural) |
172
+ | **Persistence** (per-conversation directory layout, atomic meta writes) | `crypto/persistence.py` | `tests/unit/crypto/test_persistence.py` |
173
+
174
+ Cross-module flows (X3DH → DR bootstrap, MLS multi-member lifecycle,
175
+ state save/reload, telemetry hooks) are exercised in
176
+ `tests/integration/test_crypto_*.py`.
177
+
178
+ The Phase 2 acceptance smoke at
179
+ `tests/integration/test_phase2_demo.py` is the runnable summary — it
180
+ imports every public crypto surface and walks through the four primitives
181
+ in one test. When that file fails, expect a packaging-level break;
182
+ localized regressions surface in the deeper test suites listed above.
183
+
184
+ To run only the Phase 2 acceptance demo:
185
+
186
+ ```bash
187
+ cd packages/plugin-hermes
188
+ uv run pytest tests/integration/test_phase2_demo.py -v
189
+ ```
190
+
191
+ To run the full Phase 2 suite (~210 tests, runtime ~2s, includes 9
192
+ documented `xfail`s for cross-impl bridges that land in WS-25 follow-up
193
+ and Phase 6 telemetry):
194
+
195
+ ```bash
196
+ uv run pytest tests/ -v
197
+ ```
198
+
199
+ ## Plans + design docs
200
+
201
+ See in the AgentVault repo:
202
+
203
+ - `docs/plans/2026-05-05-hermes-native-integration-design.md` — original
204
+ design.
205
+ - `docs/plans/2026-05-05-hermes-native-integration-phase0-1-implementation.md`
206
+ — Phase 0/1 implementation plan.
207
+ - `docs/plans/2026-05-07-hermes-phase2-implementation.md` — Phase 2
208
+ work-stream breakdown (WS-22 through WS-27).
@@ -0,0 +1,152 @@
1
+ # mls-rs-python audit
2
+
3
+ **Version checked:** mls-rs-uniffi 0.13.0 (from source — NOT on PyPI)
4
+ **Date:** 2026-05-06
5
+
6
+ ## Summary finding
7
+
8
+ The package name `mls-rs-python` **does not exist on PyPI** under any variant tested.
9
+ The correct binding is `mls-rs-uniffi`, a UniFFI-generated binding living in the
10
+ `mls-rs-uniffi/` subdirectory of `https://github.com/awslabs/mls-rs`.
11
+ It generates Python files from Rust via Mozilla's UniFFI framework and is **not published
12
+ to PyPI** — it must be built from source using `maturin` + a Rust toolchain.
13
+
14
+ All eight package-name variants were checked against PyPI and returned `No matching distribution found`:
15
+
16
+ | Name tried | Result |
17
+ |---|---|
18
+ | `mls-rs-python` | 404 / not found |
19
+ | `mls-rs` | not found |
20
+ | `mls_rs` | not found |
21
+ | `mls-rs-py` | not found |
22
+ | `pymls-rs` | not found |
23
+ | `mlsrs` | not found |
24
+ | `mls_rs_python` | not found |
25
+ | `mls-rs-uniffi` | not found |
26
+ | `mls_rs_uniffi` | not found |
27
+
28
+ The mls-rs repository also has **no GitHub releases** — it ships as a Rust workspace only.
29
+
30
+ **Rust toolchain is NOT installed on this machine** (`cargo` / `rustc` not found in PATH).
31
+ Any build-from-source path requires `rustup install` as a prerequisite.
32
+
33
+ ---
34
+
35
+ ## Feature coverage vs ts-mls (`mls-conversation.test.ts`)
36
+
37
+ The ts-mls column maps to actual function calls in `packages/crypto/src/mls-group.ts`.
38
+ The mls-rs-uniffi column maps to the Python API exposed by
39
+ `mls-rs-uniffi/src/lib.rs` (v0.13.0, commit `a0eb41d`, 2026-04-21)
40
+ and verified against the Python integration tests in `mls-rs-uniffi/tests/`.
41
+
42
+ | Feature | ts-mls API (`mls-group.ts`) | mls-rs-uniffi Python API | Notes |
43
+ |---|---|---|---|
44
+ | Key package generation | `generateKeyPackage(credential, caps, lifetime, exts, cs)` → returns `{publicPackage, privatePackage}` | `client.generate_key_package_message()` → `Message` | ts-mls splits public/private; uniffi bundles into opaque `Message`. No separate private key object. |
45
+ | Serialize key package | `encodeMlsMessage({wireformat:'mls_key_package', keyPackage})` | key packages are already serialized `Message` objects passed by reference | ts-mls requires explicit TLS encoding for transport; uniffi handles encoding internally. |
46
+ | Create group | `createGroup(groupId, pubPkg, privPkg, exts, cs)` → `ClientState` | `client.create_group(group_id: Optional[bytes])` → `Group` | ts-mls is stateless (returns new state); uniffi `Group` object is stateful and writes to storage. Conceptually equivalent. |
47
+ | Add member | `createCommit({state, cipherSuite, pskIndex}, {extraProposals: [{proposalType:'add', add:{keyPackage}}], ratchetTreeExtension: true})` | `group.add_members([key_package_message])` → `CommitOutput` | ts-mls uses a generic commit with proposal list; uniffi has a dedicated `add_members()`. Both produce commit + welcome. |
48
+ | Remove member | `createCommit({state, cs, pskIndex}, {extraProposals: [{proposalType:'remove', remove:{removed: idx}}]})` | `group.remove_members([signing_identity])` → `CommitOutput` | **API mismatch**: ts-mls removes by leaf index (integer); uniffi removes by `SigningIdentity` object. Mapping requires identity lookup. |
49
+ | Update key (self-update) | `createCommit({state, cs, pskIndex}, {})` — empty proposals forces implicit update | `group.commit()` → `CommitOutput` | Both support empty commit / implicit self-update. Functionally equivalent. |
50
+ | Welcome processing | `joinGroup(welcome, pubPkg, privPkg, pskIndex, cs)` → `ClientState` | `client.join_group(ratchet_tree, welcome_message)` → `JoinInfo` | ts-mls is stateless; uniffi creates a persistent `Group` stored by the `Client`. Equivalent outcome. |
51
+ | Commit processing (incoming) | `processMessage(mlsMsg, state, pskIndex, acceptAll, cs)` → `ProcessMessageResult` | `group.process_incoming_message(message)` → `ReceivedMessage` | Both handle commit + application messages via the same call. Equivalent. |
52
+ | Application message encrypt | `createApplicationMessage(state, plaintext, cs)` → `{newState, privateMessage}` then `encodeMlsMessage(...)` | `group.encrypt_application_message(message: bytes)` → `Message` | ts-mls is stateless (caller must update state); uniffi mutates `Group` in-place. Semantically equivalent. |
53
+ | Application message decrypt | `processMessage(mlsMsg, state, pskIndex, acceptAll, cs)` → `{kind:'applicationMessage', message: Uint8Array}` | `group.process_incoming_message(message)` → `ReceivedMessage` (check `.kind == 'ApplicationMessage'`) | Both distinguish application vs handshake messages in the result. |
54
+ | State export / serialization | `encodeGroupState(clientState)` → `Uint8Array`, then base64 | `group.write_to_storage()` + `client.load_group(group_id)` | **Architecture mismatch**: ts-mls uses explicit in-memory serialization; uniffi requires a storage backend (`ClientConfig` with SQLite or custom `GroupStateStorage`). No direct `exportState()` equivalent returning raw bytes. |
55
+ | State import / restore | `decodeGroupState(bytes)` → `GroupState`, reconstruct `ClientState` | `client.load_group(group_id)` from configured storage | Same as above — storage backend required; there is no raw-bytes round-trip without configuring storage. |
56
+ | Exporter secret | Not used in `mls-group.ts` but `ts-mls` exports `exporterSecret()` from `ClientState` | **NOT AVAILABLE** — `lib.rs` exposes no exporter_secret, export_secret, or epoch-keying method | This is a noted gap in the uniffi API per the lib.rs source review. |
57
+ | Ciphersuite selection | `getCiphersuiteFromName(name)` / `getCiphersuiteImpl(suite, cryptoProvider)` | `CipherSuite.CURVE25519_AES128` enum — no pluggable crypto provider | ts-mls uses a pluggable provider (noble-crypto default); uniffi hardcodes OpenSSL via `mls-rs-crypto-openssl`. Different crypto backend. |
58
+ | Epoch accessor | `clientState.groupContext.epoch` (bigint) | No direct epoch accessor on `Group` exposed to Python | Epoch tracking for replay protection requires workaround. |
59
+
60
+ ---
61
+
62
+ ## Gaps identified
63
+
64
+ 1. **No PyPI distribution** — `mls-rs-uniffi` is build-from-source only. Requires Rust toolchain
65
+ (`rustup` + `cargo`) and `maturin`. Installation is a multi-step compile, not `pip install`.
66
+ On CI/CD this means a Rust build step on every environment setup.
67
+
68
+ 2. **Exporter secret not exposed** — The uniffi Python binding does not expose `exporter_secret()`
69
+ or any epoch-keyed key derivation. This feature is available in the underlying `mls-rs` Rust
70
+ crate but was not surfaced in the UniFFI layer. Impact: any protocol that derives
71
+ per-session keys from the MLS exporter (e.g., SFrame, SRTP, or AV's own LLM proxy auth)
72
+ cannot use this binding without patching the Rust source.
73
+
74
+ 3. **Storage backend required for state persistence** — ts-mls uses explicit byte serialization
75
+ (`encodeGroupState`/`decodeGroupState`) so the caller controls persistence. The uniffi binding
76
+ requires configuring a `GroupStateStorage` (SQLite default, or custom). There is no raw-bytes
77
+ export equivalent. The Hermes plugin will need to configure and ship an SQLite file or
78
+ implement the custom storage callback interface (which involves UniFFI callback objects —
79
+ more complex to implement in Python).
80
+
81
+ 4. **Remove-by-index vs remove-by-identity** — ts-mls removes members by leaf index (integer).
82
+ The uniffi binding requires a `SigningIdentity` object. In practice the plugin must maintain
83
+ a membership roster that maps identities to group slots, adding bookkeeping overhead.
84
+
85
+ 5. **Hardcoded OpenSSL crypto backend** — ts-mls uses noble-crypto (pure JS, no native deps).
86
+ mls-rs-uniffi links against OpenSSL via `mls-rs-crypto-openssl`. This adds a system library
87
+ dependency (OpenSSL headers at build time, libssl at runtime). On macOS arm64 this typically
88
+ means installing OpenSSL via Homebrew. On Linux it depends on the distribution.
89
+
90
+ 6. **No Rust toolchain on this machine** — confirmed `cargo` and `rustc` are not installed.
91
+ Build-from-source is not currently possible in this environment without first running
92
+ `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`.
93
+
94
+ 7. **Stateful vs stateless design divergence** — ts-mls is purely functional / stateless
95
+ (every operation takes state and returns new state). mls-rs-uniffi wraps a stateful `Group`
96
+ object that mutates in place and persists to storage. The Hermes plugin wrapper will need
97
+ careful lifecycle management to avoid state corruption on concurrent message processing.
98
+
99
+ ---
100
+
101
+ ## Pinned version recommendation
102
+
103
+ **There is no PyPI version to pin.**
104
+
105
+ The binding must be installed from source at a specific git commit. The most recent
106
+ commit to `mls-rs-uniffi/` is:
107
+
108
+ ```
109
+ SHA: a0eb41d (full: e96b484e1bc5e90161d532b192b71f696e1a06b9 on main)
110
+ Date: 2026-04-21
111
+ Crate version: 0.13.0
112
+ ```
113
+
114
+ If a build-from-source approach is adopted, pin to the repo HEAD SHA:
115
+
116
+ ```
117
+ mls-rs-uniffi @ git+https://github.com/awslabs/mls-rs.git@e96b484e1bc5e90161d532b192b71f696e1a06b9#subdirectory=mls-rs-uniffi
118
+ ```
119
+
120
+ **However**, this dependency cannot currently be expressed in a standard `pyproject.toml`
121
+ `dependencies` list and requires `maturin` to compile the Rust crate. It is not installable
122
+ via a plain `pip install` without a build backend that understands Rust + UniFFI.
123
+
124
+ ### WS-5.1 GO/NO-GO input
125
+
126
+ This audit surfaces three paths for the WS-5.1 decision:
127
+
128
+ **Path A — Build mls-rs-uniffi from source (CAUTION)**
129
+ - Requires Rust + maturin in every dev/CI/production environment
130
+ - Exporter secret gap requires upstream PR or local patch to `lib.rs`
131
+ - Storage backend must be designed (SQLite file location, concurrency, cleanup)
132
+ - Significant operational complexity for customers running the Hermes plugin
133
+ - Viable but non-trivial; estimate +1-2 weeks beyond original Phase 0 scope
134
+
135
+ **Path B — Pure-Python MLS implementation (CONSIDER)**
136
+ - `pymls` or `mlslib` may exist as pure-Python RFC 9420 implementations
137
+ - Not audited in this task; recommend checking as an alternative in WS-5.1
138
+ - Avoids Rust toolchain dependency entirely
139
+ - Likely slower and potentially incomplete for production use
140
+
141
+ **Path C — Skip Python-side MLS; defer to Node binding (PREFERRED short-term)**
142
+ - The Hermes plugin (Python) acts as a relay: it forwards encrypted bytes to/from the AV backend
143
+ - MLS encrypt/decrypt runs in the Node spike (WS-2.x), which CAN use ts-mls directly
144
+ - Python plugin does NOT need its own MLS stack if the architecture is:
145
+ `Hermes agent → Python plugin → AV backend (stores ciphertext) ← Node bridge → ts-mls`
146
+ - This avoids the Rust toolchain problem entirely and is consistent with the
147
+ "server never sees plaintext" model where E2E crypto happens in the client layer (Node/TS)
148
+
149
+ **Recommendation for WS-5.1:** Escalate Path C as the primary architecture question
150
+ before investing in the mls-rs-uniffi build-from-source path. The core blocker is not
151
+ "which Python MLS library" but "does the Hermes plugin need to do MLS crypto itself,
152
+ or does it proxy ciphertext to a TS/Node layer that already has ts-mls?"