Files
hermes-identity-plugin/docs/how-it-works.md
Hermes 514706bcce feat: runtime monkey-patch instead of file patching — zero fork required
The plugin now monkey-patches HonchoMemoryProvider._do_session_init at
plugin load time instead of requiring changes to any file in the Hermes
repo. This means:

- No modifications to plugins/memory/honcho/__init__.py or any other file
- No fork needed — pure plugin approach
- Survives Hermes upgrades without merge conflicts
- Removed docs/honcho-injector.md (replaced by docs/how-it-works.md)
- Updated README with new architecture description

Config file at /opt/data/identity-config.json remains the single source
of truth for mappings.
2026-05-24 16:07:02 -04:00

3.2 KiB

Architecture: Why monkey-patch instead of patching files?

The identity plugin uses runtime monkey-patching — no files in the Hermes repo are modified. This is the key design decision that eliminates the need for a fork.

The problem

Kanban workers start with NO gateway user identity. The spawn function runs hermes -p <profile> chat -q "work kanban task <id>". When Honcho initializes, kwargs["user_id"] is None, so it creates user-default-* peers.

There is no Hermes plugin hook that fires at agent initialization before Honcho creates the session. The hooks available are:

  • pre_gateway_dispatch — only fires for gateway sessions (Discord, etc.)
  • on_session_start — fires AFTER Honcho has already initialized
  • pre_tool_call / post_tool_call — fire during tool execution, too late

The solution: runtime monkey-patch

Python allows replacing any method on any class at runtime. The identity plugin does exactly this in its register() function:

  1. At plugin load time, it imports HonchoMemoryProvider
  2. Stores a reference to the original _do_session_init method
  3. Replaces it with a wrapper that checks the identity config first
  4. If the wrapper finds a peer name, it injects it via kwargs["user_id"]
  5. Otherwise, it calls the original method unchanged
# In the plugin's register() function:
provider_cls = plugins.memory.honcho.HonchoMemoryProvider
_original_init = provider_cls._do_session_init

@wraps(_original_init)
def wrapper(self, cfg, session_id, **kwargs):
    if not kwargs.get("user_id"):
        resolved = get_peer_name()  # reads config + env vars
        if resolved:
            kwargs["user_id"] = resolved
    return _original_init(self, cfg, session_id, **kwargs)

provider_cls._do_session_init = wrapper

Advantages over file patching

Aspect File patching Monkey-patching
Repo modifications Yes (edits plugin file) Zero
Fork needed Yes (git diff to maintain) No
Upgrades Merge conflict on every pull Unchanged
Uninstall Need to revert file changes Plugin removed → clean
Reliability Works across restarts Applied at every load
Complexity Simple file edit Wrapping pattern

When does the patch apply?

  • At plugin discovery time (Hermes startup)
  • Before any Honcho session is created
  • Before any agent is initialized
  • Before any kanban worker is spawned

The Hermes plugin loader calls register() during gateway/agent startup, which means the patch is in place before any session can begin.

Safety guarantees

  1. Non-destructive: The original method is preserved via closure — removing the plugin restores original behavior with zero cleanup.

  2. Idempotent: _apply_honcho_patch() guards against double-patching via the _original_init sentinel.

  3. Graceful degradation: If the config file is missing, parsing fails, or the kanban DB is unavailable, the wrapper returns None → Honcho falls back to its default behavior (creating user-default-* peers).

  4. No data loss: The injected kwargs["user_id"] only affects what peer name Honcho assigns to the session's messages. It doesn't touch any existing data.