Guides / Build well
Privacy, consent and forgetting
Memory means storing what people tell your product, so privacy is part of the design, not an afterthought. This guide is for developers who decide what goes into Recall: what to keep out, what to tell users, how to opt someone out of being modelled, where the data goes, and how to forget a fact, a person or a whole customer.
What to store, and what not
Recall learns from whatever you send it, and it does not detect or remove sensitive data for you. Decide before you send.
| Data | Send it? | How |
|---|---|---|
| The conversation about what your product does: preferences, plans, history with you | Yes | This is what memory is for. |
| Passwords, API keys, one-time codes, card numbers, bank details | No | Strip them before calling Recall. Once stored they appear in context, search and exports, and no answer needs them. |
| Identity numbers, exact addresses, anything you would not show a support agent | Only if your product needs it | Keep it in your own systems and send a reference instead. |
| Health, religion, sexual orientation, political views and other special categories | Only with explicit consent | Ask for consent first, record it, and check whether you need a data protection impact assessment. A coaching or health product is the typical case: see Health, fitness and coaching. |
| Other people mentioned in a conversation | Unavoidable | Facts are learned about the person speaking, but they can mention others. See what stays when you forget someone. |
| Your own ids, plan, channel | Yes, as metadata | Recall stores and filters on metadata but does not learn from it. Never put secrets there. |
Files you upload with uploadFile become messages, so the same rules apply to them.
Telling users
People should know that your product remembers them, and how to make it forget. Say in your privacy notice that conversations are stored to give your product memory, that an AI memory service and the AI model providers it uses process them, how long you keep them, and how to see and delete what is remembered. Something like this, adapted to your product and your own notice:
"We use Recall, an AI memory service, so the assistant remembers what you tell it across conversations. Your messages are stored in the EU (Frankfurt) and processed by AI model providers in the United States to build that memory; they are not used to train AI models. You can see and delete what the assistant remembers under Settings, Memory."
In the product itself, a "What I remember about you" page goes a long way: show the peer card and the facts (getCard and conclusions.list), and let people delete any of them. The access and forgetting examples below are the building blocks.
Opting a person out
The examples on this page follow a team assistant for one business customer: Sanne (user-8412) and a colleague (user-5120) talk to an assistant whose own lines Recall does not learn from.
import { Recall } from "@recall-memory/sdk";
const recall = new Recall({ workspaceId: "cust-1042" });
const sanne = recall.peer("user-8412");
const colleague = recall.peer("user-5120");
const assistant = recall.peer("assistant", { configuration: { observeMe: false } });
const standup = recall.session("chat-7731");
await standup.addMessages([
sanne.message("I prefer short answers, and I work on the billing team."),
colleague.message("I am covering the billing queue this week while Sanne is away."),
assistant.message("Got it. Billing questions go to you this week."),
]);
from datetime import datetime, timedelta, timezone
from recall_memory import PeerConfig, Recall, SessionPeerConfig
recall = Recall(workspace_id="cust-1042")
sanne = recall.peer("user-8412")
colleague = recall.peer("user-5120")
assistant = recall.peer("assistant", configuration=PeerConfig(observe_me=False))
standup = recall.session("chat-7731")
standup.add_messages(
[
sanne.message("I prefer short answers, and I work on the billing team."),
colleague.message("I am covering the billing queue this week while Sanne is away."),
assistant.message("Got it. Billing questions go to you this week."),
]
)
Some people will use your product but not want to be profiled. Set observeMe: false on their peer, or on their membership in one session. From then on nothing is learned from their messages, by Recall or by any other peer. The messages are still stored and still appear in context, so the conversation keeps working.
// Someone who does not want to be modelled: their messages are still stored and shown in
// context, but nothing is learned from them, by Recall or by any other peer. It applies to
// new messages; facts already learned stay until you delete them.
await colleague.setConfiguration({ observeMe: false });
// Or for one conversation only, such as a call they did not agree to have analysed.
const call = recall.session("call-2291", {
peers: { "user-8412": { observeMe: false }, assistant: {} },
});
await call.addMessages([sanne.message("Can you move my one-to-one to Friday?")]);
# Someone who does not want to be modelled: their messages are still stored and shown in
# context, but nothing is learned from them, by Recall or by any other peer. It applies to
# new messages; facts already learned stay until you delete them.
colleague.set_configuration(PeerConfig(observe_me=False))
# Or for one conversation only, such as a call they did not agree to have analysed.
call = recall.session(
"call-2291", peers=[(sanne, SessionPeerConfig(observe_me=False)), assistant]
)
call.add_messages([sanne.message("Can you move my one-to-one to Friday?")])
- Opting out is not deleting. Facts learned before the switch stay until you delete them, and the messages stay until you delete the session or the person. To store nothing, do not send their messages.
- One message off the record. A message whose configuration turns reasoning off (
reasoning: { enabled: false }) is stored for context but not learned from. - Through the Claude or ChatGPT connector. Remember automatically is on by default for each connected app; switch it off under API keys, Connected apps to have the app save only what you ask it to.
Least privilege
Keep account-wide keys on your own servers. Give each business customer's service a key scoped to its workspace, and code on a user's device a key scoped to that user's peer or session, never more. A peer or session key cannot delete a peer (it answers 401), so forgetting runs on your server. Keys are shown once and stored only as hashes; revoke them under API keys, and keys minted with POST /v3/keys are revoked together with the key that minted them. Designing workspaces, peers and sessions has a table of which key goes where.
Where the data goes
The database and the servers that run the API and dashboard are in Frankfurt (EU). To build memory and answer questions, text is processed by AI model providers in the United States, through OpenRouter, which we ask to use only model providers that do not collect the data they process. Customer data is not used to train models, and we do not use it for our own purposes: not to sell, not for advertising and not for profiling. Every connection uses HTTPS, and the database is encrypted at rest.
| Sub-processor | What it does | Location |
|---|---|---|
| Vercel Inc. | Runs the API, dashboard and background work, and serves the website | Frankfurt, Germany (EU); company in the USA |
| Neon | Database: stores all customer personal data | Frankfurt, Germany (EU); company in the USA |
| OpenRouter, Inc. | Passes requests to AI models for text generation and embeddings | USA |
| OpenAI (through OpenRouter) | Runs the AI models that build memory and answer questions, and computes embeddings | USA |
| Anthropic, PBC (through OpenRouter) | Runs the AI models that build memory and answer questions | USA |
Transfers to the United States rely on the EU-US Data Privacy Framework where the recipient is certified, and otherwise on the European Commission's Standard Contractual Clauses. The DPA applies automatically when you accept the terms (a countersigned copy is available on request). It commits us to tell account owners at least 30 days before a sub-processor is added or replaced, and to notify you of a personal data breach without undue delay, within 48 hours at the latest. Resend, Stripe, Cloudflare and GitHub also work for us, but do not receive your customers' data. Full text: Data processing agreement.
Export and access requests
Settings, Export all data in the dashboard downloads everything your account stores as one JSON file, free and as often as you like (see Export your data). When one person asks for a copy of their data, read it through the API instead: what they said, and what Recall concluded about them. If an agent keeps its own view of them, include it too (agent.conclusionsOf(user).list()).
// A person asks for a copy of their data: what they said, and what Recall concluded.
// (waitForIdle is for this demo; real apps rarely wait for learning to finish.)
await recall.waitForIdle();
const said = [];
for (const session of await (await sanne.sessions()).toArray()) {
const page = await session.messages({ filters: { peer_id: sanne.id } });
said.push(...(await page.toArray()));
}
const copy = {
said,
facts: await (await sanne.conclusions.list()).toArray(),
card: await sanne.getCard(),
};
# A person asks for a copy of their data: what they said, and what Recall concluded.
# (wait_for_idle is for this demo; real apps rarely wait for learning to finish.)
recall.wait_for_idle()
said = []
for session in sanne.sessions().to_list():
said += session.messages(filters={"peer_id": sanne.id}).to_list()
copy = {
"said": said,
"facts": sanne.conclusions.list().to_list(),
"card": sanne.get_card(),
}
If someone contacts us about data you sent, we forward the request to you rather than answering it ourselves, because you decide what happens to it.
Forgetting
| To forget | Call | What goes | What stays |
|---|---|---|---|
| One fact | peer.conclusions.delete(id), DELETE …/conclusions/{id} | The fact, from the database at once | The message it came from, the peer card (set it without that line), and earlier versions the night shift merged, which stay unused until the conversation or person is deleted |
| A conversation | session.delete(), DELETE …/sessions/{session} | Its messages, summaries and the facts learned in it, in the background, usually within minutes | Peer cards, and patterns the night shift drew across conversations |
| A person | peer.delete(), DELETE …/peers/{peer} | Their messages and memberships, every fact and card about them or from their point of view, the summaries that quoted them (rebuilt without them) and cached answers, at once | Other people's messages, and facts about other people that mention them |
| A business customer | Delete every session, then the workspace | Everything in the workspace | None of its content |
One fact. Show people what is remembered and delete what they pick. The peer card is kept separately, so update it too.
// "Forget that I'm on the billing team." Show people what Recall holds about them (a settings
// page works well) and delete what they pick.
const picked = copy.facts.find((fact) => fact.content.includes("billing team"));
if (picked) await sanne.conclusions.delete(picked.id);
// The peer card is kept separately: write it back without that line.
const card = copy.card ?? [];
const kept = card.filter((line) => !line.includes("billing team"));
if (kept.length < card.length) await sanne.setCard(kept);
# "Forget that I'm on the billing team." Show people what Recall holds about them (a settings
# page works well) and delete what they pick.
picked = next((f for f in copy["facts"] if "billing team" in f.content), None)
if picked:
sanne.conclusions.delete(picked.id)
# The peer card is kept separately: write it back without that line.
card = copy["card"] or []
kept = [line for line in card if "billing team" not in line]
if len(kept) < len(card):
sanne.set_card(kept)
A person. delete() needs a workspace or account key and is permanent. It returns what it removed; a 404 on a retry means the person is already gone. Then deal with what stays: facts about other people that name them, and other people's messages, which go only with their session.
// Erase the person: their messages and memberships, every fact and card about them or from
// their point of view, the summaries that quoted them, and cached answers.
const erased = await sanne.delete();
// Other people's messages stay, and so do facts about other people that mention her.
// Delete the ones that must go.
const facts = await (await colleague.conclusions.list()).toArray();
for (const fact of facts.filter((f) => f.content.includes("Sanne"))) {
await colleague.conclusions.delete(fact.id);
}
// To remove what others said in that conversation too, delete the whole session.
await standup.delete();
# Erase the person: their messages and memberships, every fact and card about them or from
# their point of view, the summaries that quoted them, and cached answers.
erased = sanne.delete()
# Other people's messages stay, and so do facts about other people that mention her.
# Delete the ones that must go.
for fact in colleague.conclusions.list().to_list():
if "Sanne" in fact.content:
colleague.conclusions.delete(fact.id)
# To remove what others said in that conversation too, delete the whole session.
standup.delete()
Recall only deletes what it holds. Delete your own copies too: your database, logs, analytics, and anything you sent to your own model provider.
Retention
Recall keeps conversations until you delete them; nothing expires by itself. Choose a period that fits your product, write it in your notice, and enforce it with a scheduled job. Deleting old sessions removes their messages, summaries and the facts learned in them; peer cards and patterns drawn across conversations stay, so for people who have stopped using your product altogether, delete the peer.
// Your own retention rule, run daily: delete conversations older than 90 days. Their
// messages, summaries and the facts learned in them go too.
const cutoff = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString();
const old = await recall.sessions({ filters: { created_at: { lt: cutoff } } });
for (const session of await old.toArray()) await session.delete();
# Your own retention rule, run daily: delete conversations older than 90 days. Their
# messages, summaries and the facts learned in them go too.
cutoff = (datetime.now(timezone.utc) - timedelta(days=90)).isoformat()
for session in recall.sessions(filters={"created_at": {"lt": cutoff}}).to_list():
session.delete()
When a customer leaves
With one workspace per business customer (see workspaces), closing their account is two steps: delete the sessions, then the workspace. Deleting a workspace that still has sessions answers 409. Deleting your whole Recall account, under Settings, removes all customer data from our database at once.
// The customer closes their account: delete every session first, then the workspace
// (deleting a workspace that still has sessions answers 409).
for (const session of await (await recall.sessions()).toArray()) await session.delete();
await recall.deleteWorkspace();
# The customer closes their account: delete every session first, then the workspace
# (deleting a workspace that still has sessions answers 409).
for session in recall.sessions().to_list():
session.delete()
recall.delete_workspace(recall.workspace_id)
Children
Recall itself is a service for businesses and developers, not meant for anyone under 16. If your product is used by children, the GDPR's rules on children's consent apply to you, and the age at which a child can consent differs between EU countries. In practice: get a parent's consent where the law requires it, store and learn only what the product needs (observeMe: false keeps a conversation as context without building a profile), keep retention short, and give parents the same access and deletion you give adults. Tutoring and education covers a learner product in detail.
Pitfalls
- Sending secrets. Strip passwords, codes and card numbers before they reach Recall; once stored they show up in context, search and exports.
- Treating opt-out as deletion.
observeMe: falsestops new learning. Stored messages and facts stay until you delete them. - Forgetting only the peer. Facts about others that mention the person, other people's messages and your own copies all survive
peer.delete(). - Deleting from the client. A peer or session key cannot delete a peer. Run deletion on your server with a workspace key.
- An account key in an app or a browser. Anyone who extracts it can read and delete every workspace. Ship only scoped keys.
- No retention rule. Recall keeps everything until you delete it. Decide how long, and automate it.
Next: Designing workspaces, peers and sessions · Cost and speed