feat: auto-generate session titles after first exchange

After the first user→assistant exchange, Hermes now generates a short
descriptive session title via the auxiliary LLM (compression task config).
Title generation runs in a background thread so it never delays the
user-facing response.

Key behaviors:
- Fires only on the first 1-2 exchanges (checks user message count)
- Skips if a title already exists (user-set titles are never overwritten)
- Uses call_llm with compression task config (cheapest/fastest model)
- Truncates long messages to keep the title generation request small
- Cleans up LLM output: strips quotes, 'Title:' prefixes, enforces 80 char max
- Works in both CLI and gateway (Telegram/Discord/etc.)

Also updates /title (no args) to show the session ID alongside the title
in both CLI and gateway.

Implements #1426
This commit is contained in:
teknium1 2026-03-17 04:14:40 -07:00
parent 7049dba778
commit e5fc916814
4 changed files with 321 additions and 6 deletions

View file

@ -3328,12 +3328,12 @@ class GatewayRunner:
except ValueError as e:
return f"⚠️ {e}"
else:
# Show the current title
# Show the current title and session ID
title = self._session_db.get_session_title(session_id)
if title:
return f"📌 Session title: **{title}**"
return f"📌 Session: `{session_id}`\nTitle: **{title}**"
else:
return "No title set. Usage: `/title My Session Name`"
return f"📌 Session: `{session_id}`\nNo title set. Usage: `/title My Session Name`"
async def _handle_resume_command(self, event: MessageEvent) -> str:
"""Handle /resume command — switch to a previously-named session."""
@ -4511,6 +4511,21 @@ class GatewayRunner:
effective_session_id = getattr(agent, 'session_id', session_id) if agent else session_id
# Auto-generate session title after first exchange (non-blocking)
if final_response and self._session_db:
try:
from agent.title_generator import maybe_auto_title
all_msgs = result_holder[0].get("messages", []) if result_holder[0] else []
maybe_auto_title(
self._session_db,
effective_session_id,
message,
final_response,
all_msgs,
)
except Exception:
pass
return {
"final_response": final_response,
"last_reasoning": result.get("last_reasoning"),