ADR-096: The pipeline configuration lives on the call context
- Status
-
Accepted
- Date
-
2026-07-20
- Authors
-
Netresearch DTT GmbH
Context
The middleware pipeline that wraps every chat/embedding/vision call —
budget, telemetry, cache, idempotency, guardrail, fallback, usage, circuit
breaker — took the Llm as a separate positional parameter
of Middleware and of every Provider.
Six of the eight middleware merely forwarded it; it existed as its own parameter
only so Fallback could substitute a sibling configuration on a
retryable failure.
That shape hard-wires the pipeline to callers that have an Llm
entity. The specialized services — DALL·E, FAL, Whisper, TTS, DeepL — do not:
they are identified by provider and model strings and dispatch HTTP directly,
which is exactly why they bypass the pipeline and, with it, telemetry,
correlation ids, the circuit breaker and input guardrails.
Decision
The configuration moves onto the context. Provider gains a
nullable configuration plus provider / model /
configuration strings. Middleware
and Provider drop the separate
configuration parameter; $next and the terminal now receive the context.
Fallback swaps the configuration through
Provider.
When the configuration entity is present it is the source of truth; when it is
null the string fields are — telemetry / telemetry /
telemetry encode that fallback in one place, so
telemetry, usage and the circuit key work whether the call came from a
configuration entity or a bare service descriptor. Three factories name the
intent: for (generic), for (an entity), for
(provider/model strings, no entity).
Provider gains the specialized cases — image generation/edit/
variation, transcription, speech synthesis, translation — so every AI call is
labelled from one vocabulary.
The class names keep the Provider prefix for now. Renaming
Provider → Ai and the sibling types is a pure
cosmetic follow-up ( 40 references) and is deliberately not bundled into this
behaviour-preserving change.
Consequences
- No behaviour change. This is a structural refactor: the chat path builds a
context via
forand every existing test passes unchanged in intent.Configuration () Telemetryreads provider/model/identifier from the context helpers rather than the entity, which also means the "requested primary configuration" survives a fallback swap for free.Middleware - Breaking for downstream pipeline callers.
MiddlewareandPipeline:: run () Providerchanged signature, and a custom middleware or a directMiddleware Interface:: handle () runcaller must move the configuration onto the context. In-tree this was a mechanical migration across the middleware tests.() UsageandMiddleware Circuitnow tolerate a null configuration (a specialized call): usage attributes by the context's model string and the circuit keys off the context's provider.Breaker Middleware - This is the enabling step. It delivers no user-visible change on its own — the specialized services do not yet route through the pipeline. Adding the fail-closed dispatch seam and migrating those five services onto it is the following step, at which point they gain telemetry, correlation ids, the circuit breaker and input guardrails.