ADR-249: A configuration may answer a repeated request from the cache
- Status
-
Accepted
- Date
-
2026-11-03
- Authors
-
Netresearch DTT GmbH
Context
Cache short-circuits a call whose metadata carries a key, and
only embeddings set one. A chat or completion asked again with the same
request goes to the provider again and is paid again.
Whether that happens often enough to matter was measured before anything was built. The telemetry row stores no prompt, so a replay over 90 days of the demo installation counted, per caller and configuration, the successful calls whose message count, payload bytes and token estimate equal those of an earlier call that had finished before the later one started. Equal shape is necessary for a hit, not sufficient, so the figures are upper bounds. Over the 133 plain chat and completion calls that name their caller (streamed calls and agent runs left out), 17 had such a twin within an hour and 33 within a day. Eight of the 17 are the briefing questions of nr-landingpage, whose prompt depends only on the template.
Decision
A configuration opts in with a time to live. The field
answer_ (seconds, 0 to 86400) is 0 by default, and 0 keeps the
call exactly as before. The operator decides per configuration, because the
same request asked twice is a repeat for one caller (questions for a template)
and a request for a different answer for another (a "suggest again" button).
Only a plain call. Llm and
complete attach the entry, and every configuration
path of chat, complete, chat and
complete goes through them. A call of an agent run, a
call that carries injected context, a tool call and a streamed answer never
get one: the first three depend on more than their messages, and the stream
does not run through the pipeline. The tool loop declines the cache on its two
tool-free calls itself, because a run whose row could not be written has no run
reference. A caller whose point is a fresh answer declines with
Chat; the evaluation runner does, because a
replayed answer would grade the model as it answered before. A call bound to a
response schema gets no entry either: its caller
validates the answer afterwards, and a rejected answer must not come back for
the whole window.
The key is a SHA-256 over the configuration's uid, identifier and last edit
(tstamp), the operation, the messages after skill injection, the per-call
options, and the site and language, whose term list the guardrail applies. The user, the
budget and the correlation id stay out: they do not change the answer, so two
editors asking the same thing share one entry.
What is stored. Only a complete text answer: finish reason stop, no tool
calls, not empty, and valid JSON when the call asked for JSON mode. Cache sits outside the guardrail, so the
entry is the screened answer. A replay carries no usage, is not charged against
a budget, records cache_ in telemetry and says answer in
its metadata. The mark Cache, not the
key, opts a call in, so a caller's own key on a chat call still does nothing,
and a call that carries its own key gets no answer entry.
Privacy. The entry is the answer verbatim. It is written only when
Run answers full for the
provider that served the answer, as the provenance of ADR-220
names it, and read only when that provider's level is still full. That is
the global level tightened by the provider (ADR-242). The
serving provider, not the configuration's, decides: a model chosen by criteria
and a fallback answer on a provider the configuration does not name. An answer
without provenance is not kept. At redacted, metadata and none the
field has no effect.
Invalidation. The time to live; an edit of the configuration, through
tstamp in the key; and flushing the caches (nrllm_, tags
nrllm_ and nrllm_).
Consequences
- ●● A request that repeats within the window costs nothing and answers at once, where the operator expects repeats.
- ● Off by default and inert below
full: nothing changes for an installation that does not set it. - ◑ The default privacy level is
metadata, so an installation has to raise it tofullbefore the field works. - ◑ An answer that came from a fallback is replayed as that configuration's answer until it expires.
- ◑ A change outside the configuration record — a skill, a snippet, a model record, a term list — reaches a cached answer only when it expires.
- ◑ The demo counts are upper bounds from one installation; the real hit rate
shows in the
cache_column once a configuration opts in.hit
Net Score: +4
Alternatives considered
An extension-wide switch. Rejected: it would serve a cached answer to a button whose purpose is a different answer.
Opt-in per call through Chat. Rejected for now: no caller
asks for it, and it would change nothing until each extension adopts it.
Keep a redacted copy at redacted. Rejected: a redacted copy is not the
answer, so it cannot be served.