Skip to content
BytesBrains
Cruise + agentgateway

The gateway routes. Cruise prices.

Run Cruise as the OpenAI-compatible backend behind agentgateway. No plugin, no fork, no image of ours — and every response still says which job it was booked to, which model answered and where the budget stands. A recipe that has been run, including the trap that costs an afternoon.

Per-request cost attribution survives the mesh edge

A proxy is free to strip response headers it does not recognise, and one that did would cost every caller behind it their budget state and their cost attribution. agentgateway passes them through. Every response that came back through it carried:

x-cruise-lane: bb/code-review              the job this call was booked against
x-cruise-model: anthropic/claude-fable-5-1  who actually answered
x-cruise-selection: cheap                   why that one
x-cruise-budget-state: ok                   ok, or the reason the next call gets refused
x-cruise-budget-spend: 0.0000               spent this period
x-cruise-budget-limit: 5.00                 the hard cap
x-cruise-budget-period: day

The recipe’s smoke test asserts on these headers, so an upstream change that turned the gateway into a plain proxy hop would fail it.

On Kubernetes: the 400 that never mentions TLS

Cruise is agentgateway’s openai provider with the host pointed at Cruise. On Kubernetes, TLS to that host has to be stated — an AgentgatewayBackend does not infer it from port: 443. We removed exactly one block and put it back:

policies.tlsResult
omitted400 · The plain HTTP request was sent to HTTPS port
sni: cruise-demo.bytesbrains.net200, Cruise headers intact
apiVersion: agentgateway.dev/v1alpha1
kind: AgentgatewayBackend
metadata:
  name: cruise
  namespace: cruise
spec:
  ai:
    groups:
      - providers:
          - name: cruise
            openai: {}
            host: cruise-demo.bytesbrains.net
            port: 443
            path: /v1/chat/completions
            policies:
              auth:
                secretRef:
                  name: cruise-api-key
              tls:
                sni: cruise-demo.bytesbrains.net   # must match host

Two things make it slow to debug when it bites:

  1. It is not a BackendTLSPolicy. Despite Gateway API habit, it is spec.ai.groups.providers[].policies.tls on the backend itself. A separate policy resource will not fix it.
  2. The error never mentions TLS. The 400 comes from the upstream’s edge — Cloudflare, for Cruise — so it reads like a malformed request and sends you to inspect your JSON. The body is fine. The connection was plaintext.

agentgateway’s own OpenAI-compatible providers page includes this block in every example and in its template; copy that and you are fine. The trap is in carrying a mental model across from the standalone config, where the two surfaces behave differently:

  • Standalone — an https:// params.baseUrl expands to host, path prefix and TLS. Nothing to configure.
  • Kubernetespolicies.tls.sni must be stated, and must match host.

Lanes route with nothing in the gateway config

A catch-all model entry passes the request’s model id to Cruise unchanged, so every lane and every pinned model works without being listed. One call per lane, through the gateway:

Lane requestedModel Cruise chose
bb/summarizationdeepseek/deepseek-flash
bb/extractionmistral/mistral-small-latest
bb/code-reviewanthropic/claude-fable-5-1

Which model serves a lane is Cruise’s decision, made per request — this is what that run reported, not a fixed mapping.

A hard cap refuses, through the gateway

Against the demo’s hard-cap project — emulated spend, nothing billed — served requests reported rising spend of 0.0040, 0.0060 and 0.0080. The next call came back 429 with error.code: budget_exhausted at 0.0100 and retry-after: 32. After the wait, a completion was served again. The refusal reaches an agent behind the gateway as a code it can branch on, not prose.

Try it in two commands

The standalone gateway in Docker, against the free demo — it costs nothing and exits non-zero if anything fails. Put a cru_demo_ key from the Cruise demo tab in .env, which is gitignored:

cp .env.example .env
./smoke.sh

The whole standalone config:

llm:
  port: 4000
  providers:
    - name: cruise
      provider: openAI
      params:
        baseUrl: $CRUISE_BASE_URL   # https:// turns TLS on by itself
        apiKey: $CRUISE_API_KEY
  models:
    - name: "*"                     # every lane and model, none listed
      provider:
        reference: cruise

This runs on agentgateway’s unreleased 1.x build, pinned by digest. The newest published release, 0.8.2, rejects the llm: block outright. The Kubernetes path runs on released versions.

Failure modes worth recognising

What you seeWhat it is
400 The plain HTTP request was sent to HTTPS portNo policies.tls on a Kubernetes backend.
401 Incorrect API key provided.Cruise refused the key, relayed intact. Often a cru_demo_ key against production, or the reverse.
401 Missing bearer token.No key reached Cruise: the Secret’s key is not named Authorization, or the example Secret was applied unedited.
llm: unknown fieldagentgateway too old for the standalone config — 0.8.2 rejects it.

A cf-ray header on a response means the request reached Cruise rather than dying in the gateway.

What this page does not claim

  • That it was run against production Cruise — every call went to the demo.
  • That agentgateway endorses or lists Cruise. It does not.
  • Any agentgateway version beyond the ones above.

More about Cruise: the product · pricing