You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
All four devagentic-* plugins I shipped (G1 #59, G2 #63, G3 #64, G4 #65 + #72 lazy-load) have __init__.py files that end at logger = logging.getLogger(__name__) with no register(ctx) function. Per hermes_cli/plugins.py:19-20 docstring:
"Each directory plugin must contain a plugin.yaml manifest and an __init__.py with a register(ctx) function."
Loader behavior (line 1184-1187):
register_fn=getattr(module, "register", None)
ifregister_fnisNone:
loaded.error="no register() function"logger.warning("Plugin '%s' has no register() function", manifest.name)
So the loader scans the manifest, imports __init__.py, finds no register(), logs a WARN and the plugin is silently inert. After #76 fixed the packaging, plugins now LOAD in the wheel, but register zero hooks/tools/commands. Worker sees plugin "installed" but it does nothing.
Concrete impact (G1 most critical)
G1 vertical-preamble: preamble.on_pre_llm_call exists in preamble.py but is never registered to the pre_llm_call hook → preamble loader doesn't fire on session boot → worker sees no vertical context, hallucinates doc_ids when asked to fetch grafts.
G2 mutations / G3 github / G4 lane-h: MCP tools are registered separately in mcp_serve.py::_register_X_tools (for MCP clients), so MCP server still works. But plugin loader's register() being absent means the loader reports "no tools/hooks" for these plugins.
Root cause (mine)
When I shipped G1/G2/G3/G4 I treated the __init__.py as documentation/metadata only, copying the docstring + logger pattern but dropping the register(ctx) -> None function that lives in canvas/docs __init__.py files. Canvas was the pattern I mirrored — its register() wires register_command(...) + register_hook("pre_llm_call", _preamble.on_pre_llm_call) + register_skill(...). Mine doesn't.
Fix
Add register(ctx) -> None to all 4 plugin __init__.py:
G2/G3/G4: minimal stub register() — MCP tools surface via mcp_serve.py (consistent with devagentic-docs pattern where doc_write is MCP-only, not via register_tool). Stub + comment documents the wiring location. Loader no longer warns; plugin is properly "loaded" even though it surfaces nothing through the loader.
Test plan
Per-plugin smoke test: import __init__.py, assert register is callable.
G1 specifically: call register(stub_ctx) → assert pre_llm_call hook was registered with preamble.on_pre_llm_call as the callback.
G2/G3/G4: call register(stub_ctx) → assert no exception; assert stub-ctx records no hook/tool registrations (matches the "MCP-only" design).
Per-plugin contract test: parse __init__.py and assert register function exists at module level.
Bug
All four devagentic-* plugins I shipped (G1 #59, G2 #63, G3 #64, G4 #65 + #72 lazy-load) have
__init__.pyfiles that end atlogger = logging.getLogger(__name__)with noregister(ctx)function. Perhermes_cli/plugins.py:19-20docstring:Loader behavior (line 1184-1187):
So the loader scans the manifest, imports
__init__.py, finds noregister(), logs a WARN and the plugin is silently inert. After #76 fixed the packaging, plugins now LOAD in the wheel, but register zero hooks/tools/commands. Worker sees plugin "installed" but it does nothing.Concrete impact (G1 most critical)
preamble.on_pre_llm_callexists inpreamble.pybut is never registered to thepre_llm_callhook → preamble loader doesn't fire on session boot → worker sees no vertical context, hallucinates doc_ids when asked to fetch grafts.mcp_serve.py::_register_X_tools(for MCP clients), so MCP server still works. But plugin loader's register() being absent means the loader reports "no tools/hooks" for these plugins.Root cause (mine)
When I shipped G1/G2/G3/G4 I treated the
__init__.pyas documentation/metadata only, copying the docstring + logger pattern but dropping theregister(ctx) -> Nonefunction that lives in canvas/docs__init__.pyfiles. Canvas was the pattern I mirrored — its register() wiresregister_command(...)+register_hook("pre_llm_call", _preamble.on_pre_llm_call)+register_skill(...). Mine doesn't.Fix
Add
register(ctx) -> Noneto all 4 plugin__init__.py:ctx.register_hook("pre_llm_call", _preamble.on_pre_llm_call)register()— MCP tools surface viamcp_serve.py(consistent with devagentic-docs pattern where doc_write is MCP-only, not via register_tool). Stub + comment documents the wiring location. Loader no longer warns; plugin is properly "loaded" even though it surfaces nothing through the loader.Test plan
__init__.py, assertregisteris callable.pre_llm_callhook was registered withpreamble.on_pre_llm_callas the callback.__init__.pyand assertregisterfunction exists at module level.Combined with #77
After #76+#77 + this PR + container rebuild + service restart:
find .../site-packages/plugins -name plugin.yaml→ ~25+ files (was 0 pre-fix(packaging): ship plugin.yaml via package-data + MANIFEST.in (closes #76) #77)