- Architecture
Cache spoken answers without serving stale account facts
Separate reusable wording from current account state with a scoped freshness contract and runnable cache-boundary fixtures.
- Author
Udit Goenka- Editorial date
- Reading time
- 25 min

TL;DR: Cache reusable evidence and wording separately from account facts, then check scope, source version, and expiry before reuse. Repeat freshness and authorization checks after asynchronous waiting so a fast cache hit cannot become an obsolete spoken answer.
A voice agent answers a balance question instantly because the same question was asked earlier. The wording matches, but the account changed after the cached answer was created.
The cache improved response time at the boundary it measured. It also converted historical account state into a present-tense claim, which is a different and more consequential behavior.
Begin with a freshness contract for each answer component. State what can be reused, which identity owns it, what source version it depends on, and the moment after which it must be read again.
This article provides a small cache-admission function, runnable negative fixtures, an evidence schema, and a rollout method. It distinguishes public knowledge, personalized facts, approved wording, generated audio, and terminal conversation state without assuming they share one lifetime.
Every example is synthetic. No production latency, cache hit rate, financial result, or implementation detail from 60db Agent is presented.
Table of contents
- Identify what the answer contains
- Define freshness before cache keys
- Separate cache reuse from authority
- Run a cache admission contract
- Handle waiting and terminal state
- Diagnose stale spoken answers
- Choose invalidation and storage policy
- Build a boundary fixture matrix
- Measure useful reuse honestly
- Roll out without widening exposure
- Separate storage lifetime from answer eligibility
- Frequently asked questions
- Start with one reusable component
Identify what the answer contains
A spoken answer often combines several data classes. "The policy allows a return, and your refund is pending" joins public policy evidence with a private status read even though the caller hears one sentence.
Separate those dependencies before deciding what to cache. The public sentence may be reusable across callers, while the status clause belongs to one authorized subject and a particular source observation.
A wording template is another class. "Your current balance is {amount}" can be reused as a template without storing an amount, but a synthesized audio clip containing yesterday's amount is already personalized data.
A cached transcript can also contain private facts after superficial redaction. Combinations of dates, item details, and locations can identify an account even when the person's name is absent.
The local versus managed voice AI guide helps map where these components travel. Trace the cache service, synthesis provider, telemetry system, and playback transport separately because their retention and access boundaries can differ.
Public evidence needs freshness too. A policy page can change, and an old sentence can remain linguistically correct while no longer describing the policy that applies to the caller's current request.
Treat reusable knowledge as versioned evidence. Keep its source identity and applicable conditions, then assemble a present answer only after validating the current question and authorized account scope.
An answer can be safe to reuse for a historical question. If the caller asks what a policy said at an earlier date, the relevant requirement is a trustworthy historical version rather than the newest available text.
That exception must be explicit in the cache key and answer wording. Do not let a historical result silently satisfy a present-state question because both requests mention the same topic.
Avoid beginning with a semantic similarity threshold. Similarity can help find candidate entries, but the application's factual dependencies determine whether a candidate is eligible.
A near-identical balance question asked by another subject must fail scope admission. A differently worded public policy question can reuse the same evidence when all applicable conditions match.

