Skip to content

fix(api_server): trust full transcripts via _transcript_mode to stop repair-induced duplication - #75289

Open
LiangYang666 wants to merge 2 commits into
NousResearch:mainfrom
LiangYang666:fix/responses-repaired-transcript-duplication
Open

fix(api_server): trust full transcripts via _transcript_mode to stop repair-induced duplication#75289
LiangYang666 wants to merge 2 commits into
NousResearch:mainfrom
LiangYang666:fix/responses-repaired-transcript-duplication

Conversation

@LiangYang666

Copy link
Copy Markdown

What does this PR do?

Fixes a storage-side duplication bug in /v1/responses when previous_response_id chaining is used together with repair_message_sequence. The stored conversation_history doubles on every chained turn because the repaired transcript no longer matches the exact prefix expected by _response_messages_turn_start_index.

Root cause

AIAgent.run_conversation returns the authoritative full transcript in result["messages"]. Before each LLM call, repair_message_sequence_with_cursor may merge adjacent user/assistant messages in place. The merged transcript no longer shares an exact prefix with the loaded conversation_history, so _response_messages_turn_start_index returns 0 and _build_response_conversation_history falls through to:

full_history = prior
full_history.append(current_user)
full_history.extend(agent_messages)

Because agent_messages already contains the full transcript, this concatenation doubles the stored history every chained turn.

Fix

  • In _run_agent: when result["messages"] is a list, set result["_transcript_mode"] = "full".
  • In _build_response_conversation_history: after the exact-prefix heuristic fails, check result.get("_transcript_mode") == "full" and return agent_messages verbatim.

The existing _compressed flag and exact-prefix detection are kept as fallbacks for mocks, legacy adapters, and delta-shaped returns.

Related Issue

Fixes #68257

Complements #69306 (compression-triggered prefix misses via _compressed) and relates to #70695 (tolerant semantic turn-start detection).

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)

Changes Made

  • gateway/platforms/api_server.py
    • _run_agent annotates result["_transcript_mode"] = "full" when the agent returns a message list.
    • _build_response_conversation_history trusts the explicit mode and returns the full transcript directly.
  • tests/gateway/test_api_server.py
    • TestRepairedTranscriptDuplication: two regression tests covering a repaired transcript stored once and chained turns without exponential growth.
    • TestRunAgentTranscriptMode: unit test verifying _run_agent sets _transcript_mode="full".

How to Test

python -m pytest tests/gateway/test_api_server.py::TestRepairedTranscriptDuplication tests/gateway/test_api_server.py::TestRunAgentTranscriptMode -q

…repair-induced duplication

AIAgent.run_conversation returns its authoritative full transcript in
result[messages]. Message repair may rewrite prior messages in place,
so the transcript no longer shares an exact prefix with the input
conversation_history -- the content-equality prefix detection in
_build_response_conversation_history returns turn_start=0 and falls
through to the prior + current_user + agent_messages concatenation,
duplicating the entire history on every turn.

Set result[_transcript_mode] = full in _run_agent so the builder
trusts the full-transcript path directly, regardless of repair-induced
prefix drift. The _compressed flag and exact-prefix heuristic are
retained as fallbacks for code paths that do not set the explicit mode.

Adds regression tests for a repaired full transcript stored via
previous_response_id chaining and a unit test verifying _run_agent sets
the explicit transcript mode.

Fixes NousResearch#68257 (complements NousResearch#69306 and NousResearch#70695).
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for isolating the repaired-transcript storage path. The explicit marker matches the current agent contract: agent/turn_finalizer.py:573-578 returns the authoritative messages list, while gateway/platforms/api_server.py:5571-5574 currently prepends history after an exact-prefix miss.

Problems

  • The same prefix miss still affects response output. gateway/platforms/api_server.py:5144-5152 obtains its output offset from _response_messages_turn_start_index; that helper returns 0 on a repaired prefix mismatch at gateway/platforms/api_server.py:5592-5599. _extract_output_items then scans the full transcript, so historical assistant/tool items can still be emitted as current-turn output. This is the shared-helper concern tracked by fix(api_server): robust Responses turn-start detection #70695.

Suggested changes

  • Add an endpoint regression using adjacent user input items through gateway/platforms/api_server.py:4961-4966, with the mock applying the real repair helper before returning the transcript.
  • Cover the resulting output boundary as well as stored history, or coordinate that portion with fix(api_server): robust Responses turn-start detection #70695.

This is an automated hermes-sweeper review.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 31, 2026
@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 31, 2026
_sweeper review on NousResearch#75289 noted that the original fix only covered the
storage path in _build_response_conversation_history. The same prefix
miss also affects _extract_output_items, which uses
_response_messages_turn_start_index to decide where the current turn's
output begins.

Extend _response_messages_turn_start_index so that when
_transcript_mode=full is set, it reverse-anchors on the last user
message in the authoritative full transcript. This gives the correct
output offset even after repair_message_sequence merges adjacent user
messages and the exact-prefix comparison fails.

Adds an endpoint regression test with adjacent user input items and a
prior history containing tool calls, asserting that the output array
contains only the current turn's assistant message while the stored
conversation_history remains the repaired full transcript.
@LiangYang666

LiangYang666 commented Jul 31, 2026

Copy link
Copy Markdown
Author

@teknium1 thanks for the review.

I've pushed a follow-up commit that addresses the output-side boundary:

  • Extended _response_messages_turn_start_index to reverse-anchor on the last user message in the authoritative full transcript when _transcript_mode="full" is set and the exact-prefix comparison fails. This gives _extract_output_items the correct current-turn offset even after repair_message_sequence merges adjacent user messages.
  • Added test_repaired_adjacent_user_input_output_boundary, an endpoint regression test that feeds adjacent user items through the input[] parser (as suggested) with a prior history containing tool calls. It asserts that the output array contains only the current turn's assistant message while the stored conversation_history remains the repaired full transcript without duplication.

Please take another look when you have a chance.

@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

Three PRs reference #68257. #68282 and part of #70695 address the reported but currently unverified leading-system transcript shape, while #75289 addresses a concrete repair-induced prefix mismatch by marking authoritative full transcripts and handling their output boundary.

Related pull requests

  • fix(api): keep Responses history system-free 🩷 #68282 best fix — (+109/-9) — author action: rebase onto main, or split out the part that can merge. The diff strips leading system messages and avoids re-appending a user-prefixed suffix, but the current agent path cited in the maintainer-bot keep_open review does not produce that private-system transcript shape; the author should identify a current producer or re-scope the tests and fix to verified behavior.
  • fix(api_server): robust Responses turn-start detection #70695 best fix — (+367/-8) — author action: rebase onto main, or split out the part that can merge. Its semantic prefix matching and output-boundary coverage target broader mismatch cases, but its unconditional removal of leading system messages can discard valid client-provided history; consistent with the maintainer-bot keep_open review, the salvageable mismatch/output work should be separated from the unverified system-stripping path.
  • fix(api_server): trust full transcripts via _transcript_mode to stop repair-induced duplication #75289 best fix — (+250/-0) — keep open with a salvage path. The diff marks the normal AIAgent result as an authoritative full transcript, stores it verbatim after repair-induced prefix drift, and reverse-anchors output on the last user message; the follow-up adds output and chaining coverage, but the maintainer-bot keep_open review's requested end-to-end regression should still invoke the real repair helper rather than supplying an already-repaired mocked transcript.

Duplicates

#68282 and the leading-system handling in #70695 substantially duplicate the same unverified system-prefix mitigation. #75289 overlaps their history-doubling symptom but addresses a distinct, current repair-induced prefix mismatch rather than that claimed producer shape.

Suggested consolidation

Keep #75289 open with a salvage path: retain the explicit authoritative-transcript marker and storage/output-boundary fix, then strengthen the endpoint regression so it exercises the real repair helper. For #68282, request author action to identify a current leading-system producer or re-scope the patch; for #70695, request author action to split out the independently verifiable semantic mismatch and output-boundary work while removing role-only system stripping. Do not treat #75289 as resolving the unverified leading-system claim, and do not close #68282 or #70695 as exact duplicates of it.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I68257(["issue #68257 (closed)"])
    P75289["PR #75289 (open)"]
    P75289 -->|best fix| I68257
    class I68257 closed
    class P75289 open
    class P75289 best
    class P75289 target
    click I68257 "https://github.com/NousResearch/hermes-agent/issues/68257"
    click P75289 "https://github.com/NousResearch/hermes-agent/pull/75289"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 3 pull requests and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 42 kB of PR diffs, 16 kB of issue/PR text, 1 kB of discussion (3 comments), 7 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

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 needs-decision Awaiting maintainer decision before any implementation P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform 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.

Exponential conversation history doubling in Responses API (_build_response_conversation_history)

4 participants