Files
hermes-agent/hermes_state_session_list.py
Teknium 349f919c5f fix(sessions): pin the page-first list contract, keep the facade under its cap
Adds the invariant test for #119403: a recency page may rank every admitted
row, but each row outside the page must not also pay the projection (s.*,
resolved prompt, preview subquery over message content). Measured as SQLite
VM steps per extra admitted row at LIMIT 1 versus per projected row: base
413 vs 479 (red), head 143 vs 479.

Moves the compression-tip page projection into hermes_state_session_list.py
so hermes_state_sessions.py stays under 2,000 lines after #125515's page CTE,
and drops the one-off lineage benchmark script from the salvaged branch (its
numbers are in the PR body).
2026-10-27 07:26:36 -07:00

48 lines
2.4 KiB
Python

"""Session-list page projection for ``SessionDB.list_sessions_rich``."""
from typing import Any
def project_compression_tips(db, sessions: list[dict[str, Any]], compact_rows: bool) -> list[dict[str, Any]]:
"""Replace each compression root's surfaced fields with its live tip's (root ``started_at`` kept
for stable ordering), one batched query. ``_lineage_ids`` carries every chain id (a tile may
hold a MIDDLE segment's id)."""
chain_by_root: dict[str, list[str]] = {} # only roots whose tip differs from themselves
for s in sessions:
if s.get("end_reason") == "compression":
chain = db.get_compression_chain(s["id"])
if chain and chain[-1] != s["id"]:
chain_by_root[s["id"]] = chain
tip_rows = (
db._get_session_rich_rows_batch(
{chain[-1] for chain in chain_by_root.values()}, compact_rows=compact_rows,
) if chain_by_root else {}
)
projected = []
for s in sessions:
chain = chain_by_root.get(s["id"])
tip_row = tip_rows.get(chain[-1]) if chain else None
if not tip_row:
projected.append(s)
continue
merged = dict(s)
for key in (
"id", "ended_at", "end_reason", "message_count", "tool_call_count", "title", "last_active",
"preview", "model", "system_prompt", "cwd", "git_branch", "git_repo_root",
):
if key in tip_row:
merged[key] = tip_row[key]
if merged.get("title") is None:
# The title is carried root->tip AFTER the publish transaction; a rotation cut off in
# between leaves it on the ended root, and exact-title lookups (`hermes peer dm` ->
# canonical "Bot Chat") must still see the lineage under its name (#106165).
merged["title"] = s.get("title")
merged["_lineage_root_id"] = s["id"]
merged["_lineage_ids"] = chain
# #121148: the projected row IS an automatic continuation, not a fresh conversation and
# not a user branch. The server already classifies the edge (_COMPRESSION_CHILD_SQL) but
# only ever used it to hide rows — surface the kind so clients can label the provenance
# instead of rendering a sealed-and-rotated chat as a brand-new session.
merged["continuation_kind"] = "compression"
projected.append(merged)
return projected