Skip to content

fix(sse): stop re-prepending Kiro tool docs onto every subsequent turn (#13652) - #13808

Merged
diegosouzapw merged 2 commits into
release/v3.8.51from
fix/13652-kiro-tooldocs-repeat-every-turn
Sep 16, 2026
Merged

diegosouzapw merged 2 commits into
release/v3.8.51from
fix/13652-kiro-tooldocs-repeat-every-turn

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Closes #13652

Root cause (short)

convertMessages() in open-sse/translator/request/openai-to-kiro.ts is stateless per HTTP
request. OpenAI-compatible clients resend the full growing message array on every turn, so the
same tool-bearing first user message gets re-scanned on every request and its relocated
documentation (toolDocs) rebuilt from scratch. buildKiroPayload() then unconditionally
prepended it onto whatever the current turn was, so the ~10KB+ doc block kept landing on the
newest user message on every subsequent turn instead of staying anchored to the turn that
originally carried it.

Fix

Track the actual turn object that ends up carrying the relocated docs (toolDocsCarrier), set at
both attachment sites in convertMessages(). After currentMessage is finalized, if the
docs-bearing turn was not promoted to currentMessage (i.e. it was demoted into history on
a later turn), embed the doc text directly onto that history entry's own content and clear the
local toolDocs so buildKiroPayload()'s existing prepend does not also inject it. When the
docs-bearing turn IS currentMessage (single-turn conversations, or the "no user turn" fallback),
behavior is unchanged. Net effect: the doc block still reaches the model on every request (no
regression of the earlier _toolDocs bug that dropped documentation entirely), but now exactly
once per request, anchored to the turn it was originally attached to instead of re-glued onto the
newest turn.

This does not reduce the total bytes sent to Kiro per request (same total payload size) — it only
fixes where the doc block lands. Cutting the actual byte/token cost would require confirming
whether Kiro's conversationId-scoped Builder ID cache retains prior-turn content server-side,
which is unverified from the repo alone and out of scope here (flagged as a follow-up).

Regression test

tests/unit/issue-13652-kiro-tooldocs-repeat.test.ts

RED (unfixed code):

✖ issue #13652: relocated tool doc is re-prepended on every subsequent turn, not just the first
  AssertionError [ERR_ASSERTION]: turn 2: the tool documentation must NOT be re-prepended to a
  later turn's content once it has already been delivered earlier in the conversation
  (found 12113 chars, incl. the doc block, in currentMessage.content: # Tool Documentation ...)

GREEN (fixed code):

✔ issue #13652: relocated tool doc is not re-prepended to a later turn once already delivered
ℹ tests 1
ℹ pass 1
ℹ fail 0

Gates run

  • npx eslint --suppressions-location config/quality/eslint-suppressions.json open-sse/translator/request/openai-to-kiro.ts tests/unit/kiro-long-tool-description-docs.test.ts tests/unit/issue-13652-kiro-tooldocs-repeat.test.ts → clean, no output
  • npm run check:open-sse-typecheck → openSseTypecheckErrors=0
  • node scripts/check/check-file-size.mjs → no violation on touched files (one pre-existing, unrelated frozen-file violation in open-sse/utils/stream.ts, not touched by this PR)
  • node scripts/check/check-complexity.mjs → OK — 2824 violations (baseline 3218)
  • node scripts/check/check-cognitive-complexity.mjs → OK — 1276 violations (baseline 1437)
  • node scripts/check/check-test-discovery.mjs → OK, new test file discovered
  • DATA_DIR=$(mktemp -d) node --import tsx/esm --test --test-force-exit tests/unit/issue-13652-kiro-tooldocs-repeat.test.ts → 1/1 pass
  • DATA_DIR=$(mktemp -d) node --import tsx/esm --test --test-force-exit tests/unit/kiro-long-tool-description-docs.test.ts → 6/6 pass
  • DATA_DIR=$(mktemp -d) node --import tsx/esm --test --test-force-exit tests/unit/executor-kiro.test.ts → 20/20 pass
  • Full sweep of every other test file that imports openai-to-kiro/buildKiroPayload/convertMessages (kiro-interleaved-tool-results-8903, kiro-model-aliases, kiro-system-reminder-2306, openai-to-kiro-helpers-split, translator-openai-to-kiro, openai-responses-reasoning-effort, repro-6576-kiro-thinking-unsupported-model, request-dedup-10249, translator-ai-sdk-image-parts, kiro-continue-filler-5231) → 80/80 pass

Existing tests aligned

tests/unit/kiro-long-tool-description-docs.test.ts — two tests updated:

  • "relocated tool documentation reaches the current turn for every turn shape" → renamed to
    "relocated tool documentation reaches exactly one turn for every turn shape". It previously
    asserted the doc block always lands on currentMessage.content for every TURN_SHAPES entry —
    that encoded the pre-fix bug for the multi-turn shapes (where the tool-bearing turn is demoted
    into history). Now it asserts the doc block reaches exactly one turn across
    history + currentMessage combined, which is the actual regression guard for fix(providers): Kiro translator re-prepends full relocated tool documentation on every turn #13652 (catches
    both silent dropping and re-injection).
  • "only oversized descriptions are relocated in a mixed tool inventory" → the assertions that
    checked current.content for the relocated doc section now check the combined
    history + currentMessage content, since the "multi-turn conversation" shape it uses now
    anchors the doc in history[0].

Both changes are alignments to the corrected contract, not weakenings — no assertion was removed
or loosened; the "exactly once" check is strictly more specific than the prior "current turn
contains it" check.

#13652)

convertMessages() in openai-to-kiro.ts is stateless per HTTP request. Since
OpenAI-compatible clients resend the full growing message array on every turn,
the same tool-bearing first user message got re-scanned on every request and
its relocated documentation (toolDocs) rebuilt from scratch; buildKiroPayload()
then unconditionally prepended it onto whatever the newest turn was, so the
~10KB+ doc block kept landing on the current turn instead of staying anchored
to the turn that originally carried it.

Track the actual turn object that carries the relocated docs
(toolDocsCarrier). When that turn is demoted into history on a later request
(instead of being promoted to currentMessage), embed the doc text directly on
that history entry and clear the local toolDocs so buildKiroPayload's existing
prepend does not also inject it — docs now reach the model exactly once,
anchored to their original turn.

Regression test: tests/unit/issue-13652-kiro-tooldocs-repeat.test.ts (RED on
unfixed code: turn-2 currentMessage still carried the full doc block; GREEN
after the fix, with the doc anchored in history[0] instead).
@diegosouzapw
diegosouzapw force-pushed the fix/13652-kiro-tooldocs-repeat-every-turn branch from 73b8888 to f221c5f Compare September 15, 2026 22:56
@diegosouzapw
diegosouzapw merged commit 3fd2440 into release/v3.8.51 Sep 16, 2026
19 of 21 checks passed
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
diegosouzapw#13652) (diegosouzapw#13808)

Merged in the 2026-09-16 sweep of the maintainer's own open PRs, at the owner's explicit instruction. No push was made to the PR branch: the merge took the head as the owning session left it (verified OPEN, non-draft and MERGEABLE against the release tip immediately before merging).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(providers): Kiro translator re-prepends full relocated tool documentation on every turn

1 participant