Skip to content

fix: Desktop hooks registration gap and diagnostics improvements - #61315

Open
seasonmsg wants to merge 1 commit into
NousResearch:mainfrom
seasonmsg:fix/desktop-hooks-and-diagnostics
Open

fix: Desktop hooks registration gap and diagnostics improvements#61315
seasonmsg wants to merge 1 commit into
NousResearch:mainfrom
seasonmsg:fix/desktop-hooks-and-diagnostics

Conversation

@seasonmsg

Copy link
Copy Markdown

Problem

When running Hermes in Desktop GUI mode, shell hooks configured in config.yaml are not registered because:

  1. Desktop WebSocket path (tui_gateway/server.py:_make_agent) never calls discover_plugins(), so Python plugins — including any that register shell hooks — are never loaded for agents created via the desktop GUI.

  2. Lack of diagnostics: When hooks fire (or fail), there's no visibility at INFO level — useful diagnostic information is locked behind DEBUG logging.

  3. Crash risk: delegate_task can crash with 'list' object has no attribute 'get' when background-review re-dispatches results.

Changes

1. tui_gateway/server.py — Ensure plugin discovery in Desktop path

# discover_plugins() is not triggered by lazy imports in the dashboard/
# desktop WebSocket path the way it is for the CLI and slash_worker paths.
# Call it explicitly here so plugin hooks fire for every agent created by
# the desktop GUI.
try:
    from hermes_cli.plugins import discover_plugins
    discover_plugins()
except Exception:
    pass

discover_plugins() is idempotent — no-ops after the first call per process, so this is safe.

2. tools/delegate_tool.py — Crash guard + diagnostic logging

  • isinstance guard: Protects against 'list' object has no attribute 'get' when result is unexpectedly a list (observed with background-review re-dispatch).
  • Log level promotion: Subagent_stop hook failure escalated from logger.debug to logger.warning so it's visible in default INFO-level logs.

3. hermes_cli/plugins.py — Hook execution diagnostics

Logs callback counts at DEBUG level when hooks fire, e.g.:

invoke_hook(subagent_stop): 1 callback(s) registered, firing...

4. agent/turn_finalizer.py — Better error diagnostics

Includes exc_info=True when on_session_end hook fails, so the full traceback is available for debugging.

Testing

  • Shell hooks (pre_tool_call, subagent_stop, on_session_end) verified to fire in Desktop GUI mode after these changes.
  • Verified with user-installed governance Python plugin (loaded via discover_plugins) calling register_from_config().
  • Tested delegate_task with various result shapes — isinstance guard prevents crash.

Related

This addresses the same gap that required downstream users to patch hermes_cli/main.py (cmd_dashboard), tui_gateway/entry.py, and cli.py with manual register_from_config() calls. With discover_plugins() called in _make_agent, user Python plugins can handle shell-hook registration themselves — no source patches needed.

Four targeted fixes for Hermes desktop/TUI paths:

1. tui_gateway/server.py: Call discover_plugins() in _make_agent so
   Python plugins (and their shell-hook registrations) fire for every
   agent created via the desktop WebSocket path. discover_plugins()
   is idempotent - no-ops after the first call per process.

2. tools/delegate_tool.py: Add isinstance(result, dict) guard before
   result.get() to prevent 'list' object has no attribute 'get'
   crashes when background-review re-dispatches results. Also
   promote subagent_stop hook failure log from debug to warning so
   it's visible in default INFO-level logs.

3. hermes_cli/plugins.py: Log invoke_hook callback counts at DEBUG
   level for diagnostics. Previously silent when hooks fire.

4. agent/turn_finalizer.py: Include exc_info=True when on_session_end
   hook fails, so the traceback is available for debugging.
@alt-glitch alt-glitch added type/bug Something isn't working comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/plugins Plugin system and bundled plugins tool/delegate Subagent delegation P3 Low — cosmetic, nice to have labels Jul 9, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: #50787 / #60010 (the discover_plugins() in _make_agent cluster — #60010 is already marked a duplicate of the canonical #50787), #41457 (the desktop shell-hooks silent-gap issue), #53894 (superset dashboard+TUI hook registration).

The discover_plugins()-in-_make_agent() leg here is the same code-site + mechanism as #50787. This PR is not a pure duplicate because it also bundles net-new work (delegate_tool.py isinstance crash-guard + INFO/WARNING hook diagnostics). A maintainer should decide whether to fold the discover_plugins fix into #50787 and land only the diagnostics/crash-guard here. Note: hermes_cli/plugins.py shows as fully re-added in the diff — that is CRLF line-ending churn, not a functional change.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the diagnostics work. The current implementation needs a narrower re-scope.

Problems

  • The new _make_agent() discovery call is redundant: tui_gateway/server.py:4472 imports run_agent.AIAgent; run_agent.py:136 imports model_tools; and model_tools.py:203-208 already invokes discover_plugins().
  • This cannot register config.yaml shell hooks. That behavior is implemented by agent/shell_hooks.py:203-278 (register_from_config); CLI and gateway invoke it at cli.py:982-985 and gateway/run.py:6947-6950, but tui_gateway/ has no equivalent call.
  • The added delegate guard stringifies any non-dict result into final_response. Because tools/delegate_tool.py:2049-2057 treats a non-empty summary as completed, a malformed list would be reported as completed instead of failed.

Suggested changes

  • Re-scope the Desktop hook fix to explicit shell-hook registration and add a regression test.
  • Represent unexpected child result types as a structured failure, not a successful textual summary.
  • Preserve LF line endings and split the diagnostic-only changes from the hook fix.

Automated hermes-sweeper review.

Comment thread tools/delegate_tool.py

# Guard: result may be a list (e.g. from background-review re-dispatch)
if not isinstance(result, dict):
result = {"final_response": str(result)[:500], "completed": False, "interrupted": False, "api_calls": 0}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This coercion makes a list a non-empty final_response; the existing status branch then reports the child as completed even though completed is false. Return a structured failed entry with an explicit invalid-result-type error instead of stringifying an unsupported result.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data tool/delegate Subagent delegation type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants