mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-26 01:01:40 +00:00
feat: activate plugin lifecycle hooks (pre/post_llm_call, session start/end) (#3542)
The plugin system defined six lifecycle hooks but only pre_tool_call and
post_tool_call were invoked. This activates the remaining four so that
external plugins (e.g. memory systems) can hook into the conversation
loop without touching core code.
Hook semantics:
- on_session_start: fires once when a new session is created
- pre_llm_call: fires once per turn before the tool-calling loop;
plugins can return {"context": "..."} to inject into the ephemeral
system prompt (not cached, not persisted)
- post_llm_call: fires once per turn after the loop completes, with
user_message and assistant_response for sync/storage
- on_session_end: fires at the end of every run_conversation call
invoke_hook() now returns a list of non-None callback return values,
enabling pre_llm_call context injection while remaining backward
compatible (existing hooks that return None are unaffected).
Salvaged from PR #2823.
Co-authored-by: Nicolò Boschi <boschi1997@gmail.com>
This commit is contained in:
parent
411e3c1539
commit
455bf2e853
5 changed files with 149 additions and 18 deletions
|
|
@ -226,6 +226,42 @@ class TestPluginHooks:
|
|||
# Should not raise despite 1/0
|
||||
mgr.invoke_hook("post_tool_call", tool_name="x", args={}, result="r", task_id="")
|
||||
|
||||
def test_hook_return_values_collected(self, tmp_path, monkeypatch):
|
||||
"""invoke_hook() collects non-None return values from callbacks."""
|
||||
plugins_dir = tmp_path / "hermes_test" / "plugins"
|
||||
_make_plugin_dir(
|
||||
plugins_dir, "ctx_plugin",
|
||||
register_body=(
|
||||
'ctx.register_hook("pre_llm_call", '
|
||||
'lambda **kw: {"context": "memory from plugin"})'
|
||||
),
|
||||
)
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes_test"))
|
||||
|
||||
mgr = PluginManager()
|
||||
mgr.discover_and_load()
|
||||
|
||||
results = mgr.invoke_hook("pre_llm_call", session_id="s1", user_message="hi",
|
||||
conversation_history=[], is_first_turn=True, model="test")
|
||||
assert len(results) == 1
|
||||
assert results[0] == {"context": "memory from plugin"}
|
||||
|
||||
def test_hook_none_returns_excluded(self, tmp_path, monkeypatch):
|
||||
"""invoke_hook() excludes None returns from the result list."""
|
||||
plugins_dir = tmp_path / "hermes_test" / "plugins"
|
||||
_make_plugin_dir(
|
||||
plugins_dir, "none_hook",
|
||||
register_body='ctx.register_hook("post_llm_call", lambda **kw: None)',
|
||||
)
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes_test"))
|
||||
|
||||
mgr = PluginManager()
|
||||
mgr.discover_and_load()
|
||||
|
||||
results = mgr.invoke_hook("post_llm_call", session_id="s1",
|
||||
user_message="hi", assistant_response="bye", model="test")
|
||||
assert results == []
|
||||
|
||||
def test_invalid_hook_name_warns(self, tmp_path, monkeypatch, caplog):
|
||||
"""Registering an unknown hook name logs a warning."""
|
||||
plugins_dir = tmp_path / "hermes_test" / "plugins"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue