fix(plugins): add register(ctx) to G1/G2/G3/G4 — loader was silently skipping (closes #78)#79
Merged
Merged
Conversation
…skipping (closes #78)
This was referenced May 24, 2026
PowerCreek
added a commit
that referenced
this pull request
May 24, 2026
Layer 2 of the #67 cascade: post-#79 (register-ctx) + #81 (mcp_serve packaging), the bundled MCP server now ships and the G2/G3/G4 plugins load — but no MCP server entry pointed at it, so workers booted with zero MCP-side tools. This adds the missing wiring. Three edits: 1. mcp_serve.py — `if __name__ == "__main__":` block so `python -m mcp_serve` runs the existing stdio server. Honors `--verbose`/`-v` for subprocess debugging. 2. hermes_cli/mcp_autowire.py (new) — ensure_internal_mcp_server() idempotently inserts a `hermes-internal` entry under `mcp_servers` in the active profile config. Uses sys.executable + [-m, mcp_serve] so the spawn survives venv-not-on-PATH installs (the typical container-deploy shape). Opt-out via HERMES_DISABLE_INTERNAL_MCP=1. 3. tui_gateway/entry.py — calls ensure_internal_mcp_server() BEFORE the read_raw_config()::mcp_servers gate, so the gate sees the freshly-written entry on the same boot and discover_mcp_tools() picks it up. Issue body proposed `.mcp.json` (Claude-Code-style separate file); the actual config file is ~/.hermes/config.yaml under the `mcp_servers` key (the same location `tools/mcp_tool.py` reads via `hermes_cli.config.load_config`). This patch targets the real config. Tests (tests/test_internal_mcp_autowire.py — 20 cases): - HERMES_DISABLE_INTERNAL_MCP truthy variants all opt out - Idempotent: re-call is a no-op - Existing operator MCP servers (Linear/Notion/...) preserved - Creates `mcp_servers` key when absent - Command is sys.executable (not literal "python") - Args are [-m, mcp_serve] (catches the spawn-shape regression) - Source-level: mcp_serve has __main__ guard invoking run_mcp_server - Source-level: entry.py calls ensure_internal_mcp_server() BEFORE the read_raw_config gate (catches ordering regression) Smoke verified: `timeout 3 python3 -m mcp_serve < /dev/null` → exit 0 (stdio handshake; closes loop cleanly on stdin EOF). After container rebuild + worker spawn, poly-explorer should have end-to-end tool access (final layer of the cascade started at #67).
This was referenced May 24, 2026
Closed
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.
Closes #78. My bug from the original G1/G2/G3/G4 ships — I treated
__init__.pyas docstring + logger metadata and omitted theregister(ctx)function thathermes_cli/plugins.pyrequires.Per the loader (line 1184-1187):
So after #77 fixed packaging, plugins now ship + load — but every devagentic-* plugin registers nothing, because there's nothing to register. The G1 preamble loader is the most painful symptom:
preamble.on_pre_llm_callexists but is never wired to thepre_llm_callhook → worker boots with no vertical preamble → polynomial-explorer hallucinates doc_ids.Fix
ctx.register_hook("pre_llm_call", _preamble.on_pre_llm_call)— the actual fix. Wires the hook the plugin.yaml manifest declares.mcp_serve.py::_register_devagentic_mutation_tools). Matches devagentic-docs pattern where doc_write is MCP-only too.file_issuein_register_github_tools.lane_h_list/lane_h_fetch/grafted_context_fetchin_register_lane_h_tools.The stub register()s exist so the loader doesn't warn
no register()and skip the plugin. The MCP-tool wiring is separate (server-side) and unchanged.Test plan
tests/test_plugin_register_fns.py:pre_llm_callhook to a callback namedon_pre_llm_call(the source-truth fix).__init__.pyliterally containsdef register(ctx. Catches future regression where the function disappears.importlib.spec_from_file_locationwith sibling-module pre-loading so the package's relative import (from . import preamble) resolves without requiring the whole hermes package install.Deploy chain reminder
For polynomial-explorer to actually work end-to-end, the operator needs ALL of these landed + redeployed in the container:
/graphqlreachable on port 6071 so the preamble loader's fetch returns the rollupAfter all four:
find .../site-packages/plugins -name plugin.yaml→ ~25+ files (fix(packaging): ship plugin.yaml via package-data + MANIFEST.in (closes #76) #77 effect)hermes plugin listreports each devagentic-* plugin with1 hook(G1) or0 hooks 0 tools (MCP-only)(G2/G3/G4) — but the loader no longer WARNsgrafted_context_fetch(graft_id=<real-id>)with actual ids from the index instead of hallucinatingNotes
agent.toolsfor the worker's chat completions) would require schema definitions + handler shims per tool, ~50 LOC × 6 tools. Different PR, different scope. Today, those tools serve MCP clients (Claude desktop, hermes-internal MCP-client bridges, etc.) — the worker can still reach them via that path.