perf(state): add compact_rows to skip system_prompt blob in session list queries

list_sessions_rich and _get_session_rich_row previously used SELECT s.*,
pulling the system_prompt TEXT blob on every row even for dashboard and
picker callers that never display it. On large databases this blob routinely
runs to tens of kilobytes per session, causing unnecessary B-tree I/O.

Add compact_rows=False param to both functions. When True, an explicit
column list omitting system_prompt is substituted for s.* in both the
simple and the recursive-CTE (order_by_last_active) query paths.
Default is False so all existing callers are unaffected.

Update dashboard and session-picker callers in web_server.py and
tui_gateway/server.py to pass compact_rows=True.

Add seven regression tests covering: omission of system_prompt, presence
of all metadata fields, both query paths, _get_session_rich_row, and
backward-compat default.

(cherry picked from commit c470cbd304)
This commit is contained in:
CodeForgeNet 2026-06-17 01:54:42 +05:30 committed by kshitij
parent 4df16c429b
commit 22eb1af23a
4 changed files with 98 additions and 9 deletions

View file

@ -2175,7 +2175,6 @@ def _git_path(path: str) -> str:
class GitPathBody(BaseModel):
path: str
class GitFileBody(BaseModel):
path: str
file: Optional[str] = None
@ -3914,6 +3913,7 @@ def get_sessions(
include_archived=include_archived,
archived_only=archived_only,
order_by_last_active=order == "recent",
compact_rows=True,
)
total = db.session_count(
source=source or None,
@ -4032,6 +4032,7 @@ def get_profiles_sessions(
include_archived=include_archived,
archived_only=archived_only,
order_by_last_active=order == "recent",
compact_rows=True,
)
profile_total = db.session_count(
source=source_filter,

View file

@ -2947,6 +2947,21 @@ class SessionDB:
current = child_id
return current
# All sessions columns except the large system_prompt blob, each prefixed
# with the "s" table alias used in list_sessions_rich/_get_session_rich_row
# queries. Used when compact_rows=True to avoid reading the blob for
# dashboard and picker callers that only need lightweight metadata.
_SESSION_COMPACT_COLS = (
"s.id, s.source, s.user_id, s.model, s.model_config, "
"s.parent_session_id, s.started_at, s.ended_at, s.end_reason, "
"s.message_count, s.tool_call_count, s.input_tokens, s.output_tokens, "
"s.cache_read_tokens, s.cache_write_tokens, s.reasoning_tokens, "
"s.cwd, s.billing_provider, s.billing_base_url, s.billing_mode, "
"s.estimated_cost_usd, s.actual_cost_usd, s.cost_status, s.cost_source, "
"s.pricing_version, s.title, s.api_call_count, s.handoff_state, "
"s.handoff_platform, s.handoff_error, s.rewind_count, s.archived"
)
def distinct_session_cwds(self, include_archived: bool = False) -> List[Dict[str, Any]]:
"""Distinct non-empty session cwds with usage stats, for repo discovery.
@ -2988,6 +3003,7 @@ class SessionDB:
archived_only: bool = False,
id_query: str = None,
search_query: str = None,
compact_rows: bool = False,
) -> List[Dict[str, Any]]:
"""List sessions with preview (first user message) and last active timestamp.
@ -3021,6 +3037,12 @@ class SessionDB:
its forward compression chain). A punctuation-stripped variant is also
matched so e.g. ``an94`` finds ``AN-94``. Only honored in the
``order_by_last_active`` path.
Pass ``compact_rows=True`` for dashboard and picker callers that only
need lightweight metadata. This omits the ``system_prompt`` blob from
the SELECT so SQLite never copies it out of the B-tree page a
significant I/O saving on large databases where the blob routinely
runs to tens of kilobytes per row.
"""
where_clauses = []
params = []
@ -3138,6 +3160,7 @@ class SessionDB:
outer_where = (
f"{where_sql} AND {combined}" if where_sql else f"WHERE {combined}"
)
_sel = self._SESSION_COMPACT_COLS if compact_rows else "s.*"
query = f"""
WITH RECURSIVE chain(root_id, cur_id) AS (
SELECT s.id, s.id FROM sessions s {where_sql}
@ -3161,7 +3184,7 @@ class SessionDB:
FROM chain
GROUP BY root_id
)
SELECT s.*,
SELECT {_sel},
COALESCE(
(SELECT SUBSTR(REPLACE(REPLACE(m.content, X'0A', ' '), X'0D', ' '), 1, 63)
FROM messages m
@ -3184,8 +3207,9 @@ class SessionDB:
# only applies to the outer select.
params = params + params + id_params + [limit, offset]
else:
_sel = self._SESSION_COMPACT_COLS if compact_rows else "s.*"
query = f"""
SELECT s.*,
SELECT {_sel},
COALESCE(
(SELECT SUBSTR(REPLACE(REPLACE(m.content, X'0A', ' '), X'0D', ' '), 1, 63)
FROM messages m
@ -3322,13 +3346,17 @@ class SessionDB:
runs.append(s)
return runs
def _get_session_rich_row(self, session_id: str) -> Optional[Dict[str, Any]]:
def _get_session_rich_row(self, session_id: str, compact_rows: bool = False) -> Optional[Dict[str, Any]]:
"""Fetch a single session with the same enriched columns as
``list_sessions_rich`` (preview + last_active). Returns None if the
session doesn't exist.
Pass ``compact_rows=True`` to omit the ``system_prompt`` blob (see
``list_sessions_rich`` for details).
"""
query = """
SELECT s.*,
_sel = self._SESSION_COMPACT_COLS if compact_rows else "s.*"
query = f"""
SELECT {_sel},
COALESCE(
(SELECT SUBSTR(REPLACE(REPLACE(m.content, X'0A', ' '), X'0D', ' '), 1, 63)
FROM messages m

View file

@ -5362,3 +5362,63 @@ def test_refresh_compression_lock_requires_holder_and_preserves_reclaimability(d
monkeypatch.setattr(hermes_state.time, "time", lambda: 1016.0)
assert db.try_acquire_compression_lock("s1", "holder-b", ttl_seconds=10.0) is True
# =========================================================================
# compact_rows — lightweight column projection (issue #47414)
# =========================================================================
class TestCompactRows:
"""list_sessions_rich and _get_session_rich_row with compact_rows=True
must omit system_prompt but return all other metadata fields."""
def _create(self, db, sid, *, system_prompt="big blob " * 500):
db.create_session(session_id=sid, source="cli", model="m")
db.update_system_prompt(sid, system_prompt)
return sid
def test_compact_rows_omits_system_prompt(self, db):
self._create(db, "s1")
rows = db.list_sessions_rich(compact_rows=True)
assert len(rows) == 1
assert "system_prompt" not in rows[0]
def test_full_rows_include_system_prompt(self, db):
self._create(db, "s1", system_prompt="keep me")
rows = db.list_sessions_rich(compact_rows=False)
assert rows[0]["system_prompt"] == "keep me"
def test_compact_rows_preserves_metadata_fields(self, db):
self._create(db, "s1")
rows = db.list_sessions_rich(compact_rows=True)
row = rows[0]
for field in ("id", "source", "model", "started_at", "message_count",
"input_tokens", "output_tokens", "title", "cwd",
"archived", "preview", "last_active"):
assert field in row, f"missing field: {field}"
def test_compact_rows_order_by_last_active(self, db):
"""compact_rows=True also works with the CTE / order_by_last_active path."""
self._create(db, "s1")
self._create(db, "s2")
rows = db.list_sessions_rich(compact_rows=True, order_by_last_active=True)
assert len(rows) == 2
assert all("system_prompt" not in r for r in rows)
def test_get_session_rich_row_compact_omits_system_prompt(self, db):
self._create(db, "s1", system_prompt="should be gone")
row = db._get_session_rich_row("s1", compact_rows=True)
assert row is not None
assert "system_prompt" not in row
assert row["id"] == "s1"
def test_get_session_rich_row_full_includes_system_prompt(self, db):
self._create(db, "s1", system_prompt="stay")
row = db._get_session_rich_row("s1", compact_rows=False)
assert row["system_prompt"] == "stay"
def test_compact_rows_default_is_false(self, db):
"""Default behaviour (compact_rows not passed) is unchanged — full rows."""
self._create(db, "s1", system_prompt="present")
rows = db.list_sessions_rich()
assert "system_prompt" in rows[0]

View file

@ -5314,7 +5314,7 @@ def _(rid, params: dict) -> dict:
fetch_limit = max(limit * 2, 200)
rows = [
s
for s in db.list_sessions_rich(source=None, limit=fetch_limit, order_by_last_active=True)
for s in db.list_sessions_rich(source=None, limit=fetch_limit, order_by_last_active=True, compact_rows=True)
if (s.get("source") or "").strip().lower() not in deny
][:limit]
return _ok(
@ -5361,7 +5361,7 @@ def _(rid, params: dict) -> dict:
# users (lots of recent ``tool`` rows) don't get a false
# "no eligible session" answer. ``session.list`` uses a
# similar over-fetch strategy.
rows = db.list_sessions_rich(source=None, limit=200, order_by_last_active=True)
rows = db.list_sessions_rich(source=None, limit=200, order_by_last_active=True, compact_rows=True)
for row in rows:
src = (row.get("source") or "").strip().lower()
if src in deny:
@ -13409,7 +13409,7 @@ def _(rid, params: dict) -> dict:
cutoff = time.time() - days * 86400
rows = [
s
for s in db.list_sessions_rich(limit=500)
for s in db.list_sessions_rich(limit=500, compact_rows=True)
if (s.get("started_at") or 0) >= cutoff
]
return _ok(