Caching data
Data that is expensive to load and the same for many requests, such as the catalog's facets, can be cached. context.compass.cache stores it in Redis, so later requests read it without calling the commerce API again. A step's scratchpad, in contrast, lasts for one request only.
Compass caches two kinds of data:
- Your own data, through
context.compass.cache. It is available in every function that receives the request context: script actions, scripted transitions, tools and MCP tools. - Tool schemas built by a
schemafunction, through the tool'scacheoption. The same option exists on an LLM action's dynamicoutputSchema.
Both are stored in the same cache and follow the same rules for stores.
Caching your own data
- Call
getOrSetCachewith akeyand adatafunction that loads the value. - Put every value the data depends on in the
key, such as locale and currency. - Optionally, give the entry
tagsand atimeToLivein seconds.
callback: async (context) => {
const { locale, currency } = readCatalogScope(context);
const facets = await context.compass.cache.getOrSetCache({
key: `facets:${locale}:${currency}`,
data: () => fetchFacets(context),
tags: () => ['facets'],
timeToLive: 3600,
});
return { scratchpad: { facets } };
},The first call runs data and stores the result. Later calls with the same key return the stored value until it expires or is cleared.
Outside production, the cache is kept in memory and is empty after every restart.
Caching tool schemas
A tool whose schema is a function builds its schema on every request. When the schema depends on data that changes rarely, such as the catalog's facets, give the tool a cache option. Compass then stores the built schema and reuses it.
const facetSearch = defineTool(
{
name: 'facetSearch',
description: 'Picks the catalog filters the query mentions.',
scratchpadSchema: z.object({ facets: z.array(facetSchema) }),
schema: async (context, { scratchpad }) => buildFilterSchema(scratchpad.facets),
cache: {
key: async (context, { scratchpad }) => `facetSearch:${facetsSignature(scratchpad.facets)}`,
timeToLive: 3600,
},
},
async (context, { aiProvidedArgs }) => ({ toolResponse: aiProvidedArgs }),
);keyreceives the same arguments asschema: the request context,workflowPayloadandscratchpad. Put every value the schema depends on in the key.- A key that is an empty string skips the cache for that request, and
schemaruns. timeToLiveis in seconds. Without it, the entry does not expire.- The entry is tagged with its own key, so
invalidateCache({ tags: [key] })clears it.
Compass stores the schema as JSON Schema. Defaults and descriptions survive, but some zod features do not:
| zod feature | On a cache hit |
|---|---|
.default(), .describe(), .optional() | Kept. |
.refine(), .superRefine() | Lost. The cached schema does not run the check. |
.transform() | The schema cannot be stored. Compass logs an error and runs schema on every request. |
If the cache cannot be read or written, Compass logs the error and runs schema as if no cache were set.
Clearing entries
To clear entries, call invalidateCache with their tags:
await context.compass.cache.invalidateCache({ tags: ['facets'] });Stores and the cache
Each deployment has its own cache. Within one deployment, each store served through the config switcher also has its own cache. A key never returns data written for another of those stores, so the key does not need a store id.
File-based multistore
Stores served through the config switcher share one deployment and its Redis instance, which is why Compass separates their keys. Stores of a file-based multistore setup (apps/stores/) are separate deployments, each with its own Redis instance. Their caches never mix, so their keys do not need a store id either.
Stores that serve identical data through the config switcher can share one cache. Set cache.shareAcrossStores in the Compass integration's configuration, in sf-modules/compass/config.ts:
export const config = defineConfig({
configuration: {
apiKey: ALOKAI_COMPASS_API_KEY,
cache: { shareAcrossStores: true },
workflows: {/* ... */},
},
location: '@alokai/compass/server',
});Shopper data
Never cache data that belongs to one shopper, such as a cart, under a key that other shoppers can reach.
Reference
context.compass.cache
| Method | Arguments | Returns | Description |
|---|---|---|---|
getOrSetCache | { key: string; data: () => Promise<T>; tags?: (data: T) => string[]; timeToLive?: number } | Promise<T> | Returns the value stored under key. On a miss, runs data, stores the result and returns it. timeToLive is in seconds; without it the entry does not expire. Values must be JSON. A null result counts as a miss, so data runs again on the next call. |
invalidateCache | { tags: string[] | '*' } | Promise<void> | Deletes the entries stored with any of the tags. '*' deletes every tagged entry of the store. |
| Option | Type | Default | Description |
|---|---|---|---|
cache.shareAcrossStores | boolean | false | Compass middleware configuration. Lets the stores one deployment serves through the config switcher share cached data, including tool schemas and MCP session data. |
Tool cache option
| Option | Type | Default | Description |
|---|---|---|---|
key | (context, { workflowPayload, scratchpad }) => Promise<string> | required | Key the built schema is stored under. An empty string skips the cache. On an LLM action's outputSchema, the second argument is { workflowPayload }. |
timeToLive | number | none | Seconds until the entry expires. Without it, the entry does not expire. |