From 4e5703fc628f99bc942ee32dae149e55cb44c82b Mon Sep 17 00:00:00 2001 From: Harendra Pratap Singh Date: Wed, 12 Aug 2026 17:55:52 +0530 Subject: [PATCH 1/2] fix(streaming): inspect run_conversation signature before passing persist_user_timestamp (#6935) --- api/streaming.py | 23 +++++++- ..._issue6935_persist_user_timestamp_kwarg.py | 58 +++++++++++++++++++ 2 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 tests/test_issue6935_persist_user_timestamp_kwarg.py diff --git a/api/streaming.py b/api/streaming.py index ddc25de4..f58de62c 100644 --- a/api/streaming.py +++ b/api/streaming.py @@ -21,6 +21,7 @@ import threading import time import traceback import copy +import inspect from pathlib import Path from typing import Optional @@ -307,6 +308,19 @@ def _install_streaming_cronjob_profile_wrapper() -> None: _STREAMING_CRONJOB_WRAPPER_INSTALLED = True +def _supports_kwarg(func, kwarg_name: str) -> bool: + """Return True if callable `func` accepts `kwarg_name` (explicitly or via **kwargs).""" + try: + sig = inspect.signature(func) + for param in sig.parameters.values(): + if param.kind == param.VAR_KEYWORD or param.name == kwarg_name: + return True + return False + except Exception: + return True + + + _PERSISTENT_MEMORY_FILES = ( ("memory", ("memories", "MEMORY.md")), ("user", ("memories", "USER.md")), @@ -9884,8 +9898,9 @@ def _run_agent_streaming( ), task_id=session_id, persist_user_message=msg_text, - persist_user_timestamp=getattr(s, 'pending_started_at', None), ) + if _supports_kwarg(agent.run_conversation, 'persist_user_timestamp'): + _run_conversation_kwargs['persist_user_timestamp'] = getattr(s, 'pending_started_at', None) # Only pass moa_config when a /moa override is actually active, so a # normal send never trips a TypeError on an older hermes-agent whose # run_conversation() predates the moa_config kwarg. @@ -10387,8 +10402,9 @@ def _run_agent_streaming( ), task_id=session_id, persist_user_message=msg_text, - persist_user_timestamp=getattr(s, 'pending_started_at', None), ) + if _supports_kwarg(agent.run_conversation, 'persist_user_timestamp'): + _heal_kwargs['persist_user_timestamp'] = getattr(s, 'pending_started_at', None) if moa_config is not None: _heal_kwargs["moa_config"] = moa_config _heal_result = agent.run_conversation(**_heal_kwargs) @@ -11627,8 +11643,9 @@ def _run_agent_streaming( ), task_id=session_id, persist_user_message=msg_text, - persist_user_timestamp=getattr(s, 'pending_started_at', None), ) + if _supports_kwarg(_heal_agent.run_conversation, 'persist_user_timestamp'): + _heal_kwargs2['persist_user_timestamp'] = getattr(s, 'pending_started_at', None) if moa_config is not None: _heal_kwargs2["moa_config"] = moa_config _heal_result = _heal_agent.run_conversation(**_heal_kwargs2) diff --git a/tests/test_issue6935_persist_user_timestamp_kwarg.py b/tests/test_issue6935_persist_user_timestamp_kwarg.py new file mode 100644 index 00000000..2a6714dc --- /dev/null +++ b/tests/test_issue6935_persist_user_timestamp_kwarg.py @@ -0,0 +1,58 @@ +""" +Test for Issue #6935: Ensure agent.run_conversation() does not trip a TypeError +when persist_user_timestamp is passed to agent implementations that do not accept it. +""" +from api.streaming import _supports_kwarg + + +class LegacyAgentStub: + """Agent stub mimicking older hermes-agent whose run_conversation does NOT accept **kwargs or persist_user_timestamp.""" + def run_conversation(self, user_message, system_message=None, conversation_history=None, task_id=None, persist_user_message=None): + return {"final_response": "ok", "messages": []} + + +class ModernAgentWithKwargsStub: + """Agent stub whose run_conversation accepts **kwargs.""" + def run_conversation(self, **kwargs): + return {"final_response": "ok", "messages": []} + + +class ModernAgentWithExplicitParamStub: + """Agent stub whose run_conversation explicitly accepts persist_user_timestamp.""" + def run_conversation(self, user_message, persist_user_timestamp=None): + return {"final_response": "ok", "messages": []} + + +def test_supports_kwarg_detection(): + legacy_agent = LegacyAgentStub() + modern_kwargs_agent = ModernAgentWithKwargsStub() + modern_explicit_agent = ModernAgentWithExplicitParamStub() + + assert _supports_kwarg(legacy_agent.run_conversation, "persist_user_timestamp") is False + assert _supports_kwarg(modern_kwargs_agent.run_conversation, "persist_user_timestamp") is True + assert _supports_kwarg(modern_explicit_agent.run_conversation, "persist_user_timestamp") is True + + +def test_legacy_agent_run_conversation_kwargs_filtering(): + legacy_agent = LegacyAgentStub() + kwargs = { + "user_message": "Hello", + "system_message": "System prompt", + "conversation_history": [], + "task_id": "test-session-123", + "persist_user_message": "Hello", + "persist_user_timestamp": 1700000000.0, + } + + if not _supports_kwarg(legacy_agent.run_conversation, "persist_user_timestamp"): + kwargs.pop("persist_user_timestamp", None) + + # Calling run_conversation with filtered kwargs must not raise TypeError + result = legacy_agent.run_conversation(**kwargs) + assert result["final_response"] == "ok" + + +if __name__ == "__main__": + test_supports_kwarg_detection() + test_legacy_agent_run_conversation_kwargs_filtering() + print("All tests passed successfully!") From 7f03b52a0090bb32b0c3a6b31e8eab10ab4b89db Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Wed, 12 Aug 2026 20:51:13 +0000 Subject: [PATCH 2/2] docs(changelog): stamp #6935 persist_user_timestamp kwarg-compat fix Co-authored-by: harsh2hell --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cedf4253..eefa3790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed +- **WebUI no longer crashes a send when running against an older hermes-agent.** The streaming path passed `persist_user_timestamp=` to `agent.run_conversation()` unconditionally, so a WebUI paired with an older agent whose `run_conversation()` predates that keyword raised `TypeError: unexpected keyword argument` and the message failed. The three call sites now check the callable's signature first (via a small `_supports_kwarg` introspection helper) and only pass the timestamp when the agent accepts it — the same defensive pattern already used for `moa_config`. Modern agents are unaffected (the timestamp is still passed with the identical value); older agents degrade gracefully instead of erroring. Thanks @harsh2hell. (#6935) - **`hermes sessions list` no longer shows blank titles for WebUI sessions.** Background title generation wrote the auto-title to the WebUI session sidecar but never to hermes-agent's `state.db`, so the CLI/TUI session list showed those WebUI sessions untitled. The title is now bridged into `state.db` after it's generated (and on adaptive refresh) via `set_auto_title_if_empty`, which respects title provenance — a name you set manually via CLI/Gateway/TUI (`user` source) is never overwritten by the auto-title, an already-auto-titled row stays put, and an existing session's `source` is preserved (the sync creates the row with INSERT-OR-IGNORE semantics). Thanks @liuguangyong93. (#6892) - **No more phantom "Compressing context" barrier after switching sessions.** The compression UI state is per-session, but `loadSession()` did not clear it on a switch, so a compression card from a prior session could leak across the load and appear as a phantom "Compressing context" barrier on a fresh session that never triggered compression. Switching sessions now clears the compression UI (state, elapsed timer, composer session-lock, and barrier element) alongside the other per-session resets, before the transcript loads. A compression genuinely running on the session you switch *to* is unaffected — it re-surfaces from that session's own live event stream (and manual compression from its server job status). Thanks @happy5318. (#6938)