feat(router): reject previous_response_id on a router collection - #3497
Open
Bekhouche wants to merge 1 commit into
Open
feat(router): reject previous_response_id on a router collection#3497Bekhouche wants to merge 1 commit into
Bekhouche wants to merge 1 commit into
Conversation
A response chain is state held inside the backend that created it, so `previous_response_id` cannot survive a turn routing to a different candidate than served the last one. `/v1/responses` now returns 400 with `router_response_chain_unsupported` when the addressed model is a collection.router, before dispatch and before any model load, rather than letting a new candidate answer without the chain's context. The check reuses a single `router_collection_info()` helper that dispatch also calls, so the guard and dispatch cannot disagree about what counts as a router collection. Scoped narrowly: an empty or non-string `previous_response_id`, a concrete model, and every other endpoint are all unaffected. Guard 2 of lemonade-sdk#2957. Guards 1 and 3 are deliberately left out — the tool-loop condition needs a schema key, and responses-capability filtering belongs on top of the candidate-filter framework lemonade-sdk#3339 is introducing.
Bekhouche
marked this pull request as ready for review
September 3, 2026 10:10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reject
previous_response_idon a router collection./v1/responsesnowreturns 400 with
router_response_chain_unsupportedwhen the addressed modelis a
collection.router, before dispatch and before any model load. A responsechain is state held inside the backend that created it, so it cannot survive a
turn routing to a different candidate than served the last one; previously the
field was ignored everywhere in
src/and the selected candidate silentlyanswered without the chain's context.
Part of #2957 (Guard 2 of 3), not
Fixes. Guard 1 needs aroute_policyschema key, which would conflict on
schema-lock.jsonwith #3169 and #3339while both are open; Guard 3 belongs on top of the candidate-filter framework
#3339 introduces. Please leave #2957 open on merge.
Scope
src/cpp/server/server.cpp— the guard at the top ofhandle_responses, aresponse shaper mirroring
set_router_residency_conflict_response, androuter_collection_info()extracted fromapply_router_collection_dispatchsothe guard and dispatch cannot disagree about what counts as a router
collection.
error_types.h/server.h— the error code and the helper'sdeclaration. Plus
docs/api/openai.mdandtest/server_router.py.Testing
C++ build + full
cpp-ciCTest suite (69/69). Three new cases inserver_router.pycovering the 400's shape, a concrete-model control, and anempty-string control.
Also checked end-to-end against a live
lemond, using a collection whosecomponents point at nonexistent checkpoints so the loader always fails: only
the collection with a non-empty
previous_response_idshort-circuits with the400, while an empty string, an absent field, a concrete model, and
/chat/completionson the same collection all reach the loader instead.Documentation
docs/api/openai.md— Limitations note onPOST /v1/responses.Breaking Changes
Narrow: that one request shape previously returned 200 and now returns 400. The
old success answered without the chain's context, and the error names both
alternatives — resend the full
input, or address a concrete model.AI-assisted contribution
Yes.