# svos_miranda Hermes plugin — validation pass (2026-09-15) svos-dev asked infra-ops to run `hermes plugins validate` → `doctor` → `compat` on `/home/lkraven/development/svos/hermes_plugin/` and report before enabling. Hermes Agent v0.21.1 (2026.9.7), local `b88e6776`, on nh3-dev. ## The blocker (found, fixed by svos-dev at `c964e64`) Three absolute intra-package imports — `from hermes_plugin._vendored`, `.forward`, `.jwt` — pinned the package to its **source directory name**. The documented install renames it to `svos_miranda`, and Hermes loads directory plugins under the `hermes_plugins.` namespace; in neither case does a top-level `hermes_plugin` exist. Fix: three relative imports. ⚠ **The harness hid this from three different readers.** My first `validate` passed the import only because my cwd was the SVOS repo root. svos-dev's test suite imports `hermes_plugin.*` from that same root, and their editable install resolves the name from anywhere on the box — it only reproduced for them once `sys.path` was stripped. Same class as `feedback_filters_that_silently_narrow_the_window`: the instrument carried the result. ## ⚠ Two of the three commands CANNOT pass this plugin, ever Neither is fixable from the plugin side. Both are now documented in its README. - **`validate`** — two independent causes. Its `RecordingContext.get_config` (`hermes_cli/plugin_validate.py:219-222`) returns the **default for every key**, ignoring `config.yaml` entirely, so `dispatch_key` is always `""`. And its `register_tool` returns `None`, which the plugin's INV-P6 guard correctly reads as a name collision — so even with a key supplied it raises on the first tool. - **`doctor`** — runs `register()` under a **temp `HERMES_HOME`** with sockets blocked, so no config exists there either. ## Tool-level gotchas worth remembering - ⚠ **`hermes plugins doctor` exits 0 even when it prints ERROR.** Needs `--ci`. - ⚠ **`hermes plugins compat ` prints ✓ and exits 0.** A typo'd path reads as a pass. (The instrument itself is sound — verified with a throwaway plugin importing a real deprecated path, which it flagged with file:line, exit 1.) - ⚠ **`doctor`'s sandbox registry starts EMPTY — 0 entries, no built-ins.** So doctor cannot detect tool-name collisions at all. `validate`'s separate static "built-in tool collisions" check is what covers that. - The real `PluginContext.register_tool` (`hermes_cli/plugins.py:449-491`) returns a truthy `PluginRegistration` on success — confirmed against the live runtime. ## Roster verified another way Since neither command can supply config, a probe mirroring validate's context but returning real settings and a truthy handle gave: **8 tools** with `repo_read_enabled: true`, **7** with false or omitted, names matching `plugin.yaml` exactly, zero hooks/middleware/commands. All nine settings-validation controls (quoted booleans, `"90 s"`, zero/negative timeouts, empty/whitespace strings) raise errors naming their own key. ## A false finding I caught on myself A probe registering `read_file` got back a `PluginRegistration` instead of the expected refusal — which looked like the plugin's collision reading was wrong. It was not: doctor's sandbox holds no built-ins, so nothing was claimed and **my positive case was not positive.** Reported as untested rather than as a finding. ## ✅✅ FULLY LIVE 2026-09-15 02:17 — SVOS restarted, roster verified both ends svos-dev restarted `:8770` (pid 3931403; the pre-cutover process running since 09-09 is gone) and both startup lines printed clean: hermes roster required: platform_toolsets[api_server] = ['svos_miranda'] ; agent.disabled_toolsets NOT required hermes roster verified: ('svos_miranda',) -> [the eight] **Independently confirmed from this side**, not taken on their word: `:8770` → 200, pid matches, an unauthenticated Bifrost dispatch → **401** (wall armed), and Hermes reports 29 toolsets with `svos_miranda` the sole `enabled=True`. ### ⭐⭐ Two ops patterns from their restart — both generalise well past SVOS **1. Dry-run boot against the still-held port.** They ran `python -m server` while the OLD process still held `:8770`. It printed both roster lines and restored the thread, then died on `[Errno 98] address already in use`. **Every check above the bind proven, zero downtime, before touching anything.** It converts a one-way restart into a rehearsed one and costs nothing. Adopt for any service whose startup does meaningful validation before it binds. **2. ⚠⚠ SIGTERM released the port but did NOT end the process.** It sat in shutdown for **35 seconds** and needed SIGKILL — and **the port was free that whole time.** A script that waits on the port would have started the replacement alongside a still-live old process. ⭐ **Kill by PID and wait on the PID, never on the port.** Same family as *an unreachable post office is an OUTAGE, not an empty inbox*: a freed port is not evidence of a dead process. ## ✅ LIVE 2026-09-15 02:10 — gateway restarted, plugin registered `GET /v1/toolsets` = **29 rows including `svos_miranda`**. An api_server session resolves to **exactly 8** tools, write-klass absent. Operator's default session verified **intact at 46** tools after the restart (memory / read_file / write_file / terminal / web_search / browser_exec all present) — the whole point of the ruling. ✅ **RESOLVED — svos-dev fixed it at `c9d2a96`; the key is DELETED from config.** Their reading is better than mine and is the one to keep: `_get_platform_tools` resolves `platform_toolsets[]` **FIRST** and applies the global suppression **LAST**, so subtracting 28 names from a one-element platform set is a **no-op by resolution order** — not merely "adds no safety". That generalises to any future platform; my measurement only established the single case. ⭐ And the endpoint already carried the answer: `gateway/platforms/api_server.py:: _handle_toolsets` computes each row's `enabled` as `name in _get_platform_tools(config, "api_server")`. Verified live — **29 rows, and `svos_miranda` is the ONLY row with `enabled=True`.** A check reading `enabled` rather than counting rows was always correct. SVOS's `build_miranda_roster` now returns an empty disabled list unconditionally and its startup line no longer names the key. ⚠ The 28-name list was **removed, not commented** — a paste-ready array behind a `#` is what a future session uncomments. A short warning comment stands in its place. ⚠ **`agent.disabled_toolsets` stays OFF permanently** — operator: *"i dont want the tools disabled everywhere."* So **SVOS must stop verifying against the global `/v1/toolsets`** before it restarts: it will see 29 and refuse. Options put to svos-dev: (1) verify the api_server surface instead — recommended; (3) relax to "svos_miranda present AND write-klass five absent", which also survives any unrelated plugin landing on this host. Option 2 (accept the fleet-wide cost) is ruled out. ## ENABLED in config 2026-09-15 (operator-directed) Installed to `~/.hermes/plugins/svos_miranda`; `plugins.enabled`, the settings block (dispatch key pulled from the vault, verified byte-equal), and `platform_toolsets.api_server: [svos_miranda]` all set. Config backed up to `config.yaml.bak-20260915-svos-miranda-enable`; diffed against it, only the intended non-comment lines changed. `hermes plugins list` → `svos_miranda enabled 0.1.0 user`. Gateway restarted 02:10:17 PDT — PID 3107822 → 3901622, confirmed by **observing the change** rather than assuming it. ## ⚠⚠ `agent.disabled_toolsets` is GLOBAL — it would have cost 26 tools fleet-wide svos-dev's install instructions specify `agent.disabled_toolsets = `. **That key is not scoped to api_server.** It is a strict end-of-pipeline subtraction applied to every session on every platform (`model_tools.py:216` "subtracted after enabling", applied `:332`; `cli.py:2743` reads the same key for the CLI). Measured, not derived: default session WITHOUT the line : 46 tools default session WITH the line : 20 tools lost 26: memory, read_file, write_file, patch, search_files, terminal, process_manage, web_search, web_extract, browser_exec, execute_code, computer_use, delegate_task, vision_analyze, video_analyze, session_search, skills_*, todo_list, text_to_speech, image_generate, ha_* ⭐ **And it is not needed for the security property.** Measured: `enabled_toolsets=['svos_miranda'], disabled_toolsets=None` resolves to **exactly the 8** svos_miranda tools. `platform_toolsets.api_server: [svos_miranda]` already scopes Miranda correctly on its own; the global subtraction adds no safety on top. The only thing it buys is satisfying **SVOS's startup roster check, which reads the GLOBAL `GET /v1/toolsets` to verify a PER-PLATFORM property.** Raised with svos-dev with three options (verify against the api_server surface; accept the cost with explicit operator sign-off; or relax the check to "svos_miranda present, write-klass five absent"). Left **commented out** in the config with the measurement inline, so an incidental Hermes restart cannot gut the operator's assistant. ⚠ Minor: `stt` appears in `/v1/toolsets`'s 28 rows but resolving it logs `Unknown toolset: stt` — an exact-set comparison pinned to that endpoint can fail for reasons unrelated to the plugin.