How-to

Gateway

Taxiway's gateway path routes lab model traffic through LiteLLM. It is separate from the optional Langfuse observability stack.

  • Gateway path: shared Caddy proxy, one LiteLLM sidecar per lab, and one small gateway Postgres database per lab.
  • Observability stack: Langfuse and its storage services for trace inspection.

Gateways can run without Langfuse. When Langfuse is running and the lab has generated local Langfuse keys, LiteLLM exports traces to the observability stack.

For the Langfuse side, see Observability.

Prerequisites

  • docker on PATH and a reachable Docker daemon
  • a created lab

Lab gateways run on Docker even when the lab itself uses the Lima driver.

Quick Start

Create or refresh a lab gateway:

taxiway gateway <lab>

taxiway up <lab> and taxiway run <lab> also run the gateway phase automatically.

Print the lab endpoint and key:

taxiway access

From the host, use the lab-specific hostname printed by taxiway access:

http://<lab>.litellm.localhost:<proxy-port>/v1
http://<lab>.litellm.localhost:<proxy-port>/ui/login

Agents running inside the lab use the internal hostname:

http://<lab>.litellm.internal:<proxy-port>

Labs created before this hostname was introduced must be deleted and recreated so their driver configuration includes the internal hostname mapping.

When Taxiway is installed, the default proxy port is usually 4000; run taxiway access to print the actual port.

What The Gateway Phase Creates

For each lab, the gateway phase:

  • generates TAXIWAY_LITELLM_API_KEY;
  • writes the lab gateway environment;
  • creates a lab-specific LiteLLM Compose project;
  • starts LiteLLM and a small gateway Postgres database;
  • registers the host-facing <lab>.litellm.localhost and lab-facing <lab>.litellm.internal routes in the shared Caddy proxy;
  • configures Langfuse export when observability has been initialized.

Lab-specific gateway files live under:

~/.taxiway/lab-state/<lab>/gateway

The runtime LiteLLM model catalog lives under:

~/.taxiway/runtime/infra/gateway/litellm/models.yaml

Models And Lifecycle

--set model=... selects the main agent model. Each gateway also exposes all active catalog models for the providers declared by its agents: chatgpt for Codex, anthropic for Claude Code and Gas Town. This lets Claude's helper agents use Haiku or Sonnet while the main agent uses Opus. It does not make a model available on an account whose subscription or API permissions exclude it.

Run taxiway describe codex or taxiway describe claude-code to inspect the catalog. Deprecated models show their retirement date and replacement; they remain routable when explicitly selected as the main model or an alias default. Retired models are excluded and rejected with a replacement suggestion. There is no silent switch to another model. ChatGPT subscription retirement dates apply to the chatgpt route and do not imply retirement from the OpenAI API.

The chatgpt provider uses the ChatGPT subscription endpoint. With the pinned LiteLLM version, a catalog model's api_base does not override that endpoint. Taxiway does not currently support custom upstream endpoints for this provider; leave api_base unset for ChatGPT models.

Taxiway sets Claude's alias mappings and installs a managed exact model list in the lab at /etc/claude-code/managed-settings.json, preserving other managed settings. This requires Claude Code 2.1.284 or later; startup reports an upgrade requirement for older clients. Project model lists cannot widen this policy. Changes take effect after taxiway gateway <lab> and taxiway start <lab>. Codex keeps its own client catalog and capability metadata; gateway exposure does not replace that catalog or enable an unsupported client model.

The bundled LiteLLM version is 1.103.2. Compatibility shims preserve signed thinking blocks with omitted text during Anthropic tool replay and the explicit parallel_tool_calls value required by Codex Responses Lite. Isolated Docker tests exercise both Anthropic Messages and ChatGPT Responses without external networking or provider credentials. For live principal/subagent tests with reusable authentication, see testing model gateways.

LiteLLM prices each call and Langfuse records that cost (see Observability). At startup, the gateway downloads LiteLLM's current price map from GitHub and falls back to the map bundled in its image when the download fails. When the gateway starts, Taxiway prints a WARN line for each exposed model that has no price, for example a recent model missing from the bundled map while offline; calls to that model show a cost of 0.

New releases and explicit retirement announcements are checked by the daily catalog update workflow. It prepares a draft PR for review, runs compatibility checks, and keeps model defaults unchanged. It does not update running labs automatically.