The two lanes are a design distinction, not a claim that private data can never be cached. Private caching requires its own justified scope, lifetime, encryption, access control, and invalidation behavior.
Define freshness before cache keys
Freshness describes whether evidence remains eligible for a particular use. A time-to-live is one way to limit reuse, but it does not express all reasons an answer can become obsolete.
A record can become invalid when the source changes, the caller changes account scope, permission is revoked, the question is corrected, or the conversation ends. Any of those can happen before a TTL expires.
RFC 9111 distinguishes HTTP freshness from validation and describes cache controls. Those protocol rules are a useful reference, while your application still needs a policy for spoken business claims.
Define the protected boundary as local answer admission in the reference program. The contract says whether an entry may be used now; it does not claim that the fact will remain unchanged until the caller finishes hearing it.
For high-consequence state, read from the authoritative source at the appropriate action boundary. A spoken balance explanation and a transaction commitment can require different freshness guarantees even within one call.
Specify the expiration comparator. This guide treats now >= expires_at as expired, so an entry exactly at the deadline is no longer eligible.
Zero duration is not unlimited duration. Likewise, a missing lifetime should not silently become a permanent cache entry when the policy requires an explicit expiry.
Use monotonic time for in-process elapsed lifetime. The Python time documentation describes monotonic clocks; a persisted cross-process record needs a separately defined timestamp and clock policy rather than exporting a process-local clock value.
Do not subtract timestamps from unrelated machines and call the result cache age. Preserve the origin, units, and clock domain, then use a trusted expiration authority or a conservative policy for distributed reuse.
A source version can invalidate an entry immediately. When the application knows that the authoritative document or account record has advanced, keeping the old entry until its nominal deadline is a policy choice that needs explicit justification.
The query correction guide covers another invalidation dimension. A fresh source about an obsolete question still cannot answer the corrected request.
Write the dependency policy in ordinary language before encoding the key. For example, a public policy paragraph is reusable for the same document version, jurisdiction, product condition, and answer language until its freshness deadline.
That sentence identifies the important dimensions. If a proposed cache key omits one of them, the key is incomplete regardless of how many hashes or namespace prefixes it contains.
Separate cache reuse from authority
A cache entry is evidence from an earlier computation. It is not a renewed permission grant, a new caller confirmation, or an instruction to resume a response that was canceled.
OWASP's authorization guidance recommends checking permissions on every request. A cache path should pass through the same trusted access decision as a fresh read rather than becoming an easier route around it.
Keep terminal session disposition authoritative. If a response generation is canceled, an exact cached retry can report its historical computation without making that generation playable again.
The table compares policy properties under the stated boundary. A check means the approach includes the criterion; a cross means it needs a separate mechanism.
| Criterion | Similarity-only answer cache | TTL-only cache | Scoped versioned admission |
|---|---|---|---|
| Finds differently phrased candidates | ✓ If a semantic index is used | ✗ Key lookup only | ✗ Candidate search is separate |
| Checks account ownership | ✗ Similarity is unrelated | ✗ Lifetime is unrelated | ✓ Exact authorized scope |
| Rejects known source changes | ✗ No version comparison | ✗ Waits for expiry | ✓ Source version required |
| Rejects exact deadline | ✗ No deadline | ✓ If comparator is defined | ✓ Explicit expired outcome |
| Preserves canceled generation | ✗ No terminal check | ✗ No terminal check | ✓ Active generation required |
| Guarantees current business state | ✗ No | ✗ No | ✗ Depends on source contract |
Private scope should be exact at the admission boundary. Fuzzy matching can help recognize a public topic, but it should never decide that two account owners are close enough to share private facts.
A role name is often too broad. Two callers can share a role while belonging to different tenants or subjects, so scope should reflect the actual data ownership and current authorization context.

Preserve rejection reasons. Missing, wrong scope, stale source, expired, canceled, and unauthorized entries lead to different safe recovery behavior even when each prevents cache reuse.
Run a cache admission contract
Save this complete fence as cache_admission.py and run Python 3.11 or later. It models one candidate entry and a current admission context; it does not implement a cache server, authentication system, or authoritative account reader.
The trusted adapter provides scope, authorization, source version, generation, and current time. It must derive them from current application state rather than from the cache entry itself or a caller-supplied claim.
The dataclasses use immutable scalar fields, which avoids writable nested payloads. If you later add structured answer values, define and freeze that supported shape before making equivalent ownership claims.
import asyncio
import math
from dataclasses import dataclass, FrozenInstanceError, replace
@dataclass(frozen=True)
class Entry:
scope: str
source_version: str
generation: int
expires_at: float
text: str
def __post_init__(self):
if any(type(v) is not str or not v for v in
(self.scope, self.source_version, self.text)):
raise ValueError("nonempty strings required")
if type(self.generation) is not int or self.generation < 0:
raise ValueError("invalid generation")
if type(self.expires_at) not in (int, float) or not math.isfinite(self.expires_at):
raise ValueError("finite expiry required")
@dataclass(frozen=True)
class Context:
scope: str
source_version: str
generation: int
authorized: bool = True
active: bool = True
def admit(entry, context, now):
if type(now) not in (int, float) or not math.isfinite(now):
raise ValueError("finite current time required")
if not context.active:
return "canceled", None
if not context.authorized:
return "unauthorized", None
if entry is None:
return "missing", None
if entry.scope != context.scope:
return "wrong_scope", None
if entry.generation != context.generation:
return "stale_generation", None
if entry.source_version != context.source_version:
return "stale_source", None
if now >= entry.expires_at:
return "expired", None
return "usable", entry.text
async def after_wait(entry, current_context, clock, wait):
await wait()
return admit(entry, current_context(), clock())
async def tests():
entry = Entry("synthetic-subject", "policy-v1", 0, 10.0,
"Synthetic public policy wording")
context = Context("synthetic-subject", "policy-v1", 0)
assert admit(entry, context, 0) == ("usable", entry.text)
assert admit(entry, context, 10) == ("expired", None)
assert admit(None, context, 0) == ("missing", None)
for changed, expected in (
(replace(context, scope="other"), "wrong_scope"),
(replace(context, source_version="policy-v2"), "stale_source"),
(replace(context, generation=1), "stale_generation"),
(replace(context, authorized=False), "unauthorized"),
(replace(context, active=False), "canceled"),
):
assert admit(entry, changed, 0) == (expected, None)
canceled = replace(context, active=False)
assert admit(entry, canceled, 0) == ("canceled", None)
assert admit(entry, canceled, 0) == ("canceled", None)
try:
entry.text = "changed"
raise AssertionError("entry was writable")
except FrozenInstanceError:
pass
started, release = asyncio.Event(), asyncio.Event()
now = [0.0]
live = [context]
async def wait():
started.set()
await release.wait()
task = asyncio.create_task(after_wait(entry, lambda: live[0],
lambda: now[0], wait))
await started.wait()
now[0] = 10.0
release.set()
assert await task == ("expired", None)
assert admit(entry, replace(context, generation=1), 20) == ("stale_generation", None)
for expiry in (float("nan"), float("inf"), "10"):
try:
replace(entry, expires_at=expiry)
raise AssertionError("invalid expiry accepted")
except ValueError:
pass
print("synthetic cache admission fixtures passed")
if __name__ == "__main__":
asyncio.run(tests())
The order of checks is intentional. An obsolete generation is rejected before expiry handling so old work cannot produce an expiry side effect against a newer response.
This example has no side effects in admit; a production adapter that cancels output on expiry must apply that action only to the exact generation it evaluated. Returning a typed outcome makes that obligation visible.
Context is trusted input rather than a security credential. Its type annotations do not authenticate a caller, and the code does not pretend that constructing the record establishes permission.
Handle waiting and terminal state
An entry can be fresh when read and expired when used. Voice systems make this easy to miss because synthesis, output backpressure, and a transport clear barrier can all introduce waiting after retrieval.
Repeat admission after the final asynchronous boundary before the protected local operation. If another await is added later, move or repeat the check so the guarantee stays attached to the actual boundary.
The event-driven fixture advances the synthetic clock while the consumer is waiting. The expected outcome is expired, even though the cache still holds the entry and no newer item has arrived.
A queue can be empty while a held answer is stale. Do not use queue length as a proxy for eligibility, and do not treat an empty read as equivalent to a required cancellation.
The audio backpressure guide explains held chunks and final submission checks. Cache freshness and output age are related boundaries, but each needs its own clock and identity.
The interruption ownership guide supplies the output-generation boundary. Cancellation must remain terminal for the old response. A later cache hit can supply evidence for a newly authorized response, but it cannot reactivate the canceled generation or replay audio the caller interrupted.
For an exact retry, choose and document the return contract. The reference returns the current canceled disposition; an evidence ledger may also expose a historical successful lookup, provided that record is never confused with present permission to speak.
The playback acknowledgment guide shows why generated text, submitted audio, and transport evidence are different observations. None of them prove the account fact remained current at an unrelated business-action boundary.
If a caller corrects a material value while the cached wording is waiting, invalidate the dependent response. Reassemble from eligible evidence for the corrected question rather than replacing only the visible text while old audio remains buffered.
The spoken correction guide supplies the accepted-value model. Cache keys should reference that model rather than extracting current truth from whichever transcript summary is easiest to access.
Diagnose stale spoken answers
A stale answer can originate at several layers. The source database, document ingestion, search index, evidence cache, answer cache, audio cache, and output queue can each preserve a different historical state.
Find the earliest stale boundary before changing TTLs. Shortening every cache lifetime can increase load while leaving an old index or a wrong source mapping untouched.
A community post asks why RAG answers remain outdated after source edits. Treat the report as a diagnostic prompt, then inspect your own source identity, index version, selected chunk, entry creation time, and admission outcome.
Record the cache key dimensions in a form that can be checked without exposing private content. A digest alone establishes equality only when the original dimensions can be reconstructed from controlled evidence.
Keep source version and fetch timestamp separate. A recent fetch from an obsolete index is still obsolete, and a historical source version can be intentionally correct for a historical question.

W3C's PROV overview provides a vocabulary for tracing derived content. At minimum, connect the spoken claim to the source observation and transformation that produced the cached wording.
Capture the current authorized subject and the entry's owner as distinct evidence fields. If they differ, investigate isolation before ranking or freshness, since a fresh answer for the wrong person is still a privacy failure.
The voice observability guide helps distinguish lookup completion from output admission. Add typed cache dispositions to that trace so a miss, stale version, and canceled response do not collapse into one generic fallback count.
Do not log private text merely to prove a scope mismatch. A synthetic reproduction with distinct owners and harmless canary phrases can test the boundary without copying real account values into a report.
Choose invalidation and storage policy
TTL limits time-based reuse. Source-version checks reject known changes, and event-driven invalidation can reduce how long an obsolete entry remains discoverable.
These mechanisms solve different problems. A lost invalidation event should not make a private answer permanent, and a short TTL should not be described as immediate invalidation after a known source change.
Redis documents the behavior of EXPIRE, including how changes to a key can affect its timeout. When using Redis, test the exact write commands and client behavior rather than assuming every update preserves the lifetime you intended.
A cache key should cover the facts that determine the answer. Useful dimensions can include tenant, subject, permission revision, source identity and version, query conditions, language, and the policy that shaped the response.
Store only the dimensions your task requires. Adding every available attribute makes keys expensive and hard to reason about, while leaving out a material dimension creates false reuse.
For private account facts, consider caching the authoritative observation rather than a finished spoken sentence. That keeps evidence separate from wording and makes it easier to validate scope and freshness before creating a new response.
A finished audio clip is harder to inspect and partially invalidate. If one clause contains a stale amount, the whole clip may need replacement even when the rest of the wording remains useful.
The sentence boundary guide can help organize smaller spoken units. Unit boundaries support selective recovery, but they do not make old facts eligible on their own.

HTTP validators offer another reference point. RFC 9110 defines conditional requests and validators; an application can adopt the same idea of validating a representation while preserving its own business freshness semantics.
Do not call a model-generated summary a source validator. A trustworthy version comes from the authoritative representation or an explicitly controlled derivation, with enough provenance to know what changed.
Build a boundary fixture matrix
Start with a public entry that is eligible under an exact source version and an unexpired clock. Then change one dependency at a time so every rejection has a clear cause.
Include the exact deadline. An off-by-one comparison can be invisible in ordinary traces while violating the contract at the one instant the test needs to establish.
Test a wrong owner with the same text and question. That case proves that scope checks depend on trusted identity rather than answer similarity or a shared phone number.
The shared-phone memory guide examines that identity boundary. A cache keyed only by endpoint can repeat another person's private information even when the caller asks an otherwise ordinary question.
Test a changed source version before expiration. This distinguishes version invalidation from TTL behavior and catches implementations that keep using a known obsolete entry until its timer finishes.
Test cancellation before an exact cache retry. The response must stay canceled on every retry of that generation, including when the cached historical result is valid evidence about the earlier lookup.
Test a new generation using an old expired entry. The admission outcome should identify the old generation before any controller side effect can target current work.
The reference program checks that ordering without implementing cancellation side effects. Add a spy or a controlled local adapter in your own suite to assert that no current-generation cancellation occurs.

Include cache write races as adapter tests. A delayed write from an old source version must not overwrite the entry that a newer successful read already stored under the same logical key.
This requires an ordering rule at storage, such as version-aware compare-and-set where the source supports an order. A content hash proves difference, so it cannot by itself tell the cache which of two versions is newer.
If source versions are opaque, retain the authoritative pointer or fetch again rather than inventing an ordering from string comparison. A lexically larger version token is not necessarily a later business state.
Measure useful reuse honestly
The outcome metrics guide separates evidence of a valid answer from a completed task. A cache hit rate alone cannot establish a successful voice interaction. It can rise when the system repeats stale answers, merges scopes incorrectly, or retains content that should have expired.
Measure eligible reuse separately from candidate matches. The former passes the current scope, source, lifetime, and generation checks; the latter only found an entry worth evaluating.
Report refetches and rejection reasons alongside accepted reuse. A high expired count may indicate an overlong wait, while a high wrong-scope count can reveal a dangerous candidate-selection design.
The cost per successful task guide explains why the denominator matters. Saving a lookup is useful only when the caller still receives a valid answer and the task retains its correctness boundary.
Measure the latency interval you changed. Cache lookup time, evidence-ready time, first submitted audio, and caller-perceived useful response are different quantities and should not be merged into one unlabeled number.
The latency benchmark guide gives a framework for those boundaries. Compare warm and cold behavior under the same request population and include failed or unavailable responses rather than omitting slow cases.
For quality, use paired fixtures with a source change between requests. The second answer should reflect the new source or truthfully decline reuse, and the trace should explain which policy produced that behavior.
This article reports no measured hit rate, latency saving, or factual accuracy improvement. The harness proves the small admission decisions under synthetic inputs; a deployment needs its own observations and representative evaluation.
Roll out without widening exposure
Begin with public content whose source versions and applicability conditions are easy to identify. That gives you a way to validate cache behavior without introducing private account material into a shared storage path.
Keep the uncached path as the reference for current evidence. Compare candidate cache decisions against it on reviewed fixtures, and inspect disagreements before admitting cached answers to live output.
A shadow comparison can still cost money and expose data. Apply the same authorization and minimization rules to both paths, and avoid sending private text to an extra model simply to score a deterministic scope decision.
Document the fallback for each disposition. Missing or expired public evidence can trigger a refetch; unauthorized private evidence must remain withheld; canceled work should stop rather than restarting itself.
The workflow enforcement guide explains why a cached explanation cannot authorize a business action. Revalidate the action against current state even when the preceding spoken policy sentence was legitimately reused.
Use the shutdown semantics guide to keep a closed task terminal through delayed cache completions. If the cache becomes unavailable, decide whether the authoritative read can safely proceed. A public knowledge cache outage and an account authorization service outage have different recovery options, so one blanket fallback is insufficient.
Preserve deletion and invalidation coverage for every derived layer. Removing a cache row while a generated audio file remains reusable can leave the same private fact accessible through another path.
The repeat-call memory guide develops consent, expiry, and derived-record handling. Cache policy should fit that data lifecycle instead of treating cached output as exempt because it was generated rather than directly collected.
Separate storage lifetime from answer eligibility
A storage engine can keep an expired entry for internal cleanup or recovery without allowing the application to serve it. Conversely, deleting a row does not establish that every consumer has discarded a copy it already holds.
Treat storage retention and answer eligibility as separate contracts. The former controls where bytes remain; the latter controls whether a particular response may use those bytes at a protected boundary.
This separation becomes useful during failure recovery. A historical entry can explain why a previous response was generated, while its current disposition remains expired, unauthorized, or canceled.
Avoid returning historical text in a field that ordinary callers interpret as ready-to-speak content. Use a distinct evidence interface for diagnosis, protected by its own access policy, so a retry cannot accidentally replay a historical answer.
The cache admission result should travel with the generation it evaluated. If the controller receives an expired outcome after the conversation has moved on, it can record that old decision without canceling the new response.
A broad callback such as cancel_current_output is dangerous in this situation. Prefer a generation-specific operation whose target must still match, and make the adapter reject an obsolete target without mutating current work.
Freshness also depends on the claim's tense. A source observation can support "the last recorded status was pending" under an explicit historical policy even when it cannot support "the status is pending now."
Do not use historical phrasing as a way to avoid the caller's current-state question. Explain the observation boundary and offer the authorized fresh read when the task requires one.
A policy cache can be stale in applicability even when its text has not changed. A caller's region, product condition, account plan, or requested date can change which unchanged document applies.
For that reason, source version and query revision belong to different dimensions. Source version describes the evidence representation; query revision describes the question and accepted conditions to which the evidence is being applied.
Language adds another dimension. Cached wording in one language can omit a condition or pronounce an identifier differently after translation, so reusing the evidence does not automatically justify reusing an existing audio asset.
Use the multilingual evaluation guide to review whether critical fields and conditions survive the response language. Keep the canonical value separate from its spoken presentation.
Personalization can also hide in otherwise public wording. A sentence that mentions a caller's selected location, preferred appointment window, or past request is no longer just a public policy paragraph.
Classify the complete stored representation. A public source tag does not make a generated answer public when the generation prompt supplied private context.
For storage isolation, do not depend on an obscure key alone. The read path must verify access to the requested object even when a caller or model can guess, repeat, or manipulate its identifier.
Cache enumeration needs similar care. A nearest-neighbor search over private answer embeddings can expose the existence or substance of another subject's information before the final text admission gate runs.
Apply scope restrictions where candidate retrieval occurs and keep the final admission check as defense against mistakes and races. Filtering only after a broad private search may already send unauthorized passages to a model or telemetry system.
An entry's deletion policy should cover its derived search representation. Removing answer text while leaving an embedding, summary, or reusable audio file can preserve enough content to recreate the same exposure.
Keep a small lineage record connecting each derived asset to its source and owner. That record makes targeted invalidation and deletion possible without treating every generated object as an unrelated file.
A cache stampede is a resource problem with a correctness boundary. Coalescing concurrent misses can reduce duplicate reads, but the shared work must retain the correct scope and source conditions for every consumer.
Do not coalesce requests from different private subjects solely because their normalized questions match. For public evidence, coalescing can be appropriate when all hard applicability conditions match and each consumer still performs its own current admission.
A shared fetch should not inherit a single consumer's canceled state as global truth. If one caller interrupts while another still needs the public result, detach that consumer without restoring its canceled response when the shared fetch completes.
Similarly, a failed shared fetch should produce an explicit unavailable outcome for each waiting consumer. Reusing an old private answer as an unannounced fallback would change the freshness policy precisely when the system has least evidence.
Test the failure path with the cache service unavailable and the source service slow. Observe whether the controller truthfully explains the limitation, applies a deadline, and avoids repeating obsolete account facts.
For deployment, preserve compatibility while keys change. A new key format should not accidentally fall back to an old, less-scoped namespace on a miss, because that would turn a safe migration into a privacy regression.
Prefer refusing reuse over guessing a missing scope dimension. You can refetch eligible evidence or ask the caller for a needed clarification, while a wrongly reused private statement may already be disclosed before the error is detected.
Before removing the uncached reference path, keep a reproducible fixture bundle. Include original dependency values, source versions, synthetic clock inputs, generation transitions, and expected outcomes, so future cache changes can be checked against the same contract.
Review cache policy when the response representation changes. Adding synthesized audio, a translated version, or a personalized summary creates another derived asset whose owner, source dependencies, and lifetime must remain inspectable.
Do not inherit an unlimited lifetime from a file store simply because the text cache already expires. The same private statement can survive in a reusable audio URL after its original evidence entry has become ineligible.
Use a synthetic canary to verify that each ordinary reuse path respects invalidation. That test establishes the routes it exercises; keep the storage inventory and backup policy separate so the report does not overclaim universal erasure from one successful lookup failure.
Frequently asked questions
What should a voice answer cache store?
Store reusable evidence or wording with explicit scope, source version, and lifetime. Personalized amounts, statuses, and generated audio need their own justified private-data policy, since they can remain sensitive and become obsolete independently of the surrounding sentence.
Is a TTL enough for account facts?
A TTL limits reuse duration but does not detect every material change. Current permissions, known source revisions, corrected questions, and terminal response state can invalidate an entry before the deadline.
Can semantic similarity authorize reuse?
Semantic similarity can locate candidate entries. Exact scope and dependency checks must decide eligibility, especially when similar questions belong to different people, accounts, tenants, policy conditions, or response generations.
What happens exactly at expiration?
The reference contract treats an entry as expired when current time equals its expiration deadline. The boundary is explicit so implementations and fixtures agree rather than relying on a storage engine's cleanup timing.
Should private data use a shared cache?
Private data requires scoped access even when the physical service is shared. Decide whether storing it is justified, enforce ownership and permissions on the read path, and include retention and deletion across derived wording and audio.
Can I cache the public part of an answer?
Public policy evidence and neutral wording can be reused when their versions and applicability conditions match. Assemble account-specific clauses from eligible current observations rather than caching a mixed sentence as if all its parts shared one lifetime.
Why recheck after an await?
The entry can expire or the session can change while the consumer waits. Repeat admission before the protected local operation, and repeat it again if another asynchronous step is introduced afterward.
Can a cache hit reopen canceled speech?
A cache hit cannot restore a canceled generation under this contract. It may supply evidence for a separately authorized new response, while exact retries of the canceled generation keep returning its terminal disposition.
What is the difference between missing and expired?
Missing means no candidate entry was available; expired means an existing entry failed its lifetime policy. Keeping those outcomes distinct helps diagnose storage behavior and lets the controller apply the appropriate recovery or cancellation action.
Does a source hash establish freshness?
A hash can establish that a representation differs from another known representation. It does not tell you which version is newer or whether the stored version is current unless an authoritative source or pointer supplies that relationship.
Should cache keys contain the phone number?
A phone number alone is an unsafe private ownership key because endpoints can be shared or reassigned. Use a verified subject and tenant scope where private recall is authorized, and minimize endpoint data in diagnostic records.
Can synthesized audio be cached safely?
Audio can be reusable when its exact content, scope, source dependencies, language, and lifetime remain eligible. A clip containing account facts needs the same checks as text, plus generation and playback invalidation at output admission.
Does a fresh entry prove the fact is current?
An entry can be fresh under a declared policy without establishing instantaneous business truth. High-consequence actions need an authoritative state check at their own boundary, with explicit handling for changes between observation and commitment.
How do I find the stale layer?
Trace source identity, source version, ingestion version, selected evidence, cache entry, and final admission. Locate the earliest divergence before changing TTLs, since a fresh cache read from an obsolete index will still produce stale evidence.
Are public documents safe to cache indefinitely?
Public visibility does not remove version or applicability requirements. Policies, product conditions, and instructions can change, so define validation and expiration even when the cached content has no private account data.
What happens if authorization changes mid-call?
Recheck current authorization before admitting private cached content. Invalidate dependent prepared output when the permission change is accepted, and ensure an older cache result cannot restore the revoked scope.
How should I test a delayed cache write?
Hold an old write, store a newer authoritative version, then release the old write. Assert that storage does not regress to the obsolete version and that current reads preserve the newer dependency evidence.
Which metric matters beyond hit rate?
Track eligible reuse, rejected candidates by reason, current-answer correctness, and the response latency boundary you intend to improve. Include refetches, unavailable outcomes, and failed tasks so the denominator does not hide the cost of incorrect reuse.
Is the reference program a cache server?
The reference program is a small admission contract with synthetic tests. Storage atomicity, distributed clocks, identity verification, encryption, deletion, transport behavior, and authoritative source reads remain adapter responsibilities.
Where should caching begin?
Begin with one public, versioned evidence component and an uncached reference path. Add private facts only after scope, freshness, deletion, and terminal-state fixtures prove the specific behavior your application needs.
Start with one reusable component
Choose a public answer clause with an identifiable source version and clear applicability conditions. Keep account observations separate, run the harness, and force expiration during a wait before allowing reuse in a spoken response.
The next useful result is an inspectable trace that explains why an entry was admitted or rejected. Use that trace to earn each additional cache scope rather than treating a high match rate as permission to reuse more data.
