Skip to content

fix(backend): treat any non-end_turn stop as a failed side-channel call - #1368

Merged
philmerrell merged 1 commit into
developfrom
fix/side-channel-end-turn-only
Sep 27, 2026
Merged

philmerrell merged 1 commit into
developfrom
fix/side-channel-end-turn-only

Conversation

@philmerrell

Copy link
Copy Markdown
Contributor

What

The side-channel Converse calls only rejected stopReason == "max_tokens". Any other non-completion stop (a guardrail stop, a content-filter refusal, tool_use, an unknown reason, or a missing reason) was accepted as a valid result. This change accepts a result only on a positive completion signal. Everything else goes through the fallback that site already has. No new fallbacks are added.

Site Accepted Anything else goes to
Compaction narrative (compaction_summary._compress) end_turn, max_tokens (salvaged as before) None, then newest-first truncation (truncated_after_model / extract_then_truncate)
Compaction extraction (extract_with_model) end_turn, max_tokens (complete lines kept as before) None, then the narrative alone (plain compression)
Tool-batch summary (tool_summaries/summarizer.py) end_turn None, then the SPA's deterministic formatter
Document abstract (files/document_digest.py) end_turn None (digest without an abstract)
Conversation title (inference_api/chat/service.py) end_turn, max_tokens (the existing 50-char clip) The "New Conversation" placeholder, with no title write and no session_title event

Why

Kaizen review 2026-09-25, Proposal 2 (Ship). A refused or guardrail-stopped compaction summary becomes the checkpoint in the cacheable conversation history, and it stays there until the next cut. That costs answer quality for the rest of the session and cannot be undone. The tool-summary, abstract and title outputs also persist (as TSUM# rows, the digest and the session title), so a refusal's text would be shown or fed back to the model. We don't know exactly which stop reason Bedrock returns for a refusal, and this change makes that irrelevant.

The max_tokens salvage added for compaction in PR 1353 is unchanged. The title keeps accepting max_tokens because its overrun handling (the 50-char clip) already exists and changing it is out of scope.

Tests

  • One stubbed guardrail_intervened test per site, parametrized with content_filtered, tool_use, an unknown reason and None where relevant:
    • Compaction: narrative refused on a single call, extraction refused, and narrative refused on the extract path.
    • Tool summary, document abstract and title: the refused result is dropped.
  • The existing max_tokens tests are unchanged and still pass, including all salvage tests. Added a title test confirming max_tokens output is still clipped.
  • Touched files (plus the quality harness and the title route tests): 258 passed.
  • Full backend suite: 10890 passed, 3 skipped, 0 failed.

🤖 Generated with Claude Code

The side-channel summarizers only rejected stopReason == "max_tokens", so a
guardrail stop, content-filter refusal or unknown reason was accepted as a
valid result. For compaction that let a refusal become the checkpoint
summary in the cacheable conversation history until the next cut.

Accept only a positive completion signal and fall back through the paths
that already exist:
- compaction narrative + extraction: end_turn, or max_tokens (salvaged as
  before); anything else returns None -> truncation / plain compression
- tool-batch summary and document abstract: end_turn only -> None
- conversation title: end_turn, or max_tokens (the existing 50-char clip);
  anything else keeps the "New Conversation" placeholder

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@philmerrell
philmerrell merged commit d201402 into develop Sep 27, 2026
7 checks passed
@philmerrell
philmerrell deleted the fix/side-channel-end-turn-only branch September 27, 2026 17:29
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.

1 participant