fix(dashboard): strip compaction scaffolding from session messages - #91810
Open
pierrenode wants to merge 1 commit into
Open
fix(dashboard): strip compaction scaffolding from session messages#91810pierrenode wants to merge 1 commit into
pierrenode wants to merge 1 commit into
Conversation
agent/compaction_display.py::project_compaction_message_for_display() strips model-only compaction carrier state (handoff boilerplate, inherited tool_calls/reasoning) before conversation history reaches a client. It was wired into tui_gateway/server.py, gateway/run.py, and gateway/platforms/api_server.py (the "hide compaction carriers across surfaces" cluster, NousResearch#91517, merged same day as this window) — but hermes_cli/web_routers/sessions.py, the dashboard's own FastAPI session-messages endpoint, was never touched. grep confirmed zero references to compaction anywhere in that file before this fix. GET /api/sessions/{id}/messages feeds the dashboard's expanded message view (web/src/pages/SessionsPage.tsx and apps/desktop/src/api/sessions.ts both consume it purely for on-screen rendering — verified, neither round-trips it through import). After a session compacts, a row from this endpoint carries the full "[CONTEXT COMPACTION — REFERENCE ONLY] ..." handoff boilerplate (~700 words of internal LLM-steering instructions) plus raw tool_calls/reasoning_content/codex_reasoning_items for merged carriers — exactly the leak the same-day cluster set out to close. Scope note: GET /api/sessions/{id}/export was deliberately NOT touched. Its own docstring on the paired import endpoint says it must stay round-trip-compatible ("Import one or more sessions exported from the dashboard or CLI") — stripping carrier state there would silently corrupt a re-imported session's recovery history. This mirrors the CLI's `hermes sessions export` (a full-fidelity backup tool), which is display_compaction_display's own documented non-target. Fix: reuse project_compaction_message_for_display() via a small local wrapper (_project_dashboard_message) mirroring api_server.py's existing _project_client_message — standalone handoffs become hidden empty rows (not dropped, so pagination row-counts stay stable) rather than raw boilerplate; merged carriers keep only the real prior-tail content. Tests: two new cases against the real FastAPI app (self.client) — scaffolding-stripped-from-messages (asserts hidden/merged/live rows render correctly and the raw marker text never appears), and hidden-row-preserves-pagination-count. A third test locks in the export scope decision as a regression guard (export keeps full carrier fidelity). Mutation-verified: stashed the production fix, the scaffolding-stripping test failed showing the exact raw "[CONTEXT COMPACTION — REFERENCE ONLY]..." text leaking; the pagination-count test correctly still passed (count is fix-independent, a legitimate negative control). Caught during test-writing: my first draft put reasoning_content on a role="user" row — SessionDB only persists reasoning_content/ codex_reasoning_items for role="assistant" rows (verified directly against SessionDB), which would have made those specific assertions vacuous regardless of the fix. Moved to role="assistant", matching how merged/standalone carriers actually arrive in practice. 167 passed, 1 pre-existing skip in the full test_web_server.py file. Could not run the sibling tests/gateway/test_api_server_compaction_ projection.py (same projection mechanism, different surface) in this sandbox — aiohttp lives under optional [messaging]/[slack]/[matrix] extras, not [dev], and isn't installed here; import fails at collection, unrelated to this change. ruff clean. Full web_server app still imports cleanly (311 routes).
Contributor
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
agent/compaction_display.py::project_compaction_message_for_display()strips model-only compaction carrier state (handoff boilerplate, inheritedtool_calls/reasoning fields) before conversation history reaches a client. It was wired intotui_gateway/server.py,gateway/run.py, andgateway/platforms/api_server.pyby the same-day "hide compaction carriers across surfaces" cluster (#91517) — buthermes_cli/web_routers/sessions.py, the dashboard's own FastAPI session-messages endpoint, was never touched.grep -n "compaction" hermes_cli/web_routers/sessions.pyreturned zero hits before this fix, and#91517's own file list (checked viagh pr view --json files) doesn't include this file.GET /api/sessions/{id}/messagesfeeds the dashboard's expanded message view — verified both consumers (web/src/pages/SessionsPage.tsx,apps/desktop/src/api/sessions.ts) use it purely for on-screen rendering. After a session compacts, a row from this endpoint carries the full"[CONTEXT COMPACTION — REFERENCE ONLY] ..."handoff boilerplate (~700 words of internal LLM-steering instructions) plus rawtool_calls/reasoning_content/codex_reasoning_itemsfor merged carriers — exactly the class of leak the same-day cluster set out to close.Scope decision:
/exportintentionally NOT touchedI initially assumed both
get_session_messagesandexport_session_endpointneeded the fix, but checked further before writing it.import_sessions_endpoint's own docstring says: "Import one or more sessions exported from the dashboard or CLI" — the dashboard's/exportis round-trip-compatible with/api/sessions/import. Stripping compaction carrier state from export would silently corrupt a re-imported session's recovery history (losttool_calls/reasoning_content/etc. on the target machine). This mirrors the CLI'shermes sessions export(a full-fidelity backup tool), which the same reasoning already exempts elsewhere in this codebase. I locked this decision in with a regression-guard test (test_export_session_keeps_full_fidelity_compaction_content).Fix
Small local wrapper
_project_dashboard_message()mirroringgateway/platforms/api_server.py's existing_project_client_message(): standalone handoffs become hidden empty rows (not dropped — pagination row-counts stay stable) rather than raw boilerplate; merged carriers keep only the real prior-tail content. Applied to every message returned byget_session_messages.Test plan
test_get_session_messages_strips_compaction_scaffolding: standalone handoff → hidden empty row with no inherited carrier fields; merged carrier → only real prior-tail content survives; a genuinely live message is untouched; raw marker text never appears anywhere in the response.test_get_session_messages_hidden_summary_preserves_pagination_count: a hidden row still counts towardpagination.returned(negative control — passes with or without the fix, since it tests row identity, not content).test_export_session_keeps_full_fidelity_compaction_content: regression guard locking in the export-stays-raw scope decision.reasoning_contenton arole="user"row — verified directly againstSessionDBthatreasoning_content/codex_reasoning_itemsonly persist forrole="assistant"rows, which would have made those specific assertions vacuously true regardless of the fix. Moved torole="assistant", matching how these carriers actually arrive.tests/hermes_cli/test_web_server.py: 167 passed, 1 pre-existing skip (unrelated).tests/test_web_server_sessiondb_eventloop.py: 3/3 passed (neighbor suite touching the same DB-access pattern).hermes_cli.web_server.appstill imports cleanly (311 routes) — no circular-import issue from the new top-levelagent.compaction_displayimport.ruff checkclean on both changed files.tests/gateway/test_api_server_compaction_projection.py(same projection mechanism,api_server.py's surface) in this sandbox:aiohttplives under the optional[messaging]/[slack]/[matrix]extras, not[dev], and isn't installed here — import fails at collection, unrelated to this change.Competing PRs
Checked
gh pr list --searchwith several keyword combinations right before opening this PR. #69321 ("paginate compacted session history") touches the sameget_session_messagesfunction body (textual overlap:include_compacted/from_end/total-count pagination work) — but it's 3 weeks stale relative to currentmain(last updated 2026-07-30, base diff predates theorder/Query(...)validation andinclude_compactedparam that already exist on currentmain,mergeable: UNKNOWN) and orthogonal in intent (pagination semantics vs. compaction-scaffolding stripping). No semantic conflict. #59585/#60878/#85611 were checked and confirmed unrelated (pre-date this projection mechanism or touch different files). #57741 (PII redaction) has zero file overlap.