Skip to content

feat(plugins): add four extension hooks for plugin-directed core behavior - #74645

Closed
TurgutKural wants to merge 2 commits into
NousResearch:mainfrom
TurgutKural:feat/plugin-extension-hooks
Closed

feat(plugins): add four extension hooks for plugin-directed core behavior#74645
TurgutKural wants to merge 2 commits into
NousResearch:mainfrom
TurgutKural:feat/plugin-extension-hooks

Conversation

@TurgutKural

Copy link
Copy Markdown
Contributor

Summary

Add four narrow, fail-open plugin hooks so user-installed plugins (~/.hermes/plugins/) can override or veto specific core behaviors without patching core files.

Motivation

Several open PRs fix real bugs by patching core internals that the plugin system cannot reach:

PR Problem Core file patched
#72549 TRUNCATE checkpoint tears pages under SIGTERM race hermes_state.py
#62701 Fuzzy repair silently substitutes MCP tool names agent/agent_runtime_helpers.py
#61499 Nous delegation bypasses runtime JWT auth tools/delegate_tool.py
#58512 Compression runs under stale context-engine binding agent/conversation_compression.py

Rather than carrying fork-specific patches across every upstream rebase, this PR exposes narrow hook points that let a plugin implement the same fixes without touching core files.

Hook contracts

All hooks are fail-open: no plugin loaded → original behavior preserved exactly. Each call site wraps invoke_hook in try/except; a misbehaving plugin cannot break the core.

pre_db_checkpoint

Fired before WAL checkpoint in SessionDB.close() and all pre-VACUUM paths (3 call sites).

pre_fuzzy_repair

Fired before the fuzzy-match fallback in repair_tool_call().

pre_delegation_credentials

Fired at the top of _resolve_delegation_credentials() before any built-in resolution.

pre_compression

Fired just before context compression begins.

  • Kwargs: agent, session_id: str, message_count: int
  • Return: {"skip": True, "reason": str} to veto compression for this tick; side-effect hooks (e.g. rebinding a shared context engine) may run without returning.
  • Enables: fix(compression): rebind context engine before compaction #58512 as a plugin — rebind a shared LCM engine to the host session before compress.

Changes

File Change
hermes_cli/plugins.py Add four hooks to VALID_HOOKS with docstrings
hermes_state.py Fire pre_db_checkpoint in close() and pre-VACUUM
hermes_state_search.py Fire pre_db_checkpoint in optimize-storage VACUUM
agent/agent_runtime_helpers.py Fire pre_fuzzy_repair before fuzzy fallback
tools/delegate_tool.py Fire pre_delegation_credentials at function entry
agent/conversation_compression.py Fire pre_compression before compression starts
tests/hermes_cli/test_extension_hooks.py 26 tests covering all four hooks

Validation

  • pytest tests/hermes_cli/test_extension_hooks.py26 passed
  • pytest tests/test_wal_checkpoint_strategy.py tests/run_agent/test_repair_tool_call_name.py tests/hermes_cli/test_plugins.py tests/tools/test_delegate.py::TestDelegationCredentialResolution tests/run_agent/test_message_sequence_repair.py95 passed
  • python -m py_compile on all six modified files → OK
  • Pre-existing failures (No module named 'openai') confirmed identical on unmodified upstream/main — not caused by this change.

Design notes

  • No new middleware kinds — these are observer-with-return hooks, not request-rewriting middleware. The existing invoke_hook contract (list of non-None returns) is sufficient.
  • Lazy imports — every call site uses from hermes_cli.plugins import invoke_hook inside the function body, matching the existing pattern in hermes_state.py (which already lazy-imports from hermes_cli). No new import cycles.
  • Copy-on-write semantics preserved — no hook mutates its input kwargs. The delegation hook returns a new dict; the checkpoint hook returns a mode string; the fuzzy hook returns a skip flag.
  • AGENTS.md compliance — no new core tools, no new env vars, no cache-breaking. The hooks fire at points that are already per-operation (checkpoint, repair, credential resolution, compression), not per-API-call.

@alt-glitch alt-glitch added type/feature New feature or request comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/plugins Plugin system and bundled plugins tool/delegate Subagent delegation P3 Low — cosmetic, nice to have labels Jul 30, 2026

@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 proposing a plugin-surface alternative to the four linked core patches. The target behaviors remain live on current main: agent/agent_runtime_helpers.py:2849 still has unconditional fuzzy fallback, hermes_state.py:2542 and :8322 still issue fixed TRUNCATE checkpoints, tools/delegate_tool.py:3500 has no pre-resolution interception, and agent/conversation_compression.py:1759 invokes the compressor without a pre-hook.

