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.
- agentvault_hermes-0.1.0/.gitignore +24 -0
- agentvault_hermes-0.1.0/PKG-INFO +244 -0
- agentvault_hermes-0.1.0/README.md +208 -0
- agentvault_hermes-0.1.0/_spike/MLS_RS_PYTHON_AUDIT.md +152 -0
- agentvault_hermes-0.1.0/_spike/interop/README.md +59 -0
- agentvault_hermes-0.1.0/_spike/interop/mls-rs-uniffi-agentvault.patch +150 -0
- agentvault_hermes-0.1.0/_spike/interop/node_bob.mjs +61 -0
- agentvault_hermes-0.1.0/_spike/interop/python_alice.py +41 -0
- agentvault_hermes-0.1.0/_spike/interop/regen.sh +37 -0
- agentvault_hermes-0.1.0/_spike/phase3_e2e/SPIKE_FINDINGS.md +191 -0
- agentvault_hermes-0.1.0/_spike/phase3_e2e/phase3_pre_flight.md +459 -0
- agentvault_hermes-0.1.0/_spike/phase3_e2e/tier1_in_process_round_trip.py +218 -0
- agentvault_hermes-0.1.0/_spike/phase3_e2e/tier2_findings.md +254 -0
- agentvault_hermes-0.1.0/_spike/phase3_e2e/tier3_live_transport.py +266 -0
- agentvault_hermes-0.1.0/pyproject.toml +85 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/__init__.py +4 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/_spike/__init__.py +1 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/channel/__init__.py +47 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/channel/event_registry.py +133 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/channel/inbox.py +807 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/channel/lock_file.py +134 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/channel/loop_prevention.py +108 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/channel/mention.py +146 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/channel/outbox.py +164 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/channel/secure_channel.py +2201 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/channel/telemetry.py +97 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/cli/__init__.py +1 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/cli/__main__.py +63 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/cli/doctor.py +231 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/cli/setup.py +343 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/core/__init__.py +1 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/core/config/__init__.py +23 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/core/config/account.py +81 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/core/config/data_dir.py +60 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/core/detection/__init__.py +8 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/core/detection/openclaw.py +62 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/core/identity/__init__.py +25 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/core/identity/credentials.py +139 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/core/identity/device.py +45 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/core/identity/enrollment_payload.py +77 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/core/transport/__init__.py +19 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/core/transport/relay.py +243 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/core/transport/rest_client.py +160 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/core/transport/ws_client.py +141 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/crypto/__init__.py +34 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/crypto/mls_group.py +1017 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/crypto/persistence.py +171 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/crypto/primitives.py +112 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/crypto/ratchet.py +1043 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/crypto/x3dh.py +99 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/plugins/__init__.py +0 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/plugins/agentvault/__init__.py +80 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/plugins/agentvault/adapter.py +370 -0
- agentvault_hermes-0.1.0/src/agentvault_hermes/plugins/agentvault/plugin.yaml +7 -0
- agentvault_hermes-0.1.0/tests/__init__.py +0 -0
- agentvault_hermes-0.1.0/tests/_fixtures/__init__.py +0 -0
- agentvault_hermes-0.1.0/tests/_fixtures/gateway_runtime.py +155 -0
- agentvault_hermes-0.1.0/tests/conftest.py +23 -0
- agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/.gitattributes +1 -0
- agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/.gitkeep +0 -0
- agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/mls_group_add_member.json +15 -0
- agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/mls_group_create.json +14 -0
- agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/mls_group_encrypt_application.json +13 -0
- agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/mls_group_remove_member.json +14 -0
- agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/mls_group_welcome.json +14 -0
- agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/ratchet_5_message_chain.json +79 -0
- agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/ratchet_init.json +34 -0
- agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/ratchet_out_of_order.json +86 -0
- agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/ratchet_single_message.json +26 -0
- agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/ratchet_v2_header_encrypted.json +26 -0
- agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/x3dh_initiator.json +15 -0
- agentvault_hermes-0.1.0/tests/fixtures/oc_vectors/x3dh_receiver.json +15 -0
- agentvault_hermes-0.1.0/tests/integration/__init__.py +0 -0
- agentvault_hermes-0.1.0/tests/integration/test_adapter_lifecycle.py +415 -0
- agentvault_hermes-0.1.0/tests/integration/test_crypto_mls_lifecycle.py +125 -0
- agentvault_hermes-0.1.0/tests/integration/test_crypto_persistence.py +209 -0
- agentvault_hermes-0.1.0/tests/integration/test_crypto_telemetry.py +155 -0
- agentvault_hermes-0.1.0/tests/integration/test_crypto_x3dh_to_ratchet.py +201 -0
- agentvault_hermes-0.1.0/tests/integration/test_doctor.py +375 -0
- agentvault_hermes-0.1.0/tests/integration/test_phase1_demo.py +131 -0
- agentvault_hermes-0.1.0/tests/integration/test_phase2_demo.py +176 -0
- agentvault_hermes-0.1.0/tests/integration/test_phase3_demo.py +409 -0
- agentvault_hermes-0.1.0/tests/integration/test_setup_flow.py +464 -0
- agentvault_hermes-0.1.0/tests/integration/test_welcome_to_message_roundtrip.py +531 -0
- agentvault_hermes-0.1.0/tests/interop/test_mls_group_interop.py +119 -0
- agentvault_hermes-0.1.0/tests/interop/vectors/mls_group/01_create_keypackage.bin +0 -0
- agentvault_hermes-0.1.0/tests/interop/vectors/mls_group/02_create_group.bin +0 -0
- agentvault_hermes-0.1.0/tests/interop/vectors/mls_group/03_application_message.bin +0 -0
- agentvault_hermes-0.1.0/tests/interop/vectors/mls_group/03_application_message.expected_plaintext +1 -0
- agentvault_hermes-0.1.0/tests/interop/vectors/mls_group/04_add_member_commit.bin +0 -0
- agentvault_hermes-0.1.0/tests/interop/vectors/mls_group/05_remove_member_commit.bin +0 -0
- agentvault_hermes-0.1.0/tests/interop/vectors/mls_group/README.md +116 -0
- agentvault_hermes-0.1.0/tests/unit/__init__.py +0 -0
- agentvault_hermes-0.1.0/tests/unit/channel/__init__.py +0 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_event_registry.py +251 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_inbox.py +882 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_inbox_mls_welcome.py +592 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_inbox_stale_kp_request_welcome.py +375 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_inbox_telemetry.py +292 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_inbox_welcome_group_id_utf8.py +153 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_lock_file.py +250 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_loop_prevention.py +227 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_mention.py +276 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_outbox.py +357 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_pull_delivery_queue.py +800 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_secure_channel.py +2963 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_secure_channel_error_event.py +325 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_secure_channel_kp.py +633 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_secure_channel_telemetry_wiring.py +379 -0
- agentvault_hermes-0.1.0/tests/unit/channel/test_telemetry.py +224 -0
- agentvault_hermes-0.1.0/tests/unit/config/__init__.py +0 -0
- agentvault_hermes-0.1.0/tests/unit/config/test_account.py +64 -0
- agentvault_hermes-0.1.0/tests/unit/config/test_data_dir.py +62 -0
- agentvault_hermes-0.1.0/tests/unit/core/transport/test_rest_client_mls.py +180 -0
- agentvault_hermes-0.1.0/tests/unit/crypto/__init__.py +0 -0
- agentvault_hermes-0.1.0/tests/unit/crypto/test_mls_group.py +497 -0
- agentvault_hermes-0.1.0/tests/unit/crypto/test_persistence.py +111 -0
- agentvault_hermes-0.1.0/tests/unit/crypto/test_primitives.py +155 -0
- agentvault_hermes-0.1.0/tests/unit/crypto/test_ratchet.py +680 -0
- agentvault_hermes-0.1.0/tests/unit/crypto/test_x3dh.py +228 -0
- agentvault_hermes-0.1.0/tests/unit/crypto/vectors/__init__.py +0 -0
- agentvault_hermes-0.1.0/tests/unit/crypto/vectors/conftest.py +182 -0
- agentvault_hermes-0.1.0/tests/unit/crypto/vectors/test_mls_group_vectors.py +240 -0
- agentvault_hermes-0.1.0/tests/unit/crypto/vectors/test_ratchet_vectors.py +315 -0
- agentvault_hermes-0.1.0/tests/unit/crypto/vectors/test_seam.py +147 -0
- agentvault_hermes-0.1.0/tests/unit/crypto/vectors/test_x3dh_vectors.py +67 -0
- agentvault_hermes-0.1.0/tests/unit/detection/test_openclaw.py +94 -0
- agentvault_hermes-0.1.0/tests/unit/identity/test_credentials.py +103 -0
- agentvault_hermes-0.1.0/tests/unit/identity/test_device.py +51 -0
- agentvault_hermes-0.1.0/tests/unit/identity/test_enrollment_payload.py +91 -0
- agentvault_hermes-0.1.0/tests/unit/plugins/__init__.py +0 -0
- agentvault_hermes-0.1.0/tests/unit/plugins/test_adapter.py +617 -0
- agentvault_hermes-0.1.0/tests/unit/plugins/test_register.py +111 -0
- agentvault_hermes-0.1.0/tests/unit/transport/test_relay.py +27 -0
- agentvault_hermes-0.1.0/tests/unit/transport/test_relay_receive.py +301 -0
- agentvault_hermes-0.1.0/tests/unit/transport/test_relay_send.py +417 -0
- agentvault_hermes-0.1.0/tests/unit/transport/test_rest_client.py +106 -0
- 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?"
|