Skip to content

Show Agent Sessions should respect agent session relationships #1004

Description

@franksong2702

Summary

Show Agent Sessions currently merges Hermes Agent / CLI / Gateway rows from state.db into the WebUI sidebar as flat session entries.

That works for simple sessions, but Hermes Agent session rows can have different relationship semantics:

  • empty/unimportable rows that have a sessions record but no messages
  • compression continuation chains, where one long user conversation is split into multiple session segments linked by parent_session_id
  • subagent delegation children, where a parent task intentionally spawns child sessions that should remain visible as a tree

Today the WebUI bridge does not distinguish these cases. It treats every non-webui row as a standalone conversation, which can make the sidebar confusing or broken when Show Agent Sessions is enabled.

Observed example

With Show Agent Sessions enabled, a Weixin conversation produced many sidebar entries like:

  • Magazine Style PPT Skill #91
  • Magazine Style PPT Skill #88
  • Magazine Style PPT Skill #76

Inspecting the local Hermes Agent state.db showed this was not simply duplicate data. These rows form a continuation chain through parent_session_id:

Magazine Style PPT Skill
  -> Magazine Style PPT Skill #2
  -> ...
  -> Magazine Style PPT Skill #92 / #94

Many intermediate rows have end_reason = 'compression'. From the user's point of view this is one ongoing messaging conversation, but the WebUI sidebar exposes the backend compression segments as separate sessions.

There were also some empty rows with no message records. Those appear in the sidebar but cannot be imported, causing Session not found in CLI store when clicked.

Why this is not Weixin-specific

This surfaced with Weixin because that is where I could reproduce it locally, but the underlying model is shared by Hermes Agent:

  • hermes_state.py stores parent_session_id and explicitly supports compression-triggered session splitting.
  • gateway.session.build_session_key() is the shared gateway session key mechanism used by platforms such as Telegram, Weixin, Discord, Matrix, etc.
  • Hermes Agent's own SessionDB.list_sessions_rich() defaults to include_children=False and project_compression_tips=True, which treats compression continuations as one logical conversation projected to the latest tip.

The WebUI bridge currently bypasses that logical projection and directly queries raw sessions rows.

Current WebUI paths affected

Two WebUI paths need to stay in sync:

  1. /api/sessions via api.models.get_cli_sessions()

    • currently reads raw non-webui rows from state.db
    • applies LIMIT 200 before any logical conversation projection
  2. Gateway SSE watcher via api.gateway_watcher._get_agent_sessions_from_db()

    • also reads raw non-webui rows
    • can reintroduce the same flattened list during live refresh even if /api/sessions is fixed separately

Related issues / PRs

Proposed implementation plan

I think this should be split into two PRs for this issue.

PR 1: Hide empty/unimportable agent session rows

Scope:

  • Filter out agent sessions that have no readable message rows.
  • Prevent broken sidebar entries that fail during /api/session/import_cli.
  • Keep this narrow: do not collapse parent_session_id chains yet.

Expected behavior:

  • A sessions row with source != 'webui' but zero messages rows should not appear in the sidebar.
  • Existing valid agent sessions should continue to appear.

Suggested tests:

  • Add a regression test where a non-webui session row exists with no messages and verify /api/sessions does not return it.

This PR should reference this issue but should not close it.

PR 2: Collapse compression continuation chains into logical conversations

Scope:

  • Use parent_session_id and end_reason = 'compression' to identify continuation chains.
  • Display one sidebar item per logical compression chain, projected to the latest non-empty tip.
  • Apply the same projection in both:
    • api.models.get_cli_sessions()
    • api.gateway_watcher._get_agent_sessions_from_db()
  • Apply the final list limit after filtering/collapsing, not before.
  • Keep import/open behavior pointed at the latest real session id; do not create a fake merged WebUI session.

Expected behavior:

  • A long Weixin/Telegram/Cron/CLI conversation split by context compression should show as one logical sidebar entry.
  • Clicking the entry imports/opens the latest real continuation segment.
  • Empty intermediate continuation rows are skipped.
  • Independent sessions remain independent.

Suggested tests:

  • A compression chain with multiple non-empty children returns only the latest non-empty tip.
  • A chain with empty intermediate nodes still resolves to the latest non-empty usable node.
  • Unrelated sessions are not collapsed together.
  • The gateway watcher emits the same logical session shape as /api/sessions.

This PR can close this issue if the issue scope remains limited to empty rows + compression continuation projection.

Explicit non-goals for this issue

Product principle

Show Agent Sessions should expose useful agent conversations, not raw backend storage rows.

Different parent_session_id relationships need different UI treatment:

Suggested triage metadata

suggested metadata:

  • Labels: bug, cli-parity, gateway, ux
  • Milestone: Milestone 1 — Core quality & parity

Metadata

Metadata

Assignees

No one assigned

    Labels

    cli-parityIssues about matching Hermes CLI behavior in the WebUIenhancementNew feature or requestgatewayGateway sessions, CLI session sync, SSE watcher

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions