Skip to content

fix(api): paginate compacted session history - #69321

Open
saralilyb wants to merge 8 commits into
NousResearch:mainfrom
saralilyb:fix/compacted-session-history
Open

fix(api): paginate compacted session history#69321
saralilyb wants to merge 8 commits into
NousResearch:mainfrom
saralilyb:fix/compacted-session-history

Conversation

@saralilyb

Copy link
Copy Markdown
Contributor

Summary

  • add an opt-in include_compacted message visibility mode that restores
    compression-archived rows without exposing rewound/undone rows
  • add from_end pagination and an additive pagination.total field so clients
    can fetch the newest full-history page without first knowing its total size
  • preserve the existing active-only response when callers omit the new query
    parameters

Why

In-place compaction intentionally retains the original transcript as
active = 0, compacted = 1, but the dashboard messages endpoint only exposed
active rows. Transcript clients therefore reached the post-compaction boundary
and could not paginate to the beginning even though the history remained in the
database. Related to the full-history behavior reported in #32561.

Test plan

  • uv run pytest -q tests/test_hermes_state.py::TestGetMessagesPagination
  • uv run pytest -q tests/hermes_cli/test_web_server.py::TestWebServerEndpoints::test_get_session_messages_follows_compression_tip tests/hermes_cli/test_web_server.py::TestWebServerEndpoints::test_get_session_messages_pages_compacted_history_from_end
  • uv run ruff check hermes_state.py hermes_cli/web_server.py tests/test_hermes_state.py tests/hermes_cli/test_web_server.py
  • exercised the endpoint through FastAPI TestClient against a SQLite backup of
    a real compacted session; the returned tail offset and total matched direct
    SessionDB counts, and rewound rows remained excluded

Platform tested

  • WSL2 / Python 3.13 via uv

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard comp/dashboard Web dashboard / control panel UI (dashboard/, landing) area/compression Context compression and continuation sessions area/sessions Session lifecycle, resume, persistence, history sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 22, 2026
@saralilyb
saralilyb force-pushed the fix/compacted-session-history branch from 00b7c6e to 52950f7 Compare July 22, 2026 14:13
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for preserving the distinction between compaction archives and rewound rows; current main confirms the underlying gap: archive_and_compact() writes archived rows as active=0, compacted=1 (hermes_state.py:5531), while the dashboard endpoint reads only active rows (hermes_cli/web_server.py:11881).

Problems

  • The dashboard does not consume the proposed mode. web/src/pages/SessionsPage.tsx:489 calls getSessionMessages(session.id), and web/src/lib/api.ts:395-398 only builds the base endpoint plus profile, so the dashboard remains on active-only history.
  • The linked mobile change depends on this server contract, but the external Sessions API has the same active-only read at gateway/platforms/api_server.py:3271; this PR changes only the dashboard FastAPI server.
  • The public endpoint docs at website/docs/user-guide/features/web-dashboard.md:454-456 and website/docs/user-guide/features/api-server.md:442-458 do not describe the new parameters or pagination response.

Suggested changes

  • Wire and test the dashboard client, update the external Sessions API consistently, and document the contract.
  • Salvage onto current main's read-connection implementation (hermes_state.py:5613) rather than the older direct-reader context.

Automated hermes-sweeper review.

@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 30, 2026
@saralilyb
saralilyb force-pushed the fix/compacted-session-history branch from 52950f7 to b8f1384 Compare July 30, 2026 17:06
@saralilyb

Copy link
Copy Markdown
Contributor Author

Addressed all requested surfaces on current main's read-connection implementation. The dashboard client requests compacted history, loads the newest 500 messages, and can page backward through the complete transcript. The external Sessions API now exposes the same include_compacted / limit / offset / from_end contract and pagination metadata; both endpoint docs are updated. Rewound rows remain excluded and legacy active-only/unbounded defaults are preserved. Canonical Python run: 257 passed; dashboard TypeScript passed; Vitest: 8 passed. I also preserved the API client's legacy explicit-profile call signature.

@saralilyb

Copy link
Copy Markdown
Contributor Author

Refreshed and reduced against current main at f293e7206b4ddd66042329442c6afebc19a8808d; exact head 3814b049dab9e2ca3f295db527b8125d79c2776c.

Merged #86595 owns the core compacted-history behavior, so this branch no longer duplicates it. The surviving contract adds external Sessions API parity, dashboard backward paging, explicit-profile compatibility, and documentation. A quality review found moving-tail offsets unstable when messages append between requests; the final implementation adds stable before_id keyset pagination and next_before_id metadata across SessionDB, dashboard, and external API, with ID deduplication and offset fallback for older gateways.

Verification on the exact head:

  • focused state/router/gateway matrix: 31 passed;
  • additional cursor/compaction subset: 14 passed;
  • append-between-page regressions cover no duplicates or omissions;
  • TypeScript typecheck and ESLint passed with the available shared toolchain; current-project Vitest/Prettier dependencies were unavailable without installation;
  • Ruff, Python compilation, git diff --check, and Windows-sensitive scan passed.

Rewound rows remain excluded, and legacy active-only/unbounded defaults plus offset/order callers remain compatible. Hosted workflows still require maintainer approval.

@saralilyb

saralilyb commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

CI follow-up: the refreshed head has two unrelated timing failures:

  • Python tests / e2e: test_plaintext_restart_gateway_in_group_stays_plain_text[telegram] did not observe send within its polling window. The branch does not modify that gateway path; the same behavior passed through a direct async probe on this exact head and in the other refreshed branches.
  • JS & TS checks: ui-tui/src/__tests__/virtualHistoryOffsetCache.test.ts did not observe adjustScrollTop after a fixed 40 ms delay. This branch does not modify ui-tui; the branch's own web/src/lib/api.test.ts tests passed all 11 cases in the same job, and strict TypeScript plus ESLint passed locally.

I attempted a failed-job rerun, but GitHub requires repository admin rights. A maintainer rerun should distinguish these apparent timing flakes from persistent failures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/compression Context compression and continuation sessions area/sessions Session lifecycle, resume, persistence, history comp/cli CLI entry point, hermes_cli/, setup wizard comp/dashboard Web dashboard / control panel UI (dashboard/, landing) P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants