Guides / Use cases

Shopping assistants

For shopping assistants and recommendation chats in a web shop or app. Recall remembers each shopper's sizes, tastes, budget and returns across visits, and hands your catalogue search their preferences as structured filters, so recommendations fit the first time.

What memory adds

Without memory, every visit starts with the same questions. With it, the assistant already knows what fits:

Without memoryWith Recall
"Trail shoes for muddy runs, nothing neon, max €150"Asks for the size, then shows the bestseller in neon green.Shows the Trailblaze GTX in black, in 43: the 42s came back too small.
"Something to eat on long runs?"Suggests whey protein bars.Only vegan bars: the shopper's account says vegan.
Next month, a rain jacketStarts again from zero.Keeps to the budget and the dislike of neon without being told again.

Recall does not search your catalogue or write the replies. Your search and your model do; Recall tells them what this shopper wants, and learns from what they say.

How to model it

In your shopIn RecallWhy
Your shopWorkspace, e.g. shop-euOne memory space for all shoppers. Use separate workspaces only where data must stay apart, such as separate brands or legal entities.
A shopperPeer cust-1042, your customer idRecall learns from what they write in chat and in return forms.
Your assistantPeer shop-assistant with observeMe: falseYour product pitches are not facts about anyone; its replies cost less.
A chat visitSession, e.g. chat-5e21cA summary per visit; what was learned about the shopper carries over.
An order with a returnSession, e.g. order-77120, with dated messagesThe shopper's return reason is first-hand information about fit.
Sizes and diets the shopper entered in their accountConclusions you createFacts as given, stored at once; nothing is inferred.
A shopper who opts out of personalisationobserveMe: false on their peerRecall stops learning about them.

Build it

Install the SDK (npm install @recall-memory/sdk or pip install recall-mem) and set RECALL_API_KEY. Create the shopper and the assistant, and tell Recall once what to learn in your shop and what never to record. Custom instructions guide the model that extracts facts; they are not a hard filter, so also keep sensitive data out of what you send.

import { Recall } from "@recall-memory/sdk";

const recall = new Recall({ workspaceId: "shop-eu" });

// The shopper is a peer keyed by your customer id; the assistant is a peer Recall does not
// learn about.
const shopper = recall.peer("cust-1042", { metadata: { country: "NL" } });
const assistant = recall.peer("shop-assistant", { configuration: { observeMe: false } });
// Once, for the whole shop: what Recall should learn here, and what it must not.
await recall.setConfiguration({
  reasoning: {
    customInstructions:
      "Record sizes and fit per brand, budgets, colours, styles, brands, diets the shopper " +
      "states, and returns with their reasons. Never record or guess health, pregnancy, " +
      "religion, ethnicity, sexuality or money trouble from purchases or messages.",
  },
});

Account data: facts you write. Sizes and diets the shopper entered in their account are things they told you, not guesses. Write them as conclusions: stored as given and usable at once. Preferences that come up in chat ("nothing neon") are learned from the messages in the background, dated, and updated when they change.

// What the shopper entered in their account is fact, not inference: store it as facts.
await shopper.conclusions.create(["Shoe size EU 42.", "Eats vegan."]);

Returns: the shopper's words, dated. A return reason is some of the best information you have about fit. Add the text from the returns form as the shopper's message, in a session for the order, with createdAt set to when it was filed. Recall learns from it like any other message, so the next recommendation knows the 42s were too small in this brand. Put order numbers and SKUs in metadata for your own use; Recall learns from the text.

// A return from an earlier order, in the shopper's words from the returns form, dated when
// it was filed. Recall learns from it like any message, and can close facts it contradicts.
const order = recall.session("order-77120");
await order.addMessages([
  shopper.message("Returning the Trailblaze GTX in 42: too small. I need a 43 in this brand.", {
    createdAt: "2026-10-08T17:20:00Z",
    metadata: { order: "77120", sku: "TB-GTX-42", reason: "too_small" },
  }),
]);

In the chat: one request per message. turn stores the shopper's message and returns the context for the reply: the visit so far and what Recall knows about the shopper, including the account facts and the return. Building the context uses no model and takes 0.3 to 0.7 seconds. Your model writes the reply from it and from your catalogue.

// In the shop's chat: one request stores the message and returns what Recall knows about
// the shopper for the reply. Preferences stated here are learned in the background.
const visit = recall.session("chat-5e21c");
const { context } = await visit.turn(
  [shopper.message("Trail shoes for muddy autumn runs, please. Nothing neon, max €150.")],
  { peerTarget: shopper, tokens: 2000 },
);
const prompt = context.toOpenAI(assistant);
// Your model writes the reply from `prompt` and your catalogue; store it too.
const reply = "The Trailblaze GTX in black grips well in mud, €139. In 43 this time?";
await visit.addMessages([assistant.message(reply)]);

Preferences as filters. Your search needs fields, not prose. Ask for JSON with responseFormat once per visit and keep the result in the shopper's web session: a question takes 2 to 4.5 seconds and each one is billed. Then filter or rank results in your own search. Make fields nullable so "not known" never filters out everything, and let filters the shopper sets in your UI win over memory.

// Turn memory into filters for your catalogue: ask for JSON once per visit and keep it in your
// session. (waitForIdle is for this demo; real apps rarely block on learning.)
await recall.waitForIdle();
type Filters = {
  shoe_size_eu: number | null;
  max_price_eur: number | null;
  avoid_colours: string[];
  diet: string[];
};
const prefs = await shopper.chat("Which filters should a product search use for this shopper?", {
  reasoningLevel: "low",
  responseFormat: {
    jsonSchema: {
      type: "object",
      properties: {
        shoe_size_eu: { type: ["number", "null"] },
        max_price_eur: { type: ["number", "null"] },
        avoid_colours: { type: "array", items: { type: "string" } },
        diet: { type: "array", items: { type: "string" } },
      },
      required: ["shoe_size_eu", "max_price_eur", "avoid_colours", "diet"],
    },
    parse: (data: unknown) => data as Filters, // or a zod schema's parse
  },
});
const catalogue = [
  { sku: "TB-GTX-43", size: 43, price: 139, colour: "black" },
  { sku: "VX-9-43", size: 43, price: 165, colour: "neon green" },
];
const picks = catalogue.filter(
  (p) =>
    (!prefs?.max_price_eur || p.price <= prefs.max_price_eur) &&
    !prefs?.avoid_colours.some((c) => p.colour.includes(c)),
);

Opting out. When a shopper turns personalisation off, set observeMe: false on their peer: Recall stops learning from their messages. Delete what was learned and clear their card. Their chats stay stored for the conversation itself. To erase a shopper completely, for example when they close their account, call shopper.delete().

// The shopper turns personalisation off: stop learning from their messages and drop every
// fact memory holds about them. Their chats stay stored.
// (To erase the shopper completely instead: await shopper.delete())
await shopper.setConfiguration({ observeMe: false });
const learned = await (await shopper.conclusions.list()).toArray();
for (const fact of learned) await shopper.conclusions.delete(fact.id);
await shopper.setCard([]);

Settings that matter

SettingUse
reasoning.customInstructions (workspace configuration)What to learn in your shop, and what never to record. A guide for the model, not a guarantee.
observeMe: falseOn your assistant, always. On shoppers who opt out.
responseFormatFilters as JSON: an object at the root, nullable fields for "not known".
reasoningLevellow (the default) for filters; minimal is cheaper and faster for a few simple fields. See Reasoning levels and models.
Message createdAtReturns and imported chats: when each was written, so the newest information wins.
peerTarget and tokens on turnpeerTarget: shopper adds what Recall knows about them; 1,500 to 3,000 tokens is plenty for a shopping chat.

Cost and speed

Measured from Europe against the hosted API (Frankfurt), October 2026: a turn or context call took 0.3 to 0.7 seconds, a search about 0.9 seconds, a question at minimal or low 2 to 4.5 seconds. Learning happens in the background, usually within about 20 seconds of a message, and never holds up a call.

Context, search, storage, summaries and dreams are included. You pay for learning, $1.75 per million message tokens with every message counting as at least 50, and for questions. A shopper who chats twice a month (6 messages of about 30 tokens and 6 replies of about 120 tokens each time), files one return, and gets filters computed once per visit at low:

ItemPer month
Shopper messages and the return: 13 × 50 tokens × $1.75 / 1M$0.0011
Assistant replies, read but not learned from: 12 × 120 tokens × $1.75 / 1M$0.0025
Filters at low: 2 × $0.005$0.0100
Totalabout $0.014 per shopper

Questions are most of the bill: a cached answer is still billed as one query, so compute filters once per visit, not on every page view. More in Cost and speed.

Privacy and forgetting

What people buy and say while shopping can reveal more than their size. Be clear about what the assistant remembers, and keep it to shopping.

Pitfalls

Next: Personal assistants and companions · Privacy, consent and forgetting