Skip to content

Feature: Automatic Session Title Generation — Lightweight LLM-Based Title After First Exchange (inspired by DeerFlow) #624

@teknium1

Description

@teknium1

Overview

DeerFlow (ByteDance's open-source SuperAgent harness, github.com/bytedance/deer-flow) implements automatic conversation title generation via a dedicated TitleMiddleware. After the first complete user+assistant exchange, a lightweight LLM call generates a concise title for the session. This is a small but impactful UX improvement that Hermes currently lacks entirely — sessions are identified only by timestamps and platform keys.

This becomes critical as Hermes builds toward a Web UI (#501), session search (existing session_search tool), and better session management. Users currently cannot quickly identify sessions in session_search results or when browsing stored transcripts.


Research Findings

How DeerFlow Does It

DeerFlow's TitleMiddleware (backend/src/agents/middlewares/title_middleware.py) runs as an after_agent hook with these specifics:

Trigger conditions — title is generated only when:

  1. No title exists yet (state.get("title") is falsy)
  2. Exactly 1 user message AND at least 1 assistant message in the thread
  3. This ensures title generation happens after the first complete exchange, not on every turn

Implementation pattern:

def _should_generate_title(self, state):
    if state.get("title"): return False  # already has title
    user_messages = [m for m in messages if m.type == "human"]
    assistant_messages = [m for m in messages if m.type == "ai"]
    return len(user_messages) == 1 and len(assistant_messages) >= 1

LLM call — uses a cheap/fast model (configurable via TitleConfig) with a focused prompt:

title_config = get_title_config()
model = create_chat_model(title_config.model or default_model)
# Short system prompt asking for a 3-8 word title
# Input: first user message + first assistant response

Graceful fallback — if the LLM call fails, falls back to truncating the user message:

except Exception:
    title = user_message_text[:50] + ("..." if len(user_message_text) > 50 else "")

Configuration (title section in config.yaml):

title:
  enabled: true
  model: null  # null = use default model; or specify a fast/cheap model

Key Design Decisions

  1. One-shot, not continuous — title is generated exactly once per thread, not updated as conversation evolves
  2. Cheap model — can use a different (faster/cheaper) model than the main agent model
  3. Non-blocking — failure doesn't affect the main agent response
  4. Post-response — runs after the agent finishes, so it doesn't add latency to the user-visible response

Current State in Hermes Agent

Hermes sessions are identified by deterministic keys like agent:main:telegram:dm or agent:main:cli. The SessionDB (hermes_state.py) stores sessions with fields including session_key, started_at, last_activity, and metadata — but no title field.

session_search results currently return session timestamps and matched content snippets, but no human-readable title. This makes it hard to quickly identify what a past session was about.

The SessionStore (gateway/session.py) manages session lifecycle but also has no title concept.

Relevant files:

  • hermes_state.pySessionDB, SQLite schema with sessions + messages tables
  • gateway/session.pySessionStore, SessionEntry, session key generation
  • agent/context_compressor.py — has the summarization LLM call pattern we could reuse

Implementation Plan

Classification

This is a core codebase change, not a skill or tool. It touches the session storage layer, the agent loop, and potentially the gateway. The LLM call pattern already exists in context_compressor.py and can be reused.

What We'd Need

  1. Add title column to sessions table in hermes_state.py (ALTER TABLE migration)
  2. Title generation function (reuse the auxiliary LLM client pattern from context_compressor.py)
  3. Hook into the agent loop to trigger title generation after first exchange
  4. Store title in SessionDB
  5. Surface titles in session_search results and /sessions command output

Phased Rollout

Phase 1: Core title generation

  • Add title TEXT column to sessions table with migration
  • Add generate_title() function using the summarization/auxiliary model
  • Call it after the first complete turn in run_agent.py or gateway message handler
  • Fallback to truncated first user message on failure
  • Config: title_generation_enabled (default true), title_generation_model (optional override)

Phase 2: Surface titles everywhere

  • Include titles in session_search results
  • Show titles in /sessions command output
  • Include in session metadata for gateway platforms
  • Show in CLI session listing

Phase 3: Web UI integration


Pros & Cons

Pros

Cons / Risks

  • Small additional latency on first turn (mitigated by running post-response)
  • Extra LLM call cost (mitigated by using cheap model + only once per session)
  • Schema migration needed for existing sessions (simple ALTER TABLE)

Open Questions

  • Should the title be updatable (e.g., if the conversation topic shifts significantly)?
  • Should there be a /title command to manually set/override the title?
  • What model to default to for title generation? (Could reuse CONTEXT_COMPRESSION_MODEL config)

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions