fix(api): strip system_prompt/model_config from session list rows - #143
Conversation
GET /api/sessions and GET /api/profiles/sessions returned every row with
the fully rendered system_prompt — tens of KB per row (~34KB average
across 5,965 rows on a real install, 201MB total in the DB). A 21-row
sidebar page weighed 524KB, of which 96% was system_prompt that no list
consumer reads: the desktop's SessionInfo type doesn't declare the field,
the web UI never touches it, and the desktop command palette fetches 200
rows at a time (~6.8MB per open). The sidebar re-fetches the list on
every message.complete, so this dead weight was paid constantly.
Apply a list projection at both endpoints: rows omit system_prompt and
model_config unless the caller passes ?full=1 (escape hatch for scripts
that want complete rows). Detail reads (GET /api/sessions/{id} and the
messages endpoint) are untouched, as are direct list_sessions_rich()
callers (CLI session browse, TUI) — the strip happens only at the HTTP
list layer.
Measured on the same install: 524,162 -> 20,395 bytes for the 50-row
sidebar fetch (25.7x smaller); ?full=1 returns the previous payload
byte-for-byte.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🔎 Lint report:
|
|
Consumer sweep note: the gateway's client API ( |
|
Tests gate — baseline comparison (fork Tests workflow is pre-existing red on
Blocking checks green: ruff enforcement, ruff + ty diff, e2e, Windows footguns, attribution, common-ancestor, supply-chain, nix (ubuntu). nix (macos) hits the tracked every-PR sticky-comment 401 ( |
Why
GET /api/sessionsandGET /api/profiles/sessionsreturn every row with the fully renderedsystem_prompt— tens of KB per row (~34KB average across 5,965 rows on a real install; 201MB total in that DB). Concretely, a 21-row sidebar page weighed 524KB, 96% of itsystem_promptthat no list consumer reads:SessionInfotype doesn't even declaresystem_prompt/model_config, andgrepfinds zero references inapps/desktop/srcandweb/src;message.complete;What changed
A list projection at the two list endpoints: rows omit
system_promptandmodel_configunless the caller passes?full=1(escape hatch for external scripts that want complete rows). Implemented as one module-level helper (_strip_session_list_rows) applied after row assembly inget_sessionsand to the merged window inget_profiles_sessions.Deliberately scoped to the HTTP list layer:
GET /api/sessions/{id}and the messages endpoint are untouched (detail reads stay complete).list_sessions_rich()callers (CLI session browse, TUI, tips) are untouched./api/sessions/searchbuilds its own result shape and is unaffected.Evidence
Same live install, 50-row sidebar fetch (
limit=50&min_messages=1&order=recent):/api/profiles/sessionssame shape: 21,490 bytes lean, heavy fields absent, profile tagging intact.Testing
tests/hermes_cli/test_web_server.py: default strip on both endpoints (light fields survive),?full=1round-tripssystem_prompt/model_config.tests/gateway/test_session_api.py,test_web_server_session_search.py,test_dashboard_auth_middleware.py): 38/38 pass.ruff checkclean on both changed files.Risks
system_promptfrom LIST responses (rather than the detail endpoint) would need?full=1. No in-repo consumer does.