The gateway (apps/gateway, crate obol-gateway) is the only component your agents talk to. It is an axum service that terminates agent traffic on /v1/* and /mcp, decides whether the call is allowed, injects your vendor credential on the outbound hop, and records what happened. Everything except main.rs lives in the library, so integration tests drive the same router production serves.

Surfaces

The /v1/* LLM surface is documented in LLM routes; the MCP surface in MCP surface.

The five hot-path rules

The gateway is deliberately narrow. Five constraints shape every route.
1

Authentication is a cached Redis read, never Postgres

A virtual key is hashed with SHA-256 and looked up at key:{hash} in Redis (60-second TTL by default, OBOL_KEY_TTL_S). On a miss the gateway calls the control plane’s internal snapshot API with a short-lived service JWT and writes the result back. The gateway holds no Postgres pool on the request path (ADR-0006). A store failure fails closed as not_ready — never as unauthorized, which a client would treat as final.
2

Policy is compiled data evaluated in-process

CEL decides visibility, Cedar decides permission, and both run inside the gateway process against a snapshot already in memory. There is no network hop and no database query per decision (ADR-0003). See CEL prefilter and Cedar.
3

Vendor credentials are unwrapped at the outbound hop

The encrypted credential travels in the workspace snapshot. obol-vault decrypts it (AES-256-GCM under a KEK) immediately before the outbound request and the plaintext exists only between that call and the socket. It is never serialized, cached, or logged. See Vault.
4

No product logic, and no Python, in the gateway process

OAuth exchange orchestration, billing, approvals inboxes, and connector adapters live elsewhere. Python connectors are out-of-process MCP workers (ADR-0002, ADR-0007).
5

Usage emission is asynchronous and lossy; invocation evidence is not

Usage events go through a bounded in-memory queue and a batching flusher onto a Redis stream. If that queue is full, the event is dropped and a counter increments — a response is never blocked on metering. Invocation receipts and verification events do not use that queue: they are committed durably through the idempotency store before the gateway exposes a result (ADR-0032).

Request lifecycle

Both surfaces share the same prelude: request id, authentication, workspace snapshot, environment and freeze checks, scope check. They diverge after that.

What the gateway proves

The guarantees the gateway makes before a call are the same everywhere: the key is authenticated, the tool or model is visible to it under CEL, Cedar allowed the action under the pinned policy publication, admission limits held, and an idempotency key was assigned. What the gateway can prove after the call is not uniform. A native route executes through the gateway’s own OpenAPI executor, so the receipt can record a gateway-observed outcome. A federated catalog route executes at a broker the gateway did not observe, so its evidence is untrusted or broker_attested and never verified. The receipt always names which class it is — read Receipts before relying on one.

Error envelopes

Every error is one ErrorEnvelope rendered in the client’s dialect. /v1/* OpenAI-format routes get an error object with message, type, code, and param; /v1/messages gets the Anthropic type: "error" shape; /mcp gets a JSON-RPC error object carrying the envelope as error.data. Every error response carries x-request-id, and retry-after when the code supplies one.

Configuration

Configuration is environment-only, parsed once at startup. Debug redacts every secret. OBOL_IDEMPOTENCY_TTL_S is validated at startup: it must exceed the upstream plus drain timeouts, and must not exceed the store maximum. A bad value fails the process at boot rather than at the first call.

Startup and drain

run connects Redis and fails fast if it is unreachable, preloads up to 1000 workspace snapshots, spawns the invalidation subscriber, then binds. Shutdown on SIGTERM or SIGINT flips /readyz and /startupz to draining, stops accepting connections, waits for in-flight requests and metering reconciliation, and closes the meter queue — all inside one OBOL_DRAIN_TIMEOUT_S deadline. See Sessions and drain.

Metrics

The gateway registers these Prometheus series: JSON-RPC method labels are drawn from a fixed allowlist; an unrecognized method name is labeled unknown so an attacker cannot grow the metric cardinality. See Telemetry.

Next

LLM routes

The OpenAI-compatible surface, translation, streaming, and error mapping.

MCP surface

Tool namespacing, list filtering, invocation, and approvals.

Sessions and drain

Opaque session ids, snapshot consumption, graceful shutdown.

Crate reference

What every crate in apps/gateway/crates is responsible for.