LLM gateway architecture
One process and one write path; no multi-replica or high-availability claim
every questionOn this page
One decision point, and everything arranged around it
The single most useful structural decision is that exactly one function decides whether a request may proceed, and it decides deterministically from inputs somebody else gathered. In Token Observe that function takes the subject and its roles, the delegation chain’s role sets, the engaged kill switches, the policies, the recent spend window, the pre-flight cost estimate, and the results of the personal-data and injection scans, and it returns allow, block or require-approval plus a redaction plan. It performs no input and no output of its own.
Two properties fall out of that and both are worth the constraint. The first is reviewability: an assurance team can read what allow and block actually mean without standing up the service, because the governance domain has no runtime dependencies and no database underneath it. The second is reuse without divergence. Token Observe governs a model gateway, a tool protocol endpoint and a subscription seat compiled down to a developer’s own machine, and all three are decided by the same evaluator against the same rows, because the parameter it takes was widened to a governed subject rather than duplicated. A second policy engine is a second set of semantics, and the two only have to disagree once.
The same rule applies to the predicates that decide scope. Whether a kill switch reaches a subject, and whether a policy’s scope selects it, are each defined once and exported, because a second copy that was stricter by one character would omit an engaged switch from a compiled bundle — and the big red button would be pressed in the console and reach nothing on the device.
What sits around the decision point is gathering and enacting. Gathering resolves the caller and loads what the decision needs. Enacting turns a verdict into a typed error, an approval record, or an outbound request with the redaction plan applied. Keeping those three concerns apart is what makes the order below something you can state rather than something that emerges from where the code happened to grow.
The order is the design, step by step
Token Observe’s request path is eleven steps and the order is encoded in the evaluator rather than being a documentation artefact. It is worth walking because each position is an argument.
Authenticate first, obviously. Resolve the subject second — the agent, its roles, the active kill switches and its recent spend window — from the same row the registry holds, so there is no exported copy to drift. Open the trace third, before anything can refuse the request, so that a refusal is recorded rather than missing; the identifier returns on a response header on every response including a blocked one. Sanitise fourth, so that every later reader sees the text the model would have received rather than the text as it was transmitted. Scan fifth, over the sanitised text, per fragment and under each fragment’s own source.
Govern sixth. Inside that step the order is again fixed: engaged kill switches, then the lifecycle state, then deny-by-default permissions including the delegation intersection, then the ceilings, then the policies whose scope selects this subject in priority order. A halt has to beat a lifecycle check because a halt is the control somebody engages under pressure. Permissions have to beat ceilings because an agent that may not perform an action at all should be refused for that reason rather than for being over budget. And the mask that narrows an agent’s authority to the named human’s runs after the verdict and before the approval branch — after, because an already-blocked request gains nothing from a second reason, and before, because nothing should ask a human to approve something the intersection forbids.
Enact seventh: a typed error and a trace closed as blocked, or an approval record and a 403 carrying its identifier, or the redaction plan applied to the outbound payload. Route eighth, honouring the agent’s data policy across the primary and every fallback. Call upstream ninth, with an explicit timeout, retries only on idempotent failures, and a per-provider circuit breaker. Govern the response tenth — if the model proposes a tool call, evaluate it against tool-call rules before returning it, which is what makes a rule such as refunds over a threshold need approval bind an agent that executes tools outside your gateway. Meter and record eleventh, including when step ten refused the proposal, because the tokens were spent either way.
Step ten deserves its caveat stated where the claim is: it is defence in depth, not a guarantee. A gateway can only refuse a proposal it is shown, and an agent that never routes its tool calls anywhere near you is a discovery problem rather than a policy one.
1 authenticate digest lookup, constant-time compare
2 resolve subject agent, roles, kill switches, spend window
3 open trace trc_ id minted; returned even on a refusal
4 sanitise invisible characters stripped to a fixpoint
5 scan personal data + injection, per fragment
6 govern kill switch -> lifecycle -> RBAC -> ceilings -> policy
6b narrow by human intersection only; never a grant
7 enact block | approval | allow + redaction plan
8 route data policy applied to primary and fallbacks
9 call upstream timeout, idempotent-only retry, circuit breaker
10 govern the response proposed tool calls evaluated before return
11 meter and record usage normalised, priced, ledger written, trace closedMany dialects in, one canonical request in the middle
A gateway that agents will actually adopt has to speak the dialects their SDKs already speak, because the adoption cost has to be one base URL and one credential. Token Observe accepts OpenAI Chat Completions and Responses, the Anthropic Messages dialect, native Gemini generate-content methods, embeddings against OpenAI-compatible providers, and a tool protocol endpoint, and puts all of them through the same governance path.
The design decision that makes that tractable is a canonical request in the middle: each dialect adapter normalises inbound, and the pipeline governs one shape. The corollary is that anything the canonical shape cannot represent has to be refused rather than forwarded, and this is where most compatibility layers quietly leak. Token Observe rejects state it cannot inspect — server-held conversation references, item references, background jobs, prompt templates, opaque file ids and hosted built-in tools — with a typed invalid-request error, and asks the caller to inline those inputs so governance sees the complete payload. It rejects upstream routing controls that would delegate model choice, processing or charges outside its own decision. And it rejects image, audio and PDF inputs on every dialect, because there is no bounded media decoding behind the detectors and a caller-supplied MIME type is not proof that opaque bytes are safe.
Passthrough is the trap worth naming. A field a gateway forwards without reading is a channel into the model that no scanner sees, and the fix is not a longer allowlist of fields to inspect but a rule that an unrecognised field is refused. Token Observe had exactly this defect — vendor passthrough fields bypassing the pipeline — and records it in its published defect list rather than leaving it out.
One more thing belongs to the canonical layer rather than to the adapters: the response must remain byte-faithful to the dialect the caller chose, including the usage fields their client library expects. A gateway that is subtly not the API it claims to be gets debugged by every team that adopts it.
Streaming is where gateways quietly lose their guarantees
A buffered response has a moment where the whole answer is in hand and a decision can still be taken. A stream has no such moment: the status line is spent on the first byte, and bytes already written cannot be recalled. Every guarantee a gateway offers on the buffered path has to be re-argued for the streaming one, and most implementations do not.
Three rules make it work. Resolve the response-side plan before the first byte, computed from the policies that could apply rather than from the classes that turn out to be present, because there is no later point at which to widen it. Hold back enough of the stream that a value split across two chunks cannot escape half-masked, with a separate buffer per tool-call argument channel, and pull the cut back off anything it would split — both off a match that straddles it and out of the middle of an unbroken run of value characters, since a token that matches nothing until its third segment arrives will otherwise have its head emitted before the detectors have seen it whole. Token Observe’s hold-back floor is 64 characters, which exceeds every kind that states a maximum length, and its per-channel run bound is 4,096 characters, beyond which it emits one irreversible marker and suppresses the run through its delimiter rather than releasing either half of an ambiguous value.
The refusal shape has to change too. A blocking rule cannot answer with a status code once the stream has started, so it ends the stream with an in-band error frame the instant its class is seen, masked either way so the value never reaches the client. A rule running in observation mode uses an observe-only scan over the same boundaries and appends its evidence without changing the response, which is what keeps a dry run distinguishable from an outage.
Two smaller streaming details save a lot of debugging. Always ask the upstream for usage and suppress the extra chunk downstream when the client did not ask for it, so metering does not depend on client behaviour. And close a client disconnect as an error with what was received so far, rather than as success — a partial trace is still evidence, and Token Observe shipped the opposite behaviour once and records it as a defect.
Failure: timeouts, failover, and the one billable attempt
Classify upstream failures rather than retrying uniformly, because the class decides where a retry should go. A timeout, a rate limit or a server error is worth trying on the next provider in the chain. A malformed request, a bad credential, an over-long context or a content-policy refusal fails the same way everywhere, so failing over burns budget and hides the cause. Token Observe classifies on those lines and never launders a content refusal into a success on another provider.
Failover has to preserve the narrowing that was already applied. A fallback chain that forgets the agent’s data policy is a data-policy control that stops binding at exactly the moment things are going wrong, and Token Observe shipped that defect once — failover discarding the narrowed route — and records it. The same argument applies to price: the reservation has to cover the most expensive candidate the route could reach, not the one it started with.
Then there is the interaction almost nobody plans for. A request under a hard spend ceiling is allowed at most one potentially billable network attempt across the whole chain, because a timeout cannot prove the vendor did not complete and bill the call, so a retry would let one reservation cover several independently billable attempts. That means no retry and no failover on a budgeted call, and the reservation retained in full on an ambiguous failure rather than released as free. An agent with no spend ceiling keeps ordinary retry behaviour. It is a genuine availability trade for a genuine spend boundary, and it is better argued in a design review than discovered in an incident.
Circuit breakers sit above all of that, per provider, so a failing upstream stops being tried rather than absorbing every request’s timeout. Note the topology assumption in that sentence: breaker state, login throttles and protocol sessions are process-local, which is one of the reasons the supported shape is one process rather than a replica set.
What must never enter the request path
A gateway is in the path of every agent call, so the discipline that matters most is about what is allowed to make a request slower or make it fail. Four things belong outside it in Token Observe, and the reasoning generalises.
Event delivery is best-effort and happens on a later tick, so a webhook receiver that is down cannot delay or fail the governed request that produced the event. Licensing never reaches the request path at all: being over a ceiling is reported rather than retroactively enforced, and deleting the licence file returns the install to an unlimited fallback with every control still enforcing — because a licence problem that degraded a customer’s safety controls is the one failure a governance product cannot have. Retention runs as an hourly pass in small batches, each its own short transaction, so ageing traces out interleaves with traffic instead of stalling the fail-closed path. And discovery is scheduled from a due-time column written when a run finishes rather than from an in-process timer, so a process redeployed more often than the interval still scans.
The inverse discipline is that anything genuinely inline has to be bounded. Inspection of attacker-controlled text runs synchronously, and a JavaScript runtime cannot interrupt a running regular expression, so every pattern has to be linear in the length of its input and every walk needs caps on depth, node count and string length that fail closed. Token Observe found two super-linear patterns in its own scanner, rewrote them, added the scan cap and published the episode.
Finally, be explicit about the topology the whole design assumes. Token Observe is one process with one write path; PostgreSQL exists behind the store ports as an evaluation alternative rather than as a supported high-availability topology, and there is no multi-replica, clustering or vendor-operated uptime claim. That is a real constraint on where this architecture fits, and a gateway inline in every agent call is the last place to discover an availability assumption you had not made explicit.
- Events
- Published after the fact, best-effort. A receiver being unreachable must never turn into a governed request failing.
- Entitlements
- Reported, never enforced inline. An install that drops below its licensed ceiling keeps running and shows as over-ceiling rather than silently losing agents.
- Retention
- A scheduled pass in small batches with its own short transactions, so a purge interleaves with traffic rather than stalling it.
- Discovery
- Due time is a column written when a run finishes, not a timer. A source may only clear a finding when its run actually completed.
How to put LLM gateway architecture into practice
- 01
Write the decision as one pure function
No input and output inside it, all state gathered by the caller, one return value covering allow, block, approve and the redaction plan. Anything that needs the same semantics later calls this rather than reimplementing it. - 02
Fix the order and encode it, not just document it
Halt, lifecycle, permissions, ceilings, policy — and money after routing. An order that lives only in a document is an order the next change reorders. - 03
Open the record before anything can refuse
Mint the trace identifier before sanitisation and scanning, and return it on every response including refusals, so a blocked request is evidence rather than an absence. - 04
Normalise every dialect into one canonical request
Adapters in, one governed shape in the middle, and a typed refusal for anything the canonical shape cannot represent — including passthrough fields and media you cannot inspect. - 05
Re-argue every guarantee for the streaming path
Resolve the response plan before the first byte, hold back enough characters that a split value cannot escape, and end a blocked stream with an in-band frame because the status line is already spent. - 06
Classify failures before deciding where a retry goes
Fail over on timeouts, rate limits and server errors; never on auth, invalid request, context length or content policy. Preserve the narrowed route across failover, and price the most expensive candidate the chain could reach. - 07
Keep everything non-essential out of the path
Events, licensing, retention and discovery run beside the request rather than inside it, and everything genuinely inline gets a bound that fails closed.
Where this argument meets an implementation
MCP gateway
One endpoint in front of every upstream tool server, and the same evaluator deciding a tool call that decides a model call.
Model routing
Six upstreams behind one set of policies, and a fallback chain that will not launder a refusal.
Policy engine
One deterministic verdict on every governed request: allow, block, redact, or park it for a human.
Flight recorder
Every governed request in a timeline a compliance officer can read, and a search box that never writes SQL.
Those pages are one product's implementation of what this guide argues for; describe what your agents actually do and you will get a straight answer about whether you need any of it, including when the answer is no.
Talk it throughWhy decide spend last rather than first?
Because a defensible price is not knowable until routing has fixed the complete provider chain. Route rules can send a requested model elsewhere, tier routing can substitute a cheaper one, compatibility filtering can drop a target and failover can promote a fallback mid-request, so pricing the model name in the request body leaves a rerouted target or an unpriced fallback as a zero-dollar escape hatch. Everything that does not depend on the route — permissions, rate limits, policy — is decided in a pure pass before routing runs, and only the money verdict is deferred to after it.
Should the gateway be a separate service or a library?
A separate endpoint, because the property that makes it a control is that the agent cannot decline it. A library the agent imports is enforced by the thing being governed and changes whenever somebody redeploys. What can sensibly stay a library is the decision itself: Token Observe’s governance domain is pure and dependency-free, which is what lets the same evaluator decide a model call, a tool call and a policy bundle compiled down to a developer’s machine without three sets of semantics drifting apart.
How does a gateway keep policy binding when agents execute tools elsewhere?
By governing the proposal as well as the execution. When a model’s response proposes a tool call, Token Observe evaluates it against tool-call rules before returning it, so a rule such as refunds over a threshold need approval binds an agent that executes the call in its own framework rather than through a governed tool endpoint. State the limit beside it: this is defence in depth rather than a guarantee, because a gateway can only refuse a proposal it is shown. An agent that routes neither its model calls nor its tool calls through you is found by discovery, not by policy.
What breaks first when a gateway is put under real load?
Usually one of three things. Inline inspection on attacker-controlled text, where a single pattern that backtracks super-linearly stalls every other request sharing the process — which is why every pattern has to be linear and every walk needs caps that fail closed. Concurrency on the spend check, where several callers read the same pre-reservation window and all pass a ceiling one of them breaches, which needs one atomic per-subject transaction. And streaming, where the hold-back buffer either is not deep enough and leaks a split value, or is unbounded and exhausts memory on a hostile upstream.
Does this architecture support high availability?
Not as it stands, and that is a stated constraint rather than an omission. The supported shape is one process with one write path and its own database file; a PostgreSQL adapter exists behind the store ports as an evaluation alternative, with dual-backend testing for store and concurrency invariants, but it is not a supported high-availability topology and there is no multi-replica, clustering or point-in-time recovery claim. Process-local state such as circuit-breaker status, login throttles and protocol sessions is part of the reason. A control inline in every agent call has to be honest about its own availability envelope, and the operational answer is to decide in advance what happens when it is unavailable.
Prefer to ask a person? Write to us →
Bring us the question this guide did not answer.
Write to hello@tenhaw.com with what your agents do, which providers they call and what would have to be true for you to put something in front of them. James Rooney replies. You will get a straight answer about whether Token Observe fits, including when it does not.
no form · no qualification step · no sales desk · the other three ways in