Readme enhancements - #1
Merged
Merged
Conversation
jodeev
force-pushed
the
readme-enhancements
branch
2 times, most recently
from
April 3, 2026 20:57
1873c78 to
d0708b2
Compare
…rget audience, and project rationale
jodeev
force-pushed
the
readme-enhancements
branch
from
April 3, 2026 21:07
d0708b2 to
a04ff17
Compare
jodeev
commented
Apr 3, 2026
jodeev
commented
Apr 3, 2026
jodeev
commented
Apr 3, 2026
jodeev
left a comment
Owner
Author
There was a problem hiding this comment.
suggestions to replace plurals with neutral language
jodeev
commented
Apr 3, 2026
jodeev
commented
Apr 3, 2026
jodeev
commented
Apr 3, 2026
Co-authored-by: Jodee Varney <48965776+jodeev@users.noreply.github.com>
jodeev
commented
Apr 6, 2026
jodeev
left a comment
Owner
Author
There was a problem hiding this comment.
Additional suggested fixes
Co-authored-by: Jodee Varney <48965776+jodeev@users.noreply.github.com>
Co-authored-by: Jodee Varney <48965776+jodeev@users.noreply.github.com>
…n exclusive audience
Owner
Author
|
LGTM |
jodeev
pushed a commit
that referenced
this pull request
Jun 17, 2026
…io#348) * feat(hermes): integrate hermes-agent as a long-lived-gateway adapter Mycelium can now register hermes-agent runtimes alongside openclaw, claude_code, and cursor. Hermes runs as a single gateway process that owns its own platform adapters, so the integration mirrors the openclaw shape: ship a hermes-side plugin from the mycelium repo, stage it into ~/.hermes/plugins/mycelium/ at adapter-add time, and patch ~/.hermes/config.yaml so the gateway loads it on next start. Mycelium side: - New HermesIntegration (long_lived_gateway lifecycle) handling install, uninstall, manifest construction, register/destroy, and doctor checks. register() / destroy() append/merge room entries into ~/.hermes/config.yaml under platforms.mycelium-room.rooms. - AgentManifest gains "hermes" adapter + optional hermes_profile field (parallel to openclaw_profile) so the same handle can address different gateways on one host. - adapter CLI, doctor, daemon dispatch, and generated docs all surface the new family. doctor checks plugin presence, config.yaml shape, backend_url consistency, and the gateway pid file (JSON-encoded by modern hermes). - _restart_gateway probes for a hermes-gateway systemd service before restarting; without one it prints a banner pointing at `hermes gateway run` instead of blocking forever on a foreground start. Hermes-side plugin (assets/mycelium/plugin/, staged onto the host): - Python port of openclaw's mycelium-room TS plugin — adapter.py registers a BasePlatformAdapter via PluginContext.register_platform, route.py / mentions.py mirror the TS routing + formatter, post_to_room / room_sse / session_sse / return_address / notify_home cover the rest of the wire surface. - post_to_room sends message_type: "broadcast" so the backend's MessageCreate schema accepts the payload (matches openclaw). - adapter.send() does chat_id.rpartition(":") to split the <room>:<agent> dispatch key back into a bare room slug and a default sender_handle; session sub-room keys (parent:session:<uuid>) round- trip cleanly through the same rpartition. Tests: - test_hermes_route — snapshot-style coverage for tick / consensus formatting and route_message dispatch, loaded through an isolated synthetic package so the plugin tree can be exercised without a running hermes. - test_hermes_install — end-to-end YAML patching round-trip against a temporary $HERMES_HOME, with _restart_gateway stubbed. - test_hermes_postback — pins the regressions from the smoke test: the broadcast message_type payload and the chat_id rpartition split. Docs: - docs/adapters.html (and the generator) document the new family with the same card / sidebar / section structure as the others. Smoke-tested end-to-end against a local hermes gateway running under user-mode systemd: an inbound mention reached the agent and the reply landed back in the mycelium room. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(hermes): avoid daemon-pipe deadlock in _restart_gateway Replace ``subprocess.run(..., capture_output=True, timeout=…)`` with a temp-file backed redirect in the two ``hermes gateway …`` invocations. ``hermes gateway restart`` (on a host with the gateway installed as a systemd service) spawns a detached planned-restart helper that inherits the CLI's stdout/stderr fds. The CLI itself exits promptly, but the helper keeps the pipe write-ends open. ``Popen.communicate(timeout=…)`` then blocks waiting for an EOF that never comes; when the timeout fires, the cleanup path re-enters ``communicate()`` *without* a timeout under the (here-false) assumption that "the child is dead so reads should EOF immediately" — and we deadlock anyway. Redirecting to a ``tempfile.TemporaryFile`` instead of capturing through pipes sidesteps the whole class of issue: the helper inherits the file fd, that closes cleanly when we do, and the existing 30s timeout reliably fires. We still get the diagnostic output for failure messages, so the user-facing behaviour is unchanged on the happy path. Discovered while retesting ``mycelium adapter add hermes --reinstall`` on a host with the systemd service installed. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(hermes): pin home_channel so send_message round-trips into the room When the LLM invokes ``send_message`` to reply (instead of just returning plain text, which auto-routes via ``event.source.chat_id``), hermes's tool falls back to the per-platform ``home_channel`` whenever no ``:chat_id`` suffix is supplied on the ``target``. Without a home channel set, the tool errors with ``"No home channel set for mycelium-room …"`` and the agent's reply never reaches the room — that's the smoke-test regression we hit after restarting the gateway. Two parts: 1. ``_register_room`` now writes a ``home_channel: {platform, chat_id, name}`` block into ``platforms.mycelium-room`` at register time, with ``chat_id = "<room>:<handle>"``. Last-write-wins across re-registers, mirroring hermes's own one-``HOME_CHANNEL``-env-var-per-platform UX. The adapter's ``send()`` ``rpartition``s the chat_id back into a room slug + sender_handle, so the fallback hits the right room and posts as the right agent. The ``platform`` field is mandatory: ``PlatformConfig.from_dict`` loads home_channel via ``HomeChannel.from_dict``, which unconditionally reads ``data["platform"]`` and KeyErrors without it (hermes-agent gateway/config.py:230, hit during gateway boot → crash-loop). Including ``platform: "mycelium-room"`` keeps the gateway happy. 2. ``post_to_room`` now wraps the request in ``asyncio.wait_for`` instead of passing ``timeout=aiohttp.ClientTimeout(...)`` per call. aiohttp's per-call ``ClientTimeout`` uses ``asyncio.timeout()`` under the hood, which RuntimeErrors with "Timeout context manager should be used inside a task" when the call enters from the hermes tool_executor path (the executor bridges sync → loop in a way that breaks the task-scope assumption). ``asyncio.wait_for`` is task-agnostic. Live smoke test: ``mycelium room post hermes-demo … "@Julia-Agent …"`` → the gateway agent picks up the mention, ``send_message`` resolves via the new home_channel, and the reply lands in the room. No more "No home channel set" errors, no more "Timeout context manager" errors. The "model misuses send_message as a cross-channel forward tool" quirk (messages occasionally attributed to the originating sender's handle rather than the agent's own) is intrinsic LLM behaviour, not a plumbing bug — left as a v2 ergonomic improvement. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor(hermes): drop hermes_profile field/plumbing The `hermes_profile` field on AgentManifest and the matching threading through the install/dispatch facets, doctor, and tests was plumbing-only — no CLI flag ever surfaced it, and hermes-agent's own profile commands don't co-operate with profile-aware boot today. First-class multi-profile support is deferred to hermes-agent#25660 (single gateway, multiple agents), which will replace the per-profile-deployment model entirely with handle-level routing. Until #25660 lands, Mycelium always targets whichever profile is active on the host via `$HERMES_HOME` (or `~/.hermes/`); multi-profile setups install per-profile by re-exporting `HERMES_HOME` between runs. Docs section reworked to document this and point at the PR. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(hermes): inject mycelium handle into every dispatch + roadmap docs Hermes-side personas never learn their mycelium handle from persona/SOUL — the SKILL.md uses `<your-handle>` placeholders and the plugin is the only thing that knows which handle was addressed. So every Dispatch the plugin emits now surfaces the handle: - format_tick_instruction() folds `@<handle>` into the negotiation header line; the error-tick variant replaces the bare "Room: …" line with "You are @<handle> in room …" - _route_consensus + _route_broadcast prepend a one-line identity preamble ("[mycelium-room] You are @<handle> in room <room>.") to each per-recipient Dispatch - SKILL.md adds a "Hermes quirks" bullet explaining that every inbound message tells the agent its handle, and the agent should use that in any --handle CLI fragment it issues Also documents the post-#25660 roadmap in adapters.html under a new Roadmap subsection: the planned migration from `platforms.mycelium-room.extra.rooms[]` fan-out to per-handle `routes:` entries, how mycelium handle ↔ hermes agent_id maps 1:1, why `branding.agent_name` is irrelevant (TUI cosmetic, not a routing identity), and how Mycelium will discover agent_ids by reading the top-level `agents:` block in `~/.hermes/config.yaml` directly. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(hermes): clarify handle vs. identity in plugin SKILL.md Split the handle bullet into two: the concept (handle is a Mycelium routing label, not the agent's identity or banner name) and the mechanic (every dispatch tells the agent what handle to use in --handle CLI fragments). The model was occasionally treating "@<handle>" as part of its persona; this is the bullet that tells it otherwise. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(hermes): poll for active sessions to subscribe to session sub-rooms The backend NOTIFY's coordination_join only on the session sub-room channel, never on the parent room — so the parent-room SSE never sees joins, and the prior _route_join check (`":session:" in room_name`) was a chicken-and-egg: the only way it triggered was if we were already subscribed to the session sub-room, which is the very thing we needed the join to bootstrap. Mirror the openclaw plugin's pattern: poll /api/coordination-sessions every 5s and (re-)subscribe to every active session sub-room. _spawn_session_sub is idempotent, so re-polling is cheap. Sessions in terminal states are unsubscribed. Also keep a defensive content-extract fallback in _route_join so a future schema change (joins fanned out to the parent room channel) would Just Work — costs nothing today. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(hermes): hub-and-spoke guide + restart-confirm + docgen hardening Three concerns from setting up Hermes spokes on oclw3/oclw5 against oclw4 as hub: 1. **Restart-confirm.** `mycelium adapter add hermes` and `mycelium agent create --adapter hermes` previously returned as soon as `hermes gateway restart` was *scheduled*, not when the new process was up — operators were left grepping `~/.hermes/logs/ agent.log` to confirm the plugin had subscribed (the success line doesn't reach stderr/journalctl). `_await_gateway_subscribed` now anchors the log size before issuing the restart, polls for the post-restart `subscribed to N room(s)` line, and prints `✓ hermes-gateway subscribed to N room(s)` or a yellow timeout warning with the SIGKILL fallback. Covers all four call sites (`adapter add/remove`, `agent create/rm`). 2. **Hub-and-spoke (Hermes) guide.** New `hub-and-spoke-hermes.md` documenting the four surprises the spoke setup hit: the `GATEWAY_ALLOW_ALL_USERS=true` prerequisite (Hermes ships with user allowlists closed and silently drops hub-originated dispatch without it), the `model:` block prerequisite, the `mycelium init` no-op on pre-existing configs, and the `agent create` restart-race (now resolved by #1 but documented as a Troubleshooting entry for the timeout-warning path). 3. **Docgen hardening.** The hand-rolled markdown parser in `generate_docs.py` had two latent infinite-loop bugs: a bare `>` (blockquote paragraph break) and `####`+ headers both fell through to the paragraph collector, which exits without incrementing the line cursor and spun the outer loop forever. Hit case 1 while regenerating docs for this commit. Fixes: blockquote branch now accepts bare `>`, new H4–H6 branch, and a forward-progress guard that warns on stderr and skips any unhandled line so future markdown can't silently hang the build. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(hermes): return-address hardening, loop-suppression, config sync return_address.py: - Add hook-written sidecar (.mycelium-return-origin.json) as primary home-channel source; sessions.json scan becomes the fallback - Add write debounce (_SIDECAR_REFRESH_S=300) so the pre_gateway_dispatch hook skips disk writes when the home channel hasn't changed - Extract _parse_ts() to eliminate the wrapper-dict call pattern - Fix redundant .strip() calls in _home_from_origin_dict - Promote logger to module-level; bare returns in -> None functions adapter.py: - Fix _own_message_ids eviction: replace set(list(...)[-512:]) with a companion deque that preserves insertion order - Remove redundant rstrip('/') in _poll_active_sessions (already stripped in __init__) plugin.yaml: - Declare provides_hooks: [pre_gateway_dispatch] to match __init__.py install.py / dispatch.py / doctor.py: - Extract _read_gateway_pid() -> int | None; handles both JSON {"pid": N} and legacy plain-integer pid file formats - Replace duplicated inline PID-parsing in dispatch.py and doctor.py tests/test_hermes_return_address.py: - Fix ty errors: assert spec non-None, ty: ignore on dynamic ModuleType attribute assignments, assert await_args non-None before .kwargs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(hermes): declare hermes assets as package data in setuptools integrations/hermes/assets/**/* was missing from [tool.setuptools.package-data], so non-.py files (plugin.yaml, skills/) were excluded from installed packages. On editable installs the filesystem is live so copytree found the files, but the stale egg-info SOURCES.txt didn't list plugin.yaml — causing it to be silently omitted during `mycelium adapter add hermes` on non-editable installs and making the doctor check fail with "manifest missing". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(hermes): configured home channel wins over stale sidecar in return-address Change return_address.py resolution order so that the gateway's explicitly configured home channel (MATRIX_HOME_ROOM via config.platforms.matrix.home_channel, read from the live GatewayRunner) is always priority 1. The .mycelium-return-origin.json sidecar falls back to priority 2, sessions.json to priority 3. This prevents a stale sidecar from a previous infinite-loop session from overriding the MATRIX_HOME_ROOM set for the current test run. Also deploy the fix to both hub (oclw4) and spoke1 (oclw3) nodes via their uv tool install + hermes adapter reinstall. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(hermes): remove cross-channel notify-home and return-address Drops the entire return-address sidecar and notify-home delivery path introduced in 28aca63. Hermes agents now live exclusively in mycelium rooms — there is no cross-channel hop to bridge, so the machinery (return_address.py, notify_home.py, home_channel config pinning, pre_gateway_dispatch hook, NotifyHome/StashReturnAddress route actions) is dead code. Removes ~600 lines from the plugin and ~300 from tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(hermes): housekeeping — protocol, tests, docs, SKILL.md - protocol.py: add "hermes" to AGENT_ADAPTERS frozenset and adapter Literal; fix docstring adapter count (three → four) - test_followups_e2e_fixes.py: patch reload_daemon_service (not restart) in both positive and negative cold-spawn tests; update module docstring - SKILL.md: fix dangling "Channel Messaging" cross-reference (section was renamed to "Talking to other agents") - hermes-setup.md: remove stale Matrix warning section; add require_mention: true to example config block - hub-and-spoke-hermes.md: remove gateway_restart_notification note (Matrix-only, no mycelium-room equivalent) - docs/adapters.html: regenerated Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(hermes): poll active sessions immediately on gateway startup The session poller slept _POLL_INTERVAL_S (5s) before its first poll. When session_create + session_join fired immediately after the gateway restarted (as the test harness does), the CFN fanned out first-round ticks before the plugin had subscribed to the session sub-room SSE. Those ticks were never delivered to the agents; the CFN's 300s round watchdog fired and the session closed broken=True. Move the sleep to the end of the loop body (and into the error paths) so the first poll fires immediately after the gateway connects. The plugin discovers the already-active session sub-room within milliseconds of starting and subscribes before any ticks are missed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(hermes): tighten SKILL.md negotiation guidance from E2E triage Correct counter-offer value validation docs to match client-side CLI rejection, and add Hermes-specific rules for direct mycelium invocation, --handle vs plan commands, and memory-full retry loops. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(openclaw): build and stage dist/ before host-native reinstall OpenClaw 2026.5+ validates plugins.entries.mycelium against ~/.openclaw/extensions/mycelium/dist/index.js on every CLI call. The --reinstall path copied TypeScript-only source (dist/ excluded), then built in the package tree, so `openclaw plugins install` failed before the post-install dist copy could run. Build first, include dist/ in the reinstall copy, and sync dist/ to the extension dir before plugins install. Adds a host-path regression test mirroring the container fix from mycelium-io#347. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(daemon): make `daemon restart` a real process restart SIGHUP reload stays automatic on agent create/rm and subscribe/unsubscribe; only explicit restart should recycle the systemd/launchd service. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(doctor): skip CFN intent check on spoke nodes CFN URLs are hub-only; leftover server.mas_id on spokes should not warn. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(doctor): query hub for room MAS IDs on spoke nodes Spokes verify CFN room registration via the remote backend instead of skipping when local CFN URLs are unset. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(doctor): report /api/rooms HTTP errors instead of unreachable When the hub backend responds but room listing fails (e.g. migration drift), spoke doctor should warn with the status code rather than a silent skip. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cli): ruff-format openclaw install helper Unblocks CLI lint on feat/hermes after the shipped-dist install refactor. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Julia Valenti <juliarvalenti@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Updated the README to allow people who land on this project to decipher quickly exactly what this project does and doesn't do.
Updated the problem to be more specific. Add the key differentiators of the project. Add target audience and the Why this project is here.
Changes
REAMDE updates: