122 lines
7.4 KiB
Markdown
122 lines
7.4 KiB
Markdown
# Multi-user Hermes migration — qualification and operations
|
|
|
|
## Deployment
|
|
|
|
Target: Unraid `192.168.86.2`, public UI `https://192.168.86.2:9178`.
|
|
|
|
- `voice`: nginx, LiveKit, Azure speech agent and authenticated token bootstrap.
|
|
- `hope-control`: account service, private SQLite and trusted Docker provisioning.
|
|
- Administrator container: `hope-hermes-admin-69101358`.
|
|
- John Doe container: **`hope-hermes-john-doe-bac897a4`**, private and running.
|
|
- User-facing test login username: `john-doe`. Initial passwords are generated,
|
|
not committed or printed; obtain them from the protected deployment credential
|
|
record or use administrator password reset in the website.
|
|
- Final voice image: `sha256:ea86ac10377c553f32a70d24f46dea1413c949673ed1b9850a4aabbe8574e8d8`;
|
|
verified `healthy`. Final control HTTP health returned `{"status":"ok"}`.
|
|
- No push performed. The existing dirty NAS checkout was not overwritten; clean
|
|
build source was staged separately. Hope Music and unrelated services untouched.
|
|
|
|
Runtime root: `/mnt/user/appdata/hope-voice`. `runtime.env`, `voice.env` and
|
|
`livekit.yaml` are root-only. Account persistence is in `accounts/`; private
|
|
Hermes homes/workspaces in `hermes/`. Bootstrap password is only used on an empty
|
|
DB. Never print these files, Docker environment inspection or SQLite key fields.
|
|
|
|
The runtime Hermes image is pinned to:
|
|
`nousresearch/hermes-agent@sha256:d4da4a40cd7a28aba983775d9fd31d94cbf153eeb0cb9e844d6d0f612b7c24db`.
|
|
|
|
## Real-model result
|
|
|
|
The supplied Responses endpoint `http://192.168.86.5:2099/v1/responses` accepted
|
|
model `auto` and `reasoning.effort: none`. It reported **Qwen3.8-27b**, HTTP 200,
|
|
and **zero reasoning tokens** for the direct probe. Hermes custom provider uses
|
|
Responses transport and defaults reasoning effort to `none`. The speech bridge
|
|
also passes `model_options.reasoning.enabled: false` and forwards content only.
|
|
|
|
## Verified live
|
|
|
|
- Browser HTTPS login, HttpOnly cookie session and administrator user listing.
|
|
- John Doe created through actual administrative API: durable provisioning →
|
|
`ready` → named private Docker container, without manual Docker creation.
|
|
- John private Hermes returned `Hello John Doe!` from the real upstream.
|
|
- Real browser → authenticated token → WSS/LiveKit → Hermes → final transcript:
|
|
`Hello, John Doe — glad to have you here!` (another run: `Hey John! Good to see you.`).
|
|
- Chromium reported no page exceptions in the successful browser run.
|
|
- Long tool task: Hermes ran Python sleeping eight seconds then printed
|
|
`HERMES_LONG_TASK_OK`. Activity/status displayed working before completion;
|
|
eventual assistant reply reported the exact marker and exit code zero.
|
|
- Disposable account auto-provisioned to ready. Non-admin administration denied
|
|
403; attempted client-selected account/room ignored by token service.
|
|
- Administrator deletion immediately invalidated session/bootstrap; lifecycle
|
|
removed the account and its named container. Private data retained deliberately.
|
|
- Logout invalidated the token; room grant revocation also covered offline.
|
|
- Actual Azure out-of-band wait-notice and final answer audio received over
|
|
WebRTC on two independent tracks (measured nonzero peak levels 0.904 / 0.786).
|
|
Probe used authenticated LiveKit text input with agent voice output and muted
|
|
synthetic microphone; this verifies synthesis/transport, not physical STT.
|
|
- Actual speech worker registered and final container health passed. Health now
|
|
checks HTTPS, LiveKit, control and PID-bound worker endpoint, not just web shell.
|
|
|
|
## Offline evidence
|
|
|
|
- Account/API tests: **15 passed**, including password-reset/login race and
|
|
narrowly trusted proxy identity/failed-login throttling regressions.
|
|
- Provisioner tests: **9 passed**.
|
|
- Authenticated bootstrap tests: **2 passed**.
|
|
- LiveKit Hermes stream tests: **16 passed** (no reasoning fallback or replay,
|
|
approvals fail safely, total timeout, cancellation, visible timer progress,
|
|
out-of-band notice/first-answer callback contracts).
|
|
- iOS commit **`bebaf59`**, authenticated bearer + device-only Keychain,
|
|
origin/account-scoped history, HTTPS/WSS, no model picker, progress UI.
|
|
- iOS simulator build and final **52 unit tests passed**. Earlier combined
|
|
51 unit + 5 UI passed. Expanded UI run exposed two pre-existing asynchronous
|
|
assertion races; synchronization fixed, focused settings/large-type/account
|
|
switch tests passed. No claim of one fully green expanded UI run.
|
|
|
|
## Operational behavior and boundaries
|
|
|
|
- Each user has separate persistent state, API credential, bridge network,
|
|
nonroot UID10000 runtime, capability drop, no-new-privileges and resource caps.
|
|
- Hermes API is loopback-published, not LAN-published; homes contain no Docker
|
|
socket or account DB. Speech has neither Docker socket nor upstream provider key.
|
|
- Docker is the tenant boundary, not protection against a kernel exploit. Hermes
|
|
tools intentionally access network/model/browser/terminal within that container;
|
|
this is not a no-network hostile-code sandbox. The account service is privileged
|
|
and must remain trusted/loopback-only. LAN SSH/root administrators can access
|
|
all runtime secrets and user data.
|
|
- Hermes approvals have no interactive voice approval transport: fail explicitly
|
|
rather than hanging or executing/retrying silently. Tool actions are not replayed.
|
|
- Long requests emit progress after three seconds/tool start. Spoken wait notices
|
|
use independent audio, not `AgentSession.say` which queues behind the response.
|
|
First answer cancels notice playback; notice is not added to conversation history.
|
|
- Runtime LiveKit ICE candidates are restricted to Unraid LAN `br0` and loopback
|
|
with node IP `192.168.86.2`, avoiding dozens of unreachable Docker bridge candidates.
|
|
Adjust the private runtime YAML for a different host/interface.
|
|
- Account reset/logout/delete revoke existing grants; agent checks access every
|
|
two seconds. Transport JWT alone is not authorization to issue Hermes requests.
|
|
- Account deletion removes owned container/network, **not retained home data**.
|
|
- Historical MCP/router/memory/dispatch source and tests remain for reference,
|
|
but are excluded from runtime packaging. No shared Hermes home migration occurs.
|
|
|
|
## Deployment / rollback
|
|
|
|
Build both images, then use `scripts/deploy-unraid.sh` on Unraid. It requires
|
|
pre-existing protected environment/state/config/cert paths. It preserves original
|
|
containers by timestamped name and root-only inspection receipt; rollback is
|
|
installed before any stop/rename. Do not remove rollback containers or retained
|
|
homes until explicitly deciding retention policy.
|
|
|
|
No secrets belong in CI source; the workflow builds both images then calls the
|
|
protected-host deployment script. LiveKit keys are runtime-only; committed YAML
|
|
contains no usable key. Keep API origins synchronized for new DNS names.
|
|
|
|
## Remaining human/hardware qualification
|
|
|
|
Real physical microphone recognition, perceived speech quality, barge-in and
|
|
an installed iPhone against the NAS have **not** been claimed. Self-signed SDK WSS
|
|
trust may require installing a trusted private CA on the iPhone; native HTTP pinning
|
|
is not a guarantee the SDK socket trusts that certificate. The deployed public
|
|
leaf certificate SHA256 was compared with the native HTTP pin and matched exactly
|
|
(`07907abde3bb9c685265909927627d9ea4705d7e7b27056eefe06e10b192af46`). No trust-all/plaintext
|
|
fallback was added. Browser automation's certificate exception is a test setting,
|
|
not production TLS policy. See iOS project handoff for test artifacts.
|