Repository navigation
Keep conversations with encrypted history on the upstream that encrypted it - #368
Merged
Merged
Conversation
…ted it OpenAI Responses hands clients encrypted state (reasoning, compaction, Codex sub-agent messages) that only the encrypting account can read. A conversation moved to another upstream failed every turn with invalid_encrypted_content. - tw_dialect::sealed: detect upstream-encrypted content in a Responses request (reasoning only vs content that cannot be dropped) and recognise refusals of it. - Affinity remembers the upstream that last answered each conversation, across config reloads, cache expiry and quiet upstreams; 30 days for conversations that carried encrypted content. - Routing sends such a conversation back to that upstream (Stay::Sealed): the only candidate when more than reasoning is encrypted, refused with gw.route.sealed_upstream_gone if it was removed; first among the candidates when only reasoning is. - A refusal of encrypted content fails with gw.upstream.sealed_elsewhere, which explains what happened. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
OpenAI Responses with
store: false(how Codex always sends) hands the client encrypted state to send back every turn: reasoning, remote compaction, and Codex multi-agent messages (agent_messagecontent parts of typeencrypted_content). Only the account that encrypted it can read it. When a conversation moved to another upstream — the route changed, aload-balancegroup rotated after the cache went cold, or a failover — every following request failed withinvalid_encrypted_content("Encrypted function output content could not be decrypted or decoded"), often as an error event inside a 200 stream. Dropping encrypted reasoning (seal.rs) does not help when the encrypted part is the sub-agent's message itself.What changes
tw_dialect::sealed(shared with the enterprise gateway):sealed(body)tells whether a Responses request carries upstream-encrypted content, and whether it is only reasoning (Sealed::Reasoning) or something that cannot be dropped (Sealed::Content). Values the gateway wrote itself (tw1.prefix) do not count.refused(wire, said)recognises an upstream refusing such content;seal::refusalnow uses it and also matches the "could not be decrypted or decoded" wording.holder). Unlike the cache-driven stay, it survives the upstream going quiet, group changes, cache expiry and config reloads, and conversations that carried encrypted content are kept for 30 days instead of one.Stay::Sealedin the routing view).Content: the holder is the only candidate, no failover. If it was removed from the config, the request is refused up front withgw.route.sealed_upstream_gone.Reasoningonly: the holder goes first; other candidates stay as failover, whereseal.rsdrops the reasoning they cannot read.gw.upstream.sealed_elsewhere, which says another upstream encrypted the history and what to do, followed by the upstream's own words.config.md/config.zh-CN.mddescribe the behaviour next to conversation affinity.New message codes:
gw.route.sealed_upstream_gone,gw.upstream.sealed_elsewhere. NewStayvalue:sealed. No protocol bump: older UIs show the English text and no stay label.Tests
tw-gateway/tests/sealed.rsdrives the real pipeline against two fake accounts that each refuse the other's encrypted content: a sealed conversation returns to its account after a config change and does not fail over; a removed holder is refused plainly; reasoning-only prefers its account and still fails over; both refusal shapes (400 and in-stream) produce the new message. Three of the four fail with the pin disabled.cargo fmt --check,cargo clippy --workspace --all-targets -D warnings,cargo test --workspacepass locally.🤖 Generated with Claude Code