Files
hermes-agent/tests/agent/test_title_input.py
Ritvik Gautam 6776a429b5 fix: use original gateway text for automatic session titles
Gateway turns reach the agent enriched with channel-bound skill payloads,
sender metadata and other model context, so the first user message the
titler saw could be thousands of characters of skill text ahead of the
actual question, and new sessions were named after the skill instead of
the request.

The gateway now captures the inbound event text before that enrichment and
passes it as a turn-local title_user_message through TurnContext, the turn
runner, AIAgent.run_conversation and the conversation prologue. Only the
titler reads it: the main model, persisted history and relay metadata still
receive the full enriched message. An explicit empty or whitespace override
suppresses titling for that turn, None keeps the existing model-facing
fallback (attachment-only turns, paste previews), and the override cannot
enable model titling for cron or subagent runs. The value is a per-call
argument, so a cached agent never reuses a previous turn's override.

Rebased onto current main: the gateway call keeps main's pinned channel
prompt for internal events, and the integration fixture's context-prompt
stub accepts main's internal= keyword.

Ritvik Gautam made this change with Doryani AI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:57:52 -05:00

74 lines
3.3 KiB
Python

"""Title-only input must not change history, eligibility, or fallback behavior."""
from copy import deepcopy
from types import SimpleNamespace
from unittest.mock import Mock
import pytest
from agent.turn_context import _maybe_title_session_at_turn_start
@pytest.mark.parametrize("override, content, expected", [
(None, "Ordinary request", "Ordinary request"),
("Actual question", "Injected model context", "Actual question"),
(" Actual question ", "Injected model context", "Actual question"),
("", "Injected model context", None),
(" ", "Injected model context", None),
(None, [{"type": "text", "text": "Image caption"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,AA=="}}], "Image caption"),
(None, [{"type": "image_url", "image_url": {"url": "data:image/png;base64,AA=="}}], None),
])
def test_title_input_override_is_optional_and_history_is_unchanged(monkeypatch, override, content, expected):
agent = SimpleNamespace(_session_db=Mock(), session_id="test", _session_db_created=True)
messages = [{"role": "user", "content": content}]
original = deepcopy(messages)
title = Mock()
monkeypatch.setattr("agent.title_generator.maybe_auto_title", title)
_maybe_title_session_at_turn_start(agent, messages, override)
assert messages == original
if expected is None:
title.assert_not_called()
else:
assert title.call_args.args[2] == expected
assert title.call_args.kwargs["conversation_history"] is messages
@pytest.mark.parametrize("override, expected", [(None, "attachment"), ("Original question", "Original question")])
def test_title_override_preserves_upstream_paste_preview(monkeypatch, override, expected):
agent = SimpleNamespace(_session_db=Mock(), session_id="test", _session_db_created=True)
messages = [{"role": "user", "content": "attachment",
"display_metadata": {"title_preview": "pasted topic"}}]
original = deepcopy(messages)
title = Mock()
monkeypatch.setattr("agent.title_generator.maybe_auto_title", title)
_maybe_title_session_at_turn_start(agent, messages, override)
assert title.call_args.args[2] == expected
assert title.call_args.kwargs["title_preview"] == "pasted topic"
assert title.call_args.kwargs["conversation_history"] is messages
assert messages == original
@pytest.mark.parametrize("text, titleable", [("/help", True), ("[CONTEXT COMPACTION] synthetic handoff", False)])
def test_title_override_preserves_existing_control_message_eligibility(monkeypatch, text, titleable):
agent = SimpleNamespace(_session_db=Mock(), session_id="test", _session_db_created=True)
start = Mock()
monkeypatch.setattr("agent.memory_provider.spawn_context_thread", start)
_maybe_title_session_at_turn_start(agent, [{"role": "user", "content": "injected context"}], text)
assert start.called is titleable
@pytest.mark.parametrize("platform", ["cron", "subagent", "CRON"])
def test_title_override_cannot_enable_titling_for_machine_runs(monkeypatch, platform):
agent = SimpleNamespace(_session_db=Mock(), session_id="test", platform=platform)
title = Mock()
monkeypatch.setattr("agent.title_generator.maybe_auto_title", title)
_maybe_title_session_at_turn_start(agent, [], "Actual question")
title.assert_not_called()