Failure before the model speaks
Invisible. A provider that refuses or breaks before producing output is stepped over, the next candidate is tried, and you are never told, under either streaming policy. What reaches you is the answer, from whoever gave it, named in the reply.
"model": { "name": "kimi-k3", "tier": "xhigh", "publisher": "MoonshotAi", "provider": "TogetherAi" }This is why you read model back rather than assuming it echoes your request. The
call record lists every provider tried, in order, with the status each ended on.
Failure after the model has started
On POST /models it is your call, set in streamFailureBehavior and fixed before
dispatch: changing it requires a new request. Momor will not silently stitch two providers' half-answers together in
either mode.
streamFailureBehavior |
Behaviour |
|---|---|
"stop" (default) |
The break is the ending. No second provider speaks into a stream the first was already answering. No stop, no usage record. |
"rollover" |
The next rung finishes what the first started, on the same connection, and the frames say where that happened. |
# stop
streamStart:Anthropic → contentStart → content:"It is " → contentEnd
→ streamError:Anthropic → error:502:The provider closed the connection mid-answer.
# rollover
streamStart:Anthropic → contentStart → content:"The three causes were " → contentEnd
→ streamError:Anthropic → error:502:…
→ streamStart:Groq → contentStart → content:"a stale cache, a retry storm, and a bad deploy." → contentEnd
→ stop:stopThe frames name both sides
streamErrornames who stopped; the secondstreamStartnames who took over. If you render partial answers, that pair is your cue to show the handover rather than splice the text.
A Chat Completions stream always stops: a failure after output ends the
stream on one error chunk, with no [DONE], and the call is never moved to another provider once output has begun.
What this means for a UI
A design that renders a stream as one undifferentiated column of text cannot show a handover. Give the answer a
provider-attributed structure from the start: each streamStart opens a segment stamped with the model and provider
that produced it, and the streamError between segments is a visible seam, not a swallowed retry.
The usage on stop reports the provider that finished. Tokens spent before the break were spent with the
first provider.
Moving a conversation between providers
A conversation is not tied to the model that started it. Send a turn back as history and route it anywhere. Reasoning, open tool calls, tool results and files all travel; what a destination can carry natively it carries, and what it cannot is adapted and labelled rather than dropped.
{
"model": "gemini-3.6-flash",
"systemPrompt": "Answer in one sentence.",
"messages": [
{ "type": "user", "value": "What is the capital of France?" },
{ "type": "thinking", "value": "Recall the capitals of Europe.", "data": { "anthropic.signature": "…" } },
{ "type": "content", "value": "Checking the almanac." },
{ "type": "toolCall", "call": { "id": "toolu_01", "name": "almanac", "arguments": "{\"q\":\"capital\"}" } },
{ "type": "toolResult", "result": { "id": "toolu_01", "name": "almanac", "content": "Paris" } }
]
}| Destination | What happens to reasoning |
|---|---|
| gemini, vertex | Becomes a thought part carrying the text. Another family's signature is not sent, because Gemini cannot verify it. |
| Groq, TogetherAi, MoonshotAi, Fireworks, DeepInfra | Rides the host's own reasoning field on the assistant message. |
| azure | Rides Kimi's reasoning field for the Kimi models, and goes back as the assistant's text for every other model. |
| Mistral | Becomes a thinking part ahead of the answer in the assistant's content, carrying the signature Mistral issued where it issued one. |
| Anthropic | Replayed as its own signed thinking block where Anthropic issued the signature. Otherwise sent as text prefixed [reasoning], so the model cannot read a predecessor's working as something it already said aloud. |
Tool identities survive the crossing. Your history keeps every id as you received it, and each destination is handed the ids in the form it takes:
| Destination | The ids it is sent |
|---|---|
| Anthropic | Yours, with any character other than a letter, a digit, _ or - written as _. Gemini's functions.almanac:1 becomes functions_almanac_1. |
| OpenAI | Yours, with any id longer than 64 characters shortened to 64. |
| Mistral's models, on every host | Nine letters and digits: an id already in that form as it is, any other as a stable hash of itself. |
| Kimi K2.5 and K2.6 | functions.{name}:{index}, the form the model was trained on, numbered across the conversation. |
| Every other model | Yours, as they are. |
Every rewritten id is made the same way on every turn, so a call and its result stay paired and a provider's prompt cache still matches the conversation. You never normalise an id before sending it, and the ids in an answer are the serving provider's own. A completed tool is never re-executed because history was adapted.
What crossing costs
No provider can verify another's signed reasoning state, so what crosses is the readable text plus whatever state the destination can carry. Reasoning that started on Anthropic and moves to Gemini arrives without Anthropic's signature; moving back, it arrives as labelled text rather than a signed block.
If exact reasoning fidelity matters more than provider independence for a conversation, pin the provider for it.