Access And Credentials

Run:

taxiway access

For each running gateway, Taxiway prints:

  • the LiteLLM UI URL;
  • the OpenAI-compatible API URL;
  • the lab gateway key.

Use admin as the LiteLLM UI username. Use the printed Password/API key value as both the UI password and the API bearer key.

Labs authenticate to LiteLLM with TAXIWAY_LITELLM_API_KEY. Provider subscription credentials stay with the user agent whenever possible; LiteLLM receives the gateway key and, for subscription-backed routes, forwards the client's provider auth header.

Codex Subscription Routing

If host Codex auth exists, Taxiway can convert it into the format LiteLLM needs for ChatGPT subscription-backed Codex models:

codex login
taxiway credentials codex
taxiway gateway <lab>

taxiway credentials codex expects an existing host Codex login at ~/.codex/auth.json. It does not run codex login for you.

For Codex subscription testing, attach to the lab session:

taxiway shell <lab>

Then send a small prompt in the attached Codex session:

Respond with only: codex-subscription-ok

Codex uses a custom OpenAI-compatible provider that points to the lab LiteLLM gateway:

model_provider = "taxiway-litellm"
model = "gpt-6.1-sol"

[model_providers.taxiway-litellm]
name = "Taxiway LiteLLM"
base_url = "http://<lab>.litellm.internal:4000/v1"
wire_api = "responses"
requires_openai_auth = false
env_http_headers = { "x-litellm-api-key" = "TAXIWAY_LITELLM_API_KEY", "x-litellm-agent-id" = "TAXIWAY_LITELLM_AGENT_ID" }
supports_websockets = false

The gateway uses the ChatGPT authentication cache prepared from the host Codex login; the lab client does not need that login file. x-litellm-api-key authenticates the gateway call. Taxiway deliberately does not send x-litellm-session-id for Codex because that header would override the native Codex session. LiteLLM maps Codex's x-codex-turn-metadata.session_id header into the Langfuse session when observability is enabled.

Claude Code Max Routing

For Claude Code Max subscriptions, Claude Code can point Anthropic traffic at LiteLLM and let LiteLLM forward Claude Code's OAuth Authorization header to Anthropic:

export ANTHROPIC_BASE_URL=http://<lab>.litellm.internal:4000
export ANTHROPIC_CUSTOM_HEADERS="x-litellm-api-key: Bearer $TAXIWAY_LITELLM_API_KEY"

Use a full Claude model name declared in the LiteLLM catalog from the attached Claude Code session. Examples include:

claude-opus-5-5
claude-sonnet-5-5

Claude Code keeps the user OAuth token client-side. LiteLLM receives and forwards it. The LiteLLM config must set:

general_settings:
  forward_client_headers_to_llm_api: true

Otherwise Anthropic receives neither the Claude Code OAuth Authorization header nor an API key and returns an authentication error.

Taxiway maps Claude Code aliases (opus, sonnet, haiku, fable) to full model IDs from the same catalog. LiteLLM routes those full IDs without renaming one model to another. The managed lab environment preserves these mappings across tmux restarts and Gas Town handoffs.

Traces

LiteLLM exports successful calls to Langfuse when:

  • taxiway observe up or taxiway init has initialized observability;
  • the lab gateway has been refreshed after observability was initialized;
  • model traffic flows through the lab LiteLLM gateway.

To verify traces, start observability, refresh the gateway, and attach to the lab session:

taxiway observe up
taxiway gateway <lab>
taxiway shell <lab>

Then send a small prompt in the attached orchestrator session:

Respond with only: trace-ok

Then open Langfuse:

taxiway observe open

If traces are missing, first check the gateway path with taxiway access and the LiteLLM sidecar logs. The observability stack only stores traces after the gateway has exported them.

Troubleshooting

SymptomCheck
Gateway URL unavailableRun taxiway status; verify Docker, the shared proxy, and the lab gateway containers
LiteLLM UI auth failsUse admin and the Password/API key printed by taxiway access
Codex route fails authRun codex login, then taxiway credentials codex, then taxiway gateway <lab>
Claude route fails authVerify Claude Code is logged in and forward_client_headers_to_llm_api is enabled
Langfuse has no tracesStart observability, refresh the gateway, then send model traffic through LiteLLM

Retirement dates take effect at 00:00 UTC on the specified date.