Gateway Turn Error Codes

Typed error payload on the final response frame when a gateway turn fails

When a turn fails, the final response frame carries a typed outcome.error, so a chat UI can show the right Retry or Stop button and a safe message instead of a raw provider traceback.

Diagram

true

false

❌ Turn Fails

🔍 Classify Error

📦 outcome.error

🔁 retryable?

⏱ Wait + Retry

🛑 Show Message

Quick Start

  1. Read outcome.error on a final response
    python
    import asyncio
    from praisonai_bot.gateway.client import GatewayClient
    
    async def main():
        client = GatewayClient(url="ws://localhost:8765", agent_id="my-agent")
        await client.connect()
        await client.send("Summarise today's news")
    
        async for event in client.events():
            frame = event.data
            if event.type == "response" and frame.get("status") == "final":
                outcome = frame["outcome"]
                if outcome["status"] == "error":
                    err = outcome.get("error", {})
                    print(err.get("code"), "-", err.get("message"))
                break
    
    asyncio.run(main())
  2. Retry only when retryable
    python
    import asyncio
    from praisonai_bot.gateway.client import GatewayClient
    
    async def ask(client, text):
        await client.send(text)
        async for event in client.events():
            frame = event.data
            if event.type == "response" and frame.get("status") == "final":
                return frame
    
    async def main():
        client = GatewayClient(url="ws://localhost:8765", agent_id="my-agent")
        await client.connect()
    
        for attempt in range(3):
            frame = await ask(client, "Summarise today's news")
            outcome = frame["outcome"]
            err = outcome.get("error")
            if outcome["status"] != "error" or not err or not err["retryable"]:
                break
            await asyncio.sleep(err["retry_after_seconds"] or 2 ** attempt)
    
        print(frame["content"])
    
    asyncio.run(main())

How It Works

The gateway classifies the exception, picks a fixed safe message for its code, and attaches the result to the final frame.

Diagram
Client_turn_error_payloadGatewayAgentClient_turn_error_payloadGatewayAgentraises exceptionclassify(exc)code, retryable, retry_after_seconds, safe messageresponse / final / outcome.errorretry or show message
json
{
  "type": "response",
  "status": "final",
  "content": "Error: ...",
  "outcome": {
    "status": "error",
    "error": {
      "code": "rate_limit",
      "retryable": true,
      "retry_after_seconds": 30,
      "message": "Rate limited by the model provider. Please retry shortly."
    }
  },
  "session_id": "..."
}
FieldTypeMeaning
codestringMachine-readable error code (see table below)
retryablebooltrue when resending the same message can succeed
retry_after_seconds`int \null`Provider’s Retry-After, rounded up; null when unknown
messagestringFixed safe text per code, never raw str(e)

Error Codes

coderetryableTypical causemessageClient action
rate_limittrueProvider 429 / rate limit“Rate limited by the model provider. Please retry shortly.”Wait retry_after_seconds, then retry
overloadedtrueProvider temporarily overloaded“The model provider is overloaded. Please retry shortly.”Back off, then retry
transienttrueTimeout or network blip“A temporary error occurred. Please retry.”Retry
idle_timeouttrueProvider stopped streaming“The model provider stopped responding. Please retry.”Retry
context_overflowfalsePrompt exceeds model context“The conversation is too long for the model. Start a new chat.”Offer “Start a new chat”
billingfalseAccount out of credit“The model provider account is out of credit.”Show top-up link
authfalseCredentials rejected (recoverable)“The model provider rejected the credentials.”Ask the operator to re-authenticate
auth_permanentfalseCredentials rejected (permanent)“The model provider rejected the credentials.”Ask the operator to replace the key
model_not_foundfalseConfigured model unavailable“The configured model is not available.”Ask the operator to fix the model
invalid_requestfalse400-class request error“The agent failed to complete this turn.”Show message, do not retry
internalfalseAnything unrecognised“The agent failed to complete this turn.”Show message, do not retry

Common Patterns

Retry or stop

python
import asyncio

async def handle(frame):
    err = frame["outcome"].get("error")
    if err and err["retryable"]:
        await asyncio.sleep(err["retry_after_seconds"] or 2)
        return "retry"
    return "stop"

Branch by code

python
def next_step(err):
    code = err["code"]
    if code == "context_overflow":
        return "Start a new chat"
    if code == "billing":
        return "Top up your credit"
    if code in ("auth", "auth_permanent"):
        return "Re-authenticate"
    return err["message"]

JavaScript

javascript
const ws = new WebSocket("ws://localhost:8765");

ws.onmessage = (msg) => {
  const frame = JSON.parse(msg.data);
  if (frame.type !== "response" || frame.status !== "final") return;

  const err = frame.outcome.error;
  if (frame.outcome.status === "error" && err?.retryable) {
    setTimeout(() => resend(), (err.retry_after_seconds ?? 2) * 1000);
  }
};

User Interaction Flow

The user sees a spinner, a short “Retrying” notice, then the answer.

Diagram
GatewayUIUserGatewayUIUsersend messagemessageaccepted (spinner)final, error.code = rate_limit, retry_after_seconds = 30“Retrying in 30s…”resend after 30sfinal, outcome.status = okanswer

Best Practices

Show message, not content

content on a failed turn still starts with Error:. Show outcome.error.message to end users — it is a fixed safe string.

Honour retry_after_seconds

Wait the full retry_after_seconds before resending. Fall back to exponential backoff only when it is null.

Cap your retries

Retry a few times at most, then surface the message. retryable means a retry can succeed, not that it will.

Don't confuse with connect errors

Turn errors leave the socket open and you decide whether to resend. Connect errors close the socket and drive the reconnect loop.


Related