Part of the Codex app-server adapter.
The pinned SDK normally compacts a long chat with its previous model before switching to a model with a smaller context window. An unavailable or exhausted previous provider can therefore block a model change even after the new selection has been saved.
Releases containing the verified gateway-only SDK perform ordinary automatic compaction with the selected model inside the caller's original turn. The SDK uses its actual token estimate, reasoning effort, goal state, cancellation, and history replacement. Every compaction item retains that user turn's ID; the adapter does not submit a preceding manual turn. Declared compatibility-hash changes retain the upstream SDK's compatibility handling. This executable is used only in private gateway homes. The original signed native SDK and its authentication remain unchanged.
The installer records the pinned SDK revision, exact runtime patch, dependency lock repair, and binary checksum. Invalid or mismatched artifacts refuse a new gateway executor. A shared generation advertises selected-model compaction only after its host verifies the requested binary checksum. Its checksum participates in generation identity, so a newer executable cannot replace loaded work.
The verified Gateway SDK applies paired role context and compaction overrides to
legacy managed/* policies and the named qwen/qwen3.8-flash-next worker model.
Earlier SDK builds applied these fields only to managed names: named Qwen roles
could inherit the catalog's larger window despite smaller values in their files.
The named-worker regression checks both catalog presence and absence, real tool
execution, compaction and cold resume. Native SDK behavior and unrelated named
roles remain unchanged. Existing workers retain their loaded configuration.
Legacy packages without that artifact retain the adapter recovery below. For those per-connection gateway executors, the adapter checks public current-window usage against the selected model's frozen context metadata. It also records real failed compaction events. On the next explicit user message, a qualified idle chat is compacted with the selected gateway model before that message is submitted. The adapter waits for the actual compaction item and successful turn completion; an empty RPC acknowledgement alone does not establish success. Stop cancels only the observed compaction turn and prevents submission of the waiting message. Failure returns one application error and requires another explicit retry.
Active turns retain normal SDK steering. Recovery never pauses an active goal; pause or finish that goal first. Shared executors require explicit compaction because the SDK cannot atomically check idle state and start compaction across independent clients. Unknown context metadata is not replaced with cumulative billing totals. A confirmed rollback clears the recovery metadata. Known non-null compaction hashes are rejected because the SDK does not expose the previous-turn hash needed to prove format compatibility.
The SDK owns history replacement and retains its original rollout. Summaries are lossy, and the SDK can trim the request copy to fit the selected model; this does not guarantee exact preservation of every detail in a smaller context window. An uncertain late SDK start remains guarded until its actual completion. Reconnect the chat's computer connection if that guard requests it. The adapter never fabricates a completion or clears a live writer lock.
These changes require a fresh adapter connection and gateway executor. Installing a release preserves an already-loaded older adapter and its active chats. The legacy manual recovery can bind the desktop's pending message to the preceding compaction turn; backend completion alone does not prove rendered association. The selected-turn fixture checks the actual SDK event IDs and replays the tested desktop handlers; a rendered check remains a separate release gate.
The launch procedure and real Claude/Codex 2 conversations have been verified in the rendered desktop on MacBook Pro 2. Initial activation requires a fresh app launch with these environment variables. Test on another machine before relaunching a working app.
Max and Ultra are separate desktop display choices. Under Model features, enable the offered levels in Available reasoning efforts. Availability varies by model and the app's feature settings. Enable the choices in the app that renders the picker; enabling them on a remote Mac does not change this Mac's controls. The gateway persists these explicit display changes in its private settings and does not advertise unsupported provider effort levels.
Ultra is Codex's top effort plus Codex's own task delegation. Native rows mark
it with multi_agent_version and multi_agent_reasoning_effort; with v2,
Codex offers its collaboration tools (spawn, follow up, message, wait, list and
interrupt agents). Model discovery copies both fields from the upstream catalog,
so gateway Codex models behave like native ones. Claude rows that support max
offer Ultra with v2 delegation at xhigh. Codex sends max on the wire for
Ultra, and the Claude CLI's top level is max.
Model metadata follows each provider's own catalog. The inference server refreshes
it a minute after start and every six hours: Codex, Copilot and OpenAI-compatible
providers from their /v1/models, and Claude from the CLI's model list. Only the
metadata of configured models changes; adding or removing routes stays an explicit
gateway discover PROVIDER --load. A discovery failure keeps the saved metadata. This background
refresh never refreshes a Codex login: a provider whose access token would need a refresh
is skipped for that round and recorded as model_metadata_skipped, and the next real request
refreshes it (see responses and diagnostics).
Claude also reports the context window the CLI actually used in every response
(modelUsage.contextWindow); the server saves it in observed-context-windows.json
and the next refresh adopts it, so a window never needs a manual table. Clients
receive the new catalog the next time the app starts its app-server.
The initial Claude windows were measured on a subscription on 13 Oct 2026. The 5.x
models, Opus 4.8 and Opus 4.7 run with 1M under their plain IDs. Opus 4.6 runs
as the CLI's claude-opus-4-6[1m] variant, which the subscription includes; its
gateway alias keeps the plain ID so saved chats keep working. Sonnet 4.6's 1M
variant needs paid usage credits and Haiku 4.5 has none, so both report 200K.
Other personal Claude settings are not applied: a fallback model would silently
change the caller's model, and hooks, MCP servers and permissions must not run
inside an inference backend.
Codex 2 rows use Codex's native tool-output truncation (10,000 tokens) and window
share (95%), and keep the upstream's truncation_policy, max_context_window and
auto_compact_token_limit when discovery returns them. Other provider types keep
12,000 tokens and 90%. The upstream reports GPT-6.1-Sol at 500K with a maximum of
872K. On the desktop the adapter then applies the native row for the same model
(below). The
refresh overwrites a hand-set truncationPolicy with the upstream value; an
effectiveContextWindowPercent override lasts while the upstream omits that field.
After an update the public catalog changes twice: once for the new defaults, then
about a minute later when the first refresh saves the upstream limits.
Native GPT-6.1-Sol rows also set use_responses_lite and
supports_reasoning_effort_updates. Discovery carries both, so Codex sends gateway
Sol the same lite request as native Sol: reasoning.context: all_turns, with tool
definitions in an additional_tools input item. The gateway reads tools from that
item for tool-call mapping, passes the item to the ChatGPT endpoint unchanged, and
applies its own default namespace only to calls of top-level tools. Other
providers skip the item when building their transcript.
A model that lacks the chat's speed tier (a Fast Codex chat switched to Claude) runs its requests as Standard while the chat keeps its tier, as native Codex does. See "Speed controls" for the full rule.
Sub-agents that Codex spawns from a gateway chat (delegation, Ultra, or agent
roles in the native configuration) inherit the gateway provider but take their
model from the role, often a bare name such as gpt-5.6-terra. The server serves
such a name through its vendor's subscription route: gpt-…, o… and codex-…
names use a Codex subscription, and claude-… names a Claude subscription.
With several accounts, set "defaultForBareModels": true on one provider per
vendor. Bare names then use only that account; a model it does not offer fails
instead of falling back to another account. Account-qualified aliases still
select their own routes. This is an operator-selected default, not automatic
inheritance of the parent account; the SDK sends no parent account selector.
Without a designation, exactly one account must offer the upstream model.
Multiple aliases in that account resolve to the lexically first alias. Other
routes serving the same model, such as Copilot, are not used for a bare name.
An unknown or ambiguous name still fails with model_not_found. Each resolution is logged as
model_alias_resolved. Before this, such sub-agents failed silently: Codex retried
the 404 five times and ended the turn with nothing.
Resuming or forking a gateway chat on the same model restores the chat's own
reasoning. The adapter records the effort and summary each gateway chat last ran with in
a private desktop-thread-reasoning-<scope>.json under its state directory,
fed by applied thread settings, turns that carry an effort, and chat
start/resume/fork results; an unknown (null) value never erases a saved level,
and a chat's record never transfers to another chat. When nothing is recorded
yet, for example after a gateway restart, the adapter seeds from the chat's own
rollout file in the executor's sessions directory, reading only records that
carry reasoning. A saved level belongs to the model it was saved with: a newer
observation for another model carries no effort or summary over to it, so
switching chats never inherits the previous model's Max. A gateway model whose
row offers no reasoning levels at all receives nothing restored, because there
is no level to restore to. A restored level the model does not offer is fitted down to
the closest offered level, or to that model's default when none qualify; the
adjustment is reported as reasoning_effort_reset. A resume that switches
models still receives a level the new model supports. A caller that sends its
own effort always wins over the restored one. A native chat receives nothing
from the adapter: its requests reach the engine exactly as the desktop sent them,
and the engine restores the chat's own model and effort
(native chats keep their model).
Switching to another tokenizer
Codex decides whether to compact with the counters the previous model reported. Models from different families count the same history differently: on 30 Oct 2026 a chat that GPT-6.1-Terra measured at 591,583 tokens exceeded Claude Sonnet 5.5's 1,000,000-token window once the gateway rendered it for the Claude CLI. Sonnet's advertised 900K limit let the switch through without compaction, and every retry failed with "Codex ran out of room". The selected model cannot compact a history it cannot read, so the SDK's own recovery does not help.
Before each user turn on a gateway chat, in both SDK forms and on shared executors,
the adapter reads the last non-zero measurement since the latest compaction or
rollback and the model whose turn recorded it (turn_context; the SDK's repeated
and zero overflow counters keep the earlier attribution). When that model's family
differs from the selected one, the measurement is recounted: switches to Claude from
another family by 1.7, other cross-family switches by 1.25, and switches away from
Claude not at all, because Claude counts the larger number. When the recount reaches
the selected model's usable window, the turn is refused before anything is sent:
"Switch back to MODEL, run /compact, then switch to MODEL again." The chat and its
history are unchanged. The refusal is recorded as compaction_switch_refused.
The same recount runs for a turn/settings/update that changes the running turn's
model, because that change reaches the next inference step without a user turn. A
refused change fails with the same message and the turn keeps its model.
Families come from the upstream model name: claude-… is Anthropic, and gpt-…,
o…, codex-… and chatgpt-… are OpenAI. Any other name is its own family. The
gateway publishes each row's family as tokenizer_family in its model catalog, computed
from the upstream name, because the alias a chat records can hide it: team/large and
team/small may both be one upstream family. The adapter keeps that field only when it
is a short plain name, and falls back to the alias text for a row without one, such as
a catalog from an older gateway. Codex ignores the extra key. A fact that cannot be
read, such as a missing rollout, never refuses a turn. Recorded counters that fit even
after a 1.7 recount skip the rollout read.
Codex 0.166 records the selected model in a small turn_context record, so the guard
can attribute nearly every measurement. Earlier SDKs wrote larger records that the
adapter often could not read, which left a same-family switch between differently named
aliases unchecked; with the published family such a switch is not refused.