Skip to content

fix(api): persist compressed transcripts in ResponseStore to stop re-compression loops - #69306

Merged
teknium1 merged 8 commits into
mainfrom
salvage/58133-responses-store
Jul 22, 2026
Merged

fix(api): persist compressed transcripts in ResponseStore to stop re-compression loops#69306
teknium1 merged 8 commits into
mainfrom
salvage/58133-responses-store

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

/v1/responses now persists the compressed transcript (and the compression-rotated session id) in the ResponseStore, so chained previous_response_id requests stop re-compressing the same bloated history every turn. Root cause: _response_messages_turn_start_index does exact prefix equality, so a compressed transcript never matches the stored history and the fallback concatenated prior + current + agent messages — bloating the snapshot and re-triggering compression on every subsequent request, while the store also kept the pre-rotation session_id.

Changes

  • gateway/platforms/api_server.py:
    • _run_agent detects compression via the two authoritative signals — session rotation (agent.session_id change) and in-place compaction (agent._last_compaction_in_place) — and sets result["_compressed"].
    • _build_response_conversation_history uses _compressed to store the compressed transcript directly instead of concatenating the uncompressed prior history on front (single persist site for both streaming and non-streaming paths).
    • Streaming and non-streaming handlers persist the effective (possibly rotated) session id in the ResponseStore and X-Hermes-Session-Id, so chaining resumes the child session instead of the pre-rotation parent.
  • tests/gateway/test_api_server.py: regression tests for compressed-transcript storage, rotation and in-place fake-agent detection paths, and two-request chained rotation propagation.

Rework applied during salvage: removed the compression.persist_in_response_store config opt-out (bug fixes don't get opt-outs), removed the per-request load_config() imports, and deduped the two handler-level persist blocks into the existing _build_response_conversation_history _compressed check.

Validation

Before After
Stored history after compression prior + compressed concatenated (bloats, re-compresses every turn) compressed transcript only
Stored session id after rotation pre-rotation parent session rotated child session (store + X-Hermes-Session-Id)

Targeted tests: tests/gateway/test_api_server*.py — 394 passed, 0 failed (includes the 4 new regression tests); tests/hermes_cli/test_config.py, test_config_validation.py, tests/run_agent/test_in_place_compaction.py — 218 passed.

Credit

Salvaged from #58133 by @LiangYang666. The same root cause was first identified and fixed by @xiaolongzhao2015 in #41700 (Jun 8) and independently by @izumi0uu in #56933 — first-submitter credit to both. Also fixes the bug reported in #56996 by @ZzzSimon and #58118 by @tianma-if. Fixes #56895.

Infographic

responses-store-compression

LiangYang666 and others added 8 commits July 22, 2026 04:57
Compression produces a compact transcript in result['messages'],
but _build_response_conversation_history detected a prefix mismatch
and concatenated the original conversation_history on front.

Detect compression via _last_compaction_in_place / session_id
rotation and signal through result['_compressed'] so the builder
uses the compressed transcript directly.
- Detect when history is loaded from response_store (via previous_response_id)
- Add history_from_store parameter to distinguish history source
- When compression occurs, persist compressed messages instead of original
- Add persist_in_response_store config option (default True)
- Update session_id and response headers to reflect session rotation

Cherry-picked from alidev 2eb816f6b
The persist logic only checked _result_sid != session_id (rotation),
missing in-place mode where session_id is unchanged but _compressed
flag is set. response_store history doubled every turn (11->26->55->110->225)
causing repeated re-compression.

Fix: detect compression via _did_compress or _rotated, and only update
_effective_session_id on actual rotation (not in-place).

Note: preflight loop break (turn_context.py) from original commit
eee64097a is excluded — it's an optimization, not a bug fix.

Cherry-picked from alidev eee64097a (api_server.py only)
Address review feedback (PR #58133): the original test mocked _run_agent
with _compressed=True directly, bypassing the detection logic.

New tests mock _create_agent instead, so _run_agent's detection path
runs naturally and reads agent.session_id / _last_compaction_in_place:

1. test_rotation_compression_exercises_detection_and_persists_rotated_session_id
   - Fake agent with rotated session_id -> verifies _compressed is set,
     compressed history is stored, and rotated session_id propagates to
     both response_store and X-Hermes-Session-Id header.

2. test_inplace_compression_exercises_detection_and_persists_compressed_history
   - Fake agent with _last_compaction_in_place=True, session_id unchanged
     -> verifies _compressed is set, compressed history is stored, and
     session_id does NOT rotate.

3. test_chained_rotation_propagates_effective_session_id
   - Two-request chain: first request triggers rotation, second request
     loads history using the rotated session_id stored by the first.
     Asserts the compressed transcript is loaded correctly for chaining.
…g opt-out

Rework on top of the salvaged #58133 commits:

- Remove the compression.persist_in_response_store config key — this is
  a bug fix (stored transcripts must reflect what the agent will actually
  replay), not behavior that should be opt-out-able.
- Drop the per-request load_config() imports the handler-level persist
  blocks added.
- Dedupe the two handler-level persist blocks: the compressed-transcript
  substitution already lives in _build_response_conversation_history
  (via result["_compressed"]), so the handlers only need to propagate
  the effective (possibly rotation-changed) session_id. The streaming
  path does this via a new session_id_snapshot arg on
  _persist_response_snapshot; the non-streaming path picks up
  result["session_id"] directly.
- Rotation propagation no longer gates on history-from-store: the first
  request in a chain can also rotate, and its stored session_id must be
  the child session or the next previous_response_id request resumes the
  pre-rotation session and re-compresses every turn.
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

running on 2c313a8

looks good to me!

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery provider/openai OpenAI / Codex Responses API sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 22, 2026
@teknium1
teknium1 merged commit 1c21e96 into main Jul 22, 2026
39 checks passed
@teknium1
teknium1 deleted the salvage/58133-responses-store branch July 22, 2026 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists provider/openai OpenAI / Codex Responses API 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.

/v1/responses enters repeated compression loop after context exceeds model limit

3 participants