Problems

  • hermes_state.py’s proposed mode handling accepts every {"mode": ...} value and interpolates it into PRAGMA wal_checkpoint(...). That contradicts the documented contract that anything except the supported override keeps the default; an invalid mode instead makes the checkpoint fail. The identical parsing appears in the proposed hermes_state_search.py hunk.
  • tests/hermes_cli/test_extension_hooks.py:165 and :196 permit the vacuum tests to pass without firing a hook or executing a checkpoint. They also do not cover the modified SessionDB.vacuum() path.
  • tests/hermes_cli/test_extension_hooks.py:450-502 tests dispatcher return values rather than compress_context(), so it does not prove the veto branch avoids compressor execution and releases its lease.

Suggested changes

  • Validate checkpoint modes against an explicit allow-list and retain TRUNCATE on every invalid result.
  • Add deterministic path-level checkpoint tests plus a real compression-veto integration test.

Automated hermes-sweeper review.

Comment thread hermes_state.py Outdated
Comment thread tests/hermes_cli/test_extension_hooks.py Outdated
Comment thread tests/hermes_cli/test_extension_hooks.py Outdated
@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state 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-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 30, 2026
@TurgutKural
TurgutKural force-pushed the feat/plugin-extension-hooks branch 4 times, most recently from 03067d6 to f313e5f Compare August 2, 2026 03:36
@TurgutKural
TurgutKural force-pushed the feat/plugin-extension-hooks branch 2 times, most recently from 04d7892 to e3193bb Compare August 5, 2026 03:47
…vior

Add pre_db_checkpoint, pre_fuzzy_repair, pre_delegation_credentials,
and pre_compression hooks so plugins can override or veto specific
core behaviors without patching core files.

Motivation: several open PRs (NousResearch#72549, NousResearch#62701, NousResearch#61499, NousResearch#58512) fix
real bugs by patching core internals that the plugin system cannot
reach. Rather than carrying fork-specific patches across every
upstream rebase, expose narrow, fail-open hook points that let a
user-installed plugin (~/.hermes/plugins/) implement the same fixes
without touching core files.

Hook contracts (all fail-open — no plugin loaded = original behavior):

- pre_db_checkpoint: fired before WAL checkpoint in SessionDB.close()
  and pre-VACUUM paths. Return {"mode": "PASSIVE"} to override the
  default TRUNCATE mode. Enables plugins to prevent page-tear under
  SIGTERM races (NousResearch#45383) without patching hermes_state.py.

- pre_fuzzy_repair: fired before the fuzzy-match fallback in
  repair_tool_call(). Return {"skip": True} to suppress fuzzy
  matching for specific tool names (e.g. MCP names where fuzzy
  substitution changes semantics — NousResearch#62701).

- pre_delegation_credentials: fired at the top of
  _resolve_delegation_credentials(). Return a full credential dict
  (with "provider" key) to short-circuit built-in resolution.
  Enables plugins to implement Nous JWT rotation (NousResearch#61499) before
  the direct-endpoint path runs.

- pre_compression: fired just before context compression begins.
  Return {"skip": True, "reason": str} to veto compression for
  this tick. Side-effect hooks (e.g. rebinding a shared context
  engine) may run without returning (NousResearch#58512).

Changes:
- hermes_cli/plugins.py: add four hooks to VALID_HOOKS with docs
- hermes_state.py: fire pre_db_checkpoint in close() and pre-VACUUM
- hermes_state_search.py: fire pre_db_checkpoint in optimize VACUUM
- agent/agent_runtime_helpers.py: fire pre_fuzzy_repair before fuzzy
- tools/delegate_tool.py: fire pre_delegation_credentials at entry
- agent/conversation_compression.py: fire pre_compression before start
- tests/hermes_cli/test_extension_hooks.py: 26 tests covering all
  four hooks (default behavior, override, error fallback, kwargs)

Validation:
- python -m pytest tests/hermes_cli/test_extension_hooks.py → 26 passed
- python -m pytest tests/test_wal_checkpoint_strategy.py
  tests/run_agent/test_repair_tool_call_name.py
  tests/hermes_cli/test_plugins.py
  tests/tools/test_delegate.py::TestDelegationCredentialResolution
  tests/run_agent/test_message_sequence_repair.py → 95 passed
- python -m py_compile on all six modified files → OK
- Pre-existing failures (No module named 'openai') confirmed identical
  on unmodified upstream/main — not caused by this change.
…ssion tests

- Validate checkpoint mode against (PASSIVE, FULL, RESTART, TRUNCATE);
  invalid values keep the TRUNCATE default (all 3 call sites)
- Vacuum tests now use SessionDB.vacuum() directly instead of
  optimize_fts_storage() — deterministic, no conditional guard
- Added invalid-mode rejection test (SQL injection attempt → TRUNCATE)
- Compression tests now exercise compress_context() path-level:
  veto returns messages unchanged + compressor never called;
  no-plugin and hook-error paths confirm compressor IS reached
@TurgutKural
TurgutKural force-pushed the feat/plugin-extension-hooks branch from e3193bb to ccdaf7f Compare August 5, 2026 10:26
@TurgutKural TurgutKural closed this Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit 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 sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state tool/delegate Subagent delegation type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants