diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c9834c36..55884d71 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -123,7 +123,7 @@ Environment variables controlling behavior: HERMES_WEBUI_PASSWORD Optional: enable password auth (off by default) HERMES_WEBUI_SKIP_ONBOARDING Optional: bypass the first-run onboarding wizard HERMES_PREFILL_MESSAGES_FILE Optional JSON message list for browser-turn prefill context - HERMES_WEBUI_PREFILL_MESSAGES_SCRIPT Optional command that prints JSON messages or text prefill context + HERMES_WEBUI_PREFILL_MESSAGES_SCRIPT Optional command that prints JSON messages or plain-text user prefill context HERMES_WEBUI_PREFILL_MESSAGES_SCRIPT_TIMEOUT Optional script timeout in seconds (default 5, max 30) HERMES_HOME Base directory for Hermes state (~/.hermes by default) diff --git a/CHANGELOG.md b/CHANGELOG.md index c365129f..dc1737b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## [Unreleased] +### Fixed + +- Plain-text `webui_prefill_messages_script` output is now wrapped as a `user` prefill message instead of an extra `system` message, keeping dynamic recall context provider-safe while preserving explicit JSON message roles. + ## [v0.51.145] — 2026-05-26 — Release DQ (stage-batch27 — sidebar running-state preservation) ### Fixed diff --git a/README.md b/README.md index 7120dfa7..903c4b6d 100644 --- a/README.md +++ b/README.md @@ -154,10 +154,11 @@ HERMES_WEBUI_PREFILL_MESSAGES_SCRIPT_TIMEOUT=5 \ ``` The script may print either an OpenAI-style JSON message list, a JSON object with -a `messages` list, or plain text; plain text is wrapped as one `system` prefill -message. Script output is capped at 256 KiB before parsing. The browser only -receives a compact status event (`source`, `label`, message count, and redacted -errors), never the prefill message bodies. +a `messages` list, or plain text; plain text is wrapped as one `user` prefill +message so dynamic recall text becomes ordinary context instead of an extra +system instruction. Script output is capped at 256 KiB before parsing. The +browser only receives a compact status event (`source`, `label`, message count, +and redacted errors), never the prefill message bodies. The bootstrap will: diff --git a/api/streaming.py b/api/streaming.py index a77a442c..bd640ae9 100644 --- a/api/streaming.py +++ b/api/streaming.py @@ -340,7 +340,7 @@ def _messages_from_prefill_script_output(text: str) -> list[dict]: messages = _valid_prefill_messages(payload) if messages: return messages - return [{"role": "system", "content": stripped}] + return [{"role": "user", "content": stripped}] def _load_prefill_messages_script(config_data: dict) -> dict: diff --git a/tests/test_webui_prefill_context.py b/tests/test_webui_prefill_context.py index 26b9aca9..9c2b4aad 100644 --- a/tests/test_webui_prefill_context.py +++ b/tests/test_webui_prefill_context.py @@ -70,7 +70,7 @@ def test_webui_prefill_script_loads_json_messages(tmp_path): assert result["messages"] == [{"role": "system", "content": "Joplin has durable context; use notes/search tools for details."}] -def test_webui_prefill_script_wraps_plain_text_for_any_notes_source(tmp_path): +def test_webui_prefill_script_wraps_plain_text_as_user_context(tmp_path): from api.streaming import _load_webui_prefill_context script = tmp_path / "obsidian_recall.py" @@ -80,7 +80,7 @@ def test_webui_prefill_script_wraps_plain_text_for_any_notes_source(tmp_path): assert result["status"] == "loaded" assert result["source"] == "script" - assert result["messages"] == [{"role": "system", "content": "Obsidian project note context"}] + assert result["messages"] == [{"role": "user", "content": "Obsidian project note context"}] def test_webui_prefill_script_errors_are_redacted(tmp_path): @@ -111,7 +111,7 @@ def test_webui_prefill_script_takes_precedence_over_static_file(tmp_path): }) assert result["source"] == "script" - assert result["messages"] == [{"role": "system", "content": "dynamic"}] + assert result["messages"] == [{"role": "user", "content": "dynamic"}] def test_webui_prefill_script_timeout_returns_redacted_error(tmp_path):