Skip to content

fix(api): persist compressed responses history - #56996

Closed
ZzzSimon wants to merge 1 commit into
NousResearch:mainfrom
ZzzSimon:codex/fix-responses-compression-store
Closed

fix(api): persist compressed responses history#56996
ZzzSimon wants to merge 1 commit into
NousResearch:mainfrom
ZzzSimon:codex/fix-responses-compression-store

Conversation

@ZzzSimon

@ZzzSimon ZzzSimon commented Jul 2, 2026

Copy link
Copy Markdown

What does this PR do?

This PR fixes repeated context compression in the /v1/responses previous_response_id chain after Hermes compacts an agent transcript.

The Responses endpoint stores snapshots in response_store.db so later requests can continue from a previous response. When the agent compressed context, it returned a compacted result["messages"] transcript, but the API server could fail to recognize that transcript as the current turn's canonical history. In same-session compression, returned messages may include internal metadata such as _db_persisted or _compressed_summary, so full dict equality against the current user message failed.

When that boundary detection failed, the snapshot builder reattached the old oversized parent history in front of the compacted transcript. The next previous_response_id request therefore loaded stale history again and triggered compression repeatedly.

This fix:

  • Stores the effective agent session_id returned by _run_agent, so chains follow compression-rotated sessions.
  • Persists the compacted result["messages"] transcript after compression instead of rebuilding from stale parent history.
  • Detects same-session compressed transcripts by matching the current user boundary on only role and content, ignoring internal persistence metadata.
  • Applies the behavior to both non-streaming and streaming /v1/responses paths.
  • Adds regression coverage for rotated-session compression, same-session compression with metadata, and streaming compression snapshots.

Related Issue

Fixes #56895

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Security fix
  • Documentation update
  • Tests (adding or improving test coverage)
  • Refactor (no behavior change)
  • New skill (bundled or hub)

Changes Made

  • gateway/platforms/api_server.py

    • Persist the effective session id returned by the agent result for Responses snapshots and response headers.
    • Prefer the agent-returned compacted transcript when compression changes the effective session.
    • Detect same-session compacted transcripts by scanning for the current user message using only role and content.
    • Apply snapshot persistence changes to both streaming and non-streaming Responses flows.
  • tests/gateway/test_api_server.py

    • Added a regression test for previous_response_id chaining after compression rotates to a new session.
    • Added a regression test for same-session compression where messages contain _db_persisted / _compressed_summary metadata.
    • Added streaming Responses coverage to ensure compressed session/history snapshots are persisted correctly.

How to Test

  1. Run syntax compilation:

    python -m compileall -q gateway\platforms\api_server.py tests\gateway\test_api_server.py
  2. Run targeted Responses regression coverage:

    python -m pytest tests/gateway/test_api_server.py::TestResponsesEndpoint::test_previous_response_id_advances_to_in_place_compressed_history tests/gateway/test_api_server.py::TestResponsesEndpoint::test_previous_response_id_advances_to_compressed_session_and_history tests/gateway/test_api_server.py::TestResponsesStreaming::test_streamed_previous_response_id_advances_to_compressed_session_and_history tests/gateway/test_api_server.py::TestResponsesEndpoint::test_previous_response_id_chaining tests/gateway/test_api_server.py::TestResponsesEndpoint::test_previous_response_id_outputs_only_current_turn_items
  3. Run the same async test methods through a focused manual runner:

    python - <<'PY'
    import asyncio
    from tests.gateway.test_api_server import _make_adapter, TestResponsesEndpoint, TestResponsesStreaming
    
    CASES = [
        (TestResponsesEndpoint(), "test_previous_response_id_advances_to_in_place_compressed_history"),
        (TestResponsesEndpoint(), "test_previous_response_id_advances_to_compressed_session_and_history"),
        (TestResponsesStreaming(), "test_streamed_previous_response_id_advances_to_compressed_session_and_history"),
        (TestResponsesEndpoint(), "test_previous_response_id_chaining"),
        (TestResponsesEndpoint(), "test_previous_response_id_outputs_only_current_turn_items"),
    ]
    
    async def main():
        for instance, name in CASES:
            adapter = _make_adapter()
            await getattr(instance, name)(adapter)
            print(f"PASS {name}")
    
    asyncio.run(main())
    PY

Expected result:

PASS test_previous_response_id_advances_to_in_place_compressed_history
PASS test_previous_response_id_advances_to_compressed_session_and_history
PASS test_streamed_previous_response_id_advances_to_compressed_session_and_history
PASS test_previous_response_id_chaining
PASS test_previous_response_id_outputs_only_current_turn_items

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Windows 11

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) - N/A
  • I've updated cli-config.yaml.example if I added/changed config keys - N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows - N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide
  • I've updated tool descriptions/schemas if I changed tool behavior - N/A

Screenshots / Logs

Targeted verification on Windows:

PASS test_previous_response_id_advances_to_in_place_compressed_history
PASS test_previous_response_id_advances_to_compressed_session_and_history
PASS test_streamed_previous_response_id_advances_to_compressed_session_and_history
PASS test_previous_response_id_chaining
PASS test_previous_response_id_outputs_only_current_turn_items

Store the effective agent session id and compacted result transcript for /v1/responses chaining after compression.

Detect same-session compressed transcripts by matching the current user boundary on role and content so internal persistence metadata does not cause old history to be reattached.
@alt-glitch alt-glitch added type/bug Something isn't working 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 P2 Medium — degraded but workaround exists duplicate This issue or pull request already exists labels Jul 2, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #56933 (izumi0uu) -- both PRs fix #56895 (/v1/responses repeated-compression loop) via the same mechanism at the same code site in gateway/platforms/api_server.py: an effective-session-id helper, persisting the compacted result["messages"] transcript as the next previous_response_id chaining baseline, advancing session_id in the snapshot, across both streaming and non-streaming paths (verified by diffing both). #56933 was opened ~2h earlier and is the canonical entry (it also adds an X-Hermes-Session-Id header). Both are open -- maintainer picks one; this one is marked duplicate for lineage.

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for covering the stale snapshot and effective-session-id paths. The premise is confirmed on current main: gateway/platforms/api_server.py:4001-4013 reattaches prior history when the compacted result no longer has the old exact prefix, and gateway/platforms/api_server.py:3632,3639 persist/return the pre-run session ID despite _run_agent exposing the effective ID at gateway/platforms/api_server.py:4259-4264.

Suggested changes

  • Add a streaming regression for same-session/in-place compression with _db_persisted / _compressed_summary message metadata. Commit 72de5c45af9c tests that metadata boundary in the non-streaming case and tests streaming only for a rotated session.

The current implementation otherwise addresses the verified non-streaming and streaming persistence sites. Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/compression Context compression and continuation sessions labels Jul 15, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Fixed via #69306 (merged), which salvaged #58133 using the same two authoritative signals your PR identified (session rotation + in-place compaction flag). Your metadata-insensitive boundary matching informed the review. Note #56933 by @izumi0uu proposed the same mechanism ~2h before yours — first-submitter credit went there and to #41700. Thanks!

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

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

3 participants