Skip to content

fix(api): strip system_prompt/model_config from session list rows - #143

Merged
OmarB97 merged 1 commit into
mainfrom
fix/session-list-strip-heavy-fields
Jun 10, 2026
Merged

fix(api): strip system_prompt/model_config from session list rows#143
OmarB97 merged 1 commit into
mainfrom
fix/session-list-strip-heavy-fields

Conversation

@OmarB97

@OmarB97 OmarB97 commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Why

GET /api/sessions and GET /api/profiles/sessions return 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 that DB). Concretely, a 21-row sidebar page weighed 524KB, 96% of it system_prompt that no list consumer reads:

  • the desktop's SessionInfo type doesn't even declare system_prompt/model_config, and grep finds zero references in apps/desktop/src and web/src;
  • the desktop sidebar re-fetches the list on every message.complete;
  • the desktop command palette fetches 200 rows per open (~6.8MB of dead weight);
  • remote-profile splices (fix(desktop): stop dead remote profiles from stalling the session list for 45s #140) ship these rows over tunnels.

What changed

A list projection at the two list endpoints: rows omit system_prompt and model_config unless 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 in get_sessions and to the merged window in get_profiles_sessions.

Deliberately scoped to the HTTP list layer:

  • GET /api/sessions/{id} and the messages endpoint are untouched (detail reads stay complete).
  • Direct list_sessions_rich() callers (CLI session browse, TUI, tips) are untouched.
  • /api/sessions/search builds its own result shape and is unaffected.

Evidence

Same live install, 50-row sidebar fetch (limit=50&min_messages=1&order=recent):

default (lean): HTTP 200 0.031s  20,395 bytes   ← 25.7× smaller
full=1:         HTTP 200 0.033s  524,162 bytes  ← byte-identical to pre-patch
lean rows=21 keys_have_heavy=False
full rows=21 first_has_sp=True

/api/profiles/sessions same shape: 21,490 bytes lean, heavy fields absent, profile tagging intact.

Testing

  • 3 new tests in tests/hermes_cli/test_web_server.py: default strip on both endpoints (light fields survive), ?full=1 round-trips system_prompt/model_config.
  • Full file: 246/246 pass. Adjacent suites (tests/gateway/test_session_api.py, test_web_server_session_search.py, test_dashboard_auth_middleware.py): 38/38 pass.
  • ruff check clean on both changed files.

Risks

  • External scripts that read system_prompt from LIST responses (rather than the detail endpoint) would need ?full=1. No in-repo consumer does.

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>
@github-actions

Copy link
Copy Markdown

🔎 Lint report: fix/session-list-strip-heavy-fields vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 10583 on HEAD, 10583 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 5544 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@OmarB97

OmarB97 commented Jun 10, 2026

Copy link
Copy Markdown
Owner Author

Consumer sweep note: the gateway's client API (gateway/platforms/api_server.py::_session_response) already projects system_prompt/model_config out of its /api/sessions list, exposing only has_system_prompt/has_model_config booleans — "Avoid exposing full system prompts/model_config through the client API". This PR brings the dashboard list endpoints in line with that established projection pattern. Remaining consumers verified unaffected: desktop (SessionInfo doesn't declare the fields, zero grep hits in apps/desktop/src), web UI (zero hits in web/src), CLI/TUI/tools/plugins (use SessionDB.list_sessions_rich() directly, not HTTP).

@OmarB97

OmarB97 commented Jun 10, 2026

Copy link
Copy Markdown
Owner Author

Tests gate — baseline comparison (fork Tests workflow is pre-existing red on main):

  • PR run 27290853502 (head ce5f6af, all 6 slices): 60 unique FAILED entries
  • Baseline run 27289924588 (main @ 84cb42b, this PR's merge-base): 60 unique FAILED entries
  • Set difference both directions: empty — zero new failures, zero vanished.

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 (fork-ci-nix-job-fails-every-pr-at-sticky-pr-comment); its flake check step is unaffected by this Python-only diff.

@OmarB97
OmarB97 merged commit 559b472 into main Jun 10, 2026
17 of 23 checks passed
@OmarB97
OmarB97 deleted the fix/session-list-strip-heavy-fields branch August 2, 2026 16:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant