Context Caching
Cache expensive context resolvers and leverage LLM provider token caching for cost savings.
Context resolvers that call databases, RAG systems, or APIs run on every prompt.resolve() call. Crux names the two caching layers separately:
memo: { ttl }: skip the resolver function when the same declared inputs produce the same output.cache: true: mark the resolved block for provider-level token caching.
Quick Start
import { context } from "@use-crux/core";
import { z } from "zod";
const brand = context({
id: "brand-voice",
input: z.object({ orgId: z.string() }),
system: async ({ input }) => {
const data = await fetchBrandProfile(input.orgId); // expensive DB call
return `## Brand Voice\n${data.guidelines}`;
},
memo: { ttl: 300_000 }, // app-side resolver memoization
cache: true, // provider cache hint
});The first call runs the resolver and memoizes the result. Subsequent calls with the same orgId within 5 minutes skip the resolver entirely. Adapters mark the block for provider caching; see the Anthropic adapter reference for exact marker placement.
Option Forms
| Form | Resolver Memo | Provider Cache |
|---|---|---|
memo: { ttl: 300_000 } | 300s | No |
cache: true | None | Yes |
memo: { ttl: 300_000 }, cache: true | 300s | Yes |
Not set / cache: false | None | No |
memo.ttl is your freshness tolerance: the resolved contribution may be that
old. cache: true is only a provider hint; it does not memoize resolver work.
Fine-Grained Control
Use the two fields independently when the layers need different settings:
// Cache the resolver but DON'T mark as a provider cache breakpoint
// (content varies too much for prefix caching)
const searchResults = context({
id: "search",
input: z.object({ query: z.string() }),
system: async ({ input }) => ragSearch(input.query),
memo: { ttl: 30_000 },
});
// Provider cache only, cheap to compute but stable across calls
const rules = context({
id: "rules",
system: "## Rules\nAlways respond in JSON.",
cache: true,
});How It Works
Application-Level (Resolver Caching)
When memo.ttl > 0:
- A cache key is computed from
contextId+ a stable hash of the input fields declared in the context'sinputSchema - On cache hit: the cached text is returned,
systemFn()is never called, and inspect/artifact metadata reportsservedFrom: "memo", the originalresolvedAt, andage - On cache miss:
systemFn()runs, the result is stored withmemo.ttl, and metadata reportsservedFrom: "live"plusresolvedAt - Unrelated prompt-level input fields don't affect the cache key
Provider-Level (Token Caching)
When cache: true:
- The resolution pipeline includes
systemBlocksonResolvedPrompt: one block per system contribution, each with aproviderCacheflag and a singlecacheBoundarymarker on the final cached block - Cached contexts form a stable prefix after the prompt's own system text; uncached contexts form the droppable tail. Token budgets can drop only the uncached tail.
@use-crux/anthropicconverts blocks to AnthropicTextBlockParam[]withcache_control: { type: 'ephemeral' }on thecacheBoundaryblock@use-crux/googlecreates server-sideCachedContentobjects for the cacheable system prefix via Google's caching API, then references them ingenerateContent()calls while sending any uncached remainder assystemInstruction. A single CachedContent lifecycle handles creation, reuse, per-call TTL, concurrency dedup, and graceful error fallback automatically; configure it (TTL, max entries,onError: 'throw', a custom cache port, or a fully custom lifecycle) viacreateGoogle(client, { cachedContent }). See the@use-crux/googlereference.@use-crux/ai(Vercel AI SDK) converts blocks toSystemModelMessage[]withproviderOptions: { anthropic: { cacheControl: { type: 'ephemeral' } } }on thecacheBoundaryblock when the model is Anthropic- OpenAI prefix caching works automatically: Crux's deterministic context ordering already provides stable prefixes
Requirements
idis required whenmemois set: needed for cache key derivation. An error is thrown at definition time if missing.- Static contexts cannot use
memobecause there is no resolver call to memoize. Usecache: trueon stable static content for provider prefix caching. - Short memo with provider cache warns when
cache: trueandmemo.ttl < 300000, because the provider may reuse the block longer than your declared freshness tolerance. - Dynamic prompt system with cached contexts warns because provider cache prefixes must be byte-stable before the native breakpoint.
Observability
Resolved contributions carry freshness facts through .inspect() and
context.contribution artifacts:
- Live resolutions report
servedFrom: "live"andresolvedAt - Memo hits report
servedFrom: "memo", the originalresolvedAt, andage - Structured segments can also carry
observedAtandsourceVersionwhen a primitive already knows source freshness
compilePrompt().inspect() uses the same memo-cache path as .resolve(), but
runs with quiet observability and instrumentation ports so inspection does not
emit spans, artifacts, or cache events.