Praxis AI - Inference & Agent Gateway

A Gateway ThatReads the Body

Built on Praxis Core. It looks inside the request - the model, the format, the intent - and routes, enriches, and guards AI traffic without the backend lifting a finger.

scroll to begin ↓
Part I - Scope & Architecture
Chapter 01

Ordinary proxies route on the envelope

A normal proxy decides where traffic goes by looking at the path and headers - the envelope. But AI traffic hides everything that matters inside the JSON body: which model, which API shape, whether it streams, whether it is stateful.

Praxis AI extends Core with filters that inspect that body safely and turn what they find into routing signals. The base proxy still handles TLS, HTTP/2, load balancing; Praxis AI adds the intelligence layer on top.

The enabling trick is StreamBuffer: buffer just enough of the body to classify it, before choosing an upstream - without buffering the whole stream or blocking it.
Chapter 02

An overlay on Core, not a fork

Praxis AI is not a standalone proxy. It is a layer of AI-aware filters and provider types that plugs into the same filter pipeline covered in the Core booklet.

Core stays general and AI-free: TLS, routing, load balancing, the pipeline engine. Praxis AI adds only what is specific to inference and agent traffic - body classification, provider API shapes, credential injection, streaming translation, a response store. Nothing AI-specific leaks into the core.

fig.01 - where AI sits on the stack
AI filters compose into Core's pipeline; Core rides on Pingora.

praxis-ai-apis

Provider API types and persistence: OpenAI and Anthropic shapes, the request classifier, response store, token usage extraction, and an MCP client. The nouns.

praxis-ai-filters

AI filter implementations: classification, model routing, credential injection, prompt enrichment, stream translation, guardrails, MCP/A2A agentic protocols. The verbs.

On top sits praxis-ai-proxy, the AI-native server binary built on Praxis. It calls FilterRegistry::with_builtins() to get all Core filters, then registers the AI filters on top. The split means provider types and the response store can be tested and reused independently of the filters that act on them.

Part II - How a Request Is Understood
Part II - The Request Path
Chapter 03

What kind of request is this, really?

The first filter is a pure classifier - no I/O, it just parses the body once and returns the facts. It walks a precedence ladder to name the format.

fig.02 - the classification ladder
A body drops in; the classifier checks each rule in order.

The classifier recognizes six formats. Precedence matters: an input field means Responses API; messages + max_tokens + Anthropic signals means Anthropic Messages; messages alone is Chat Completions. Valid JSON with no recognized fields is UnknownJson; malformed bodies are InvalidJson; empty bodies are NonJson.

Path-based shortcuts bypass body parsing entirely: GET /v1/responses/{id} and similar sub-resource endpoints are classified as Responses API by URL alone. WebSocket upgrade handshakes on /v1/responses are also detected.

Chapter 04

Facts travel on three channels

Once classified, the facts have to reach the filters downstream. Praxis AI promotes them three ways at once, each for a different consumer.

fig.03 - one fact, three destinations
The classified model and format fan out to three channels.

Metadata

Durable key-value pairs that persist across proxy phases - the way filters talk to each other through the entire request lifetime.

Headers

Added to the upstream request (e.g. X-Model) so the router can pick a provider-specific cluster.

Filter results

Written to a result set that branch chains read to make conditional routing decisions.

Sanitized

Every promoted value is length-capped and scrubbed of control characters. Untrusted body content never flows into a header unchecked.

Chapter 05

Route, rewrite, enrich, inject

With facts in hand, a toolbox of 27+ AI filters does the real work - each a normal Praxis filter, composed like any other.

fig.04 - the AI filter toolbox
Model routing, credential injection, prompt enrichment, format translation - pick what you need.

Credential injection

Per-cluster API keys added at the proxy; client keys stripped. Backends stay secret. Clients never hold provider credentials.

Prompt enrichment

Inject system or user messages into the chat body before forwarding - policy and context applied centrally via prepend and append lists.

Stream translation

Transform streaming SSE between OpenAI and Anthropic shapes on the fly, handling incomplete UTF-8 across chunk boundaries. One client format reaches many backends.

Response store

Persist non-streaming responses (SQLite or Postgres) and rehydrate a conversation by previous_response_id for multi-turn Responses API support.

Token counting works across six provider formats: OpenAI, Anthropic, Google, Bedrock (both Converse and InvokeModel), and Azure. The token_count filter extracts usage from both streaming SSE events and non-streaming JSON responses, writing token.input, token.output, and token.total to metadata for downstream consumption.

Chapter 06

Speaking JSON-RPC for MCP and A2A

Beyond model calls, Praxis AI understands agentic protocols. A shared json_rpc filter from Core parses the JSON-RPC 2.0 envelope once; MCP and A2A filters layer protocol-specific extraction on top.

fig.05 - the agentic protocol stack
A JSON-RPC envelope splits into protocol-aware handlers.

The MCP filter recognizes methods like tools/call, tools/list, resources/read, and initialize, extracting tool and resource names from params. In broker mode, it can aggregate tool catalogs from multiple backend MCP servers and handle tools/list and tools/call locally, routing tool invocations to the correct backend.

The A2A filter handles Google's Agent-to-Agent protocol, extracting task IDs and context IDs for routing. It tracks task ownership across responses so follow-up requests route back to the originating backend. Agent traffic becomes first-class, routable, inspectable proxy traffic.

Chapter 07

Conversations and persistence

For the OpenAI Responses API, Praxis AI manages multi-turn conversations locally without forwarding to any upstream.

The conversations filter handles all eight CRUD operations on conversations and items directly via FilterAction::Reject - these requests never leave the proxy. The rehydrate filter loads prior context from the response store when a request includes previous_response_id.

The response store supports both SQLite (local development) and Postgres (production) backends. Records are tenant-scoped, and the store tracks conversation items with monotonic positioning for ordered retrieval. The ResponseStoreRegistry implements PipelineExtension so stores are injected into the request context at pipeline setup - no global state needed.

The Responses API has two routing modes: stateful (when using previous_response_id, tools, conversations, or background mode) and stateless (when store=false with no stateful markers). The format filter detects this automatically and promotes the mode for routing decisions.