Guides / Use cases

Discord and Slack bots

For bots that live in a busy Discord server or Slack workspace, where hundreds of members talk in the same channels. Recall keeps every member apart, remembers each one across channels and weeks, and keeps what someone told the bot in private out of its public replies.

What memory adds

Without memory, the bot sees the message that mentions it and perhaps the last few lines of the channel. With it, the bot knows who is asking:

Without memoryWith Recall
"@bot any tips for the level 12 boss?"Generic tips for every class."As a frost mage, swap to a fire staff for this fight: the boss is immune to frost."
Two members both called AlexThe bot mixes them up.Each is a peer keyed by user id; their memories never meet.
A member tells the bot in a DM that they may leave the raid teamA bot with one shared history may repeat it in #general.The DM feeds only that member's own memory, which the bot reads in that DM and nowhere else.

Recall is memory, not the bot: your model writes the replies and your bot code talks to Discord or Slack. Recall has no Discord or Slack integration of its own; the bot calls the SDK.

How to model it

On the platformIn RecallWhy
A Discord server or Slack workspaceWorkspace, e.g. discord-912847561203948572Each community's memory stays apart, and a server that removes the bot is one delete.
Each memberPeer keyed by platform user id: discord-284711093281726464, slack-U04AB12CD34User ids never change. Display names change and repeat.
The botPeer bot with observeMe: falseRecall learns about members, not from the bot's own lines, which also cost less.
A channel or threadSession, e.g. ch-1129384756102938475Members join it with their first message; its context is the channel's recent history and a summary.
What the bot knows from public channelsThe bot's view: observeOthers: true in public channelsRead with peerPerspective: bot when replying in public.
A DM with the botA session of its own, without observeOthers for the botWhat a member says there never reaches the bot's public view.

Build it

Install the SDK (npm install @recall-memory/sdk or pip install recall-mem) and set RECALL_API_KEY. Create the bot peer and turn off learning from it before its first line is stored, then a session per channel in which the bot observes the others.

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

const recall = new Recall({ workspaceId: "discord-912847561203948572" });

// One workspace per server. Members are peers keyed by their platform user id, never by
// display name, which members change and share.
const member = (userId: string) => recall.peer(`discord-${userId}`);
// The bot learns about members, not from its own lines.
const bot = recall.peer("bot");
await bot.setConfiguration({ observeMe: false });

// A channel (or thread) is a session. In public channels the bot builds its own view of
// every member who speaks; members join the session with their first message.
const general = recall.session("ch-1129384756102938475", {
  peers: { bot: { observeOthers: true } },
});

Busy channels: batch, and only read when asked. Storing every message as it arrives is a request per message, and a busy server soon meets the rate limit. Buffer what nobody addressed to the bot and store it every few seconds in one request (up to 100 messages; the SDKs split larger batches). When someone mentions the bot, turn stores their message and returns the context for the reply in one request of a fraction of a second.

// Two members talk in #general.
const asker = member("284711093281726464");
const helper = member("391028475610293847");
// Messages not addressed to the bot wait in a buffer, stored in one request every few seconds.
const buffer = [
  asker.message("Anyone stuck on the level 12 boss? I play a frost mage."),
  helper.message("Frost will not work there, it is immune. Bring a fire staff."),
];
await general.addMessages(buffer.splice(0));

// A member mentions the bot: store the message and get context in one request, read through
// the bot's own view, so nothing said in a DM comes up in a public channel.
const mention = asker.message("@bot any tips for that boss with my build?");
const { context } = await general.turn([mention], {
  peerTarget: asker,
  peerPerspective: bot,
  tokens: 1500,
});
const prompt = context.toOpenAI(bot);
// Your model writes the reply from `prompt`; store it so the channel history is complete.
const answer = "As a frost mage, swap to a fire staff for this fight.";
await general.addMessages([bot.message(answer)]);

DMs stay private. In a DM the bot reads the member's own memory, which includes what they said in public. In public it reads only its own view, which was never told what was said in a DM.

// A DM is a session of its own, and the bot does not observe it: what a member says here goes
// only into their own memory, which the bot reads here (no perspective), never in public.
const dm = recall.session("dm-391028475610293847", { peers: [helper, bot] });
const reply = await dm.turn([helper.message("Between us, I may leave the raid team.")], {
  peerTarget: helper,
  tokens: 1500,
});

Ask about a member. For a /memory command or a moderator tool, ask the bot's view of one member in plain language. That is a model call of a few seconds, so keep it out of the reply path.

// Per-member memory: what has the bot learned about this member in public channels?
// (waitForIdle is for this demo; a bot never blocks on learning.)
await recall.waitForIdle();
const known = await bot.chat("What does this member play, and what do they need help with?", {
  target: asker,
  reasoningLevel: "minimal",
});

Forget on request.

// `/forget`, or a member who leaves the server: erase them from this server's memory.
const removed = await asker.delete();

Wiring it to discord.js or Slack Bolt

The platform code stays small. A sketch for discord.js, with one Recall client per server and the calls from above; yourModel stands for your own model call:

const clients = new Map(); // guild id -> Recall client for that server's workspace
async function recallFor(guildId) {
  if (!clients.has(guildId)) {
    const recall = new Recall({ workspaceId: `discord-${guildId}` });
    await recall.peer("bot").setConfiguration({ observeMe: false });
    clients.set(guildId, recall);
  }
  return clients.get(guildId);
}
const pending = new Map(); // channel id -> messages not stored yet

client.on("messageCreate", async (msg) => {
  if (msg.author.bot || !msg.guild) return;
  const recall = await recallFor(msg.guild.id);
  const author = recall.peer(`discord-${msg.author.id}`);
  const waiting = pending.get(msg.channelId) ?? [];
  pending.set(msg.channelId, waiting);
  waiting.push(author.message(msg.content, { createdAt: msg.createdAt }));
  if (!msg.mentions.has(client.user)) return; // a timer flushes `waiting` with addMessages

  const channel = recall.session(`ch-${msg.channelId}`, {
    peers: { bot: { observeOthers: true } },
  });
  const { context } = await channel.turn(waiting.splice(0), {
    peerTarget: author,
    peerPerspective: "bot",
    tokens: 1500,
  });
  const reply = await yourModel(context.toOpenAI("bot"));
  await msg.reply(reply);
  await channel.addMessages([recall.peer("bot").message(reply)]);
});

With Slack Bolt the same calls go in app.message (buffer) and app.event("app_mention") (turn); peer ids are slack- plus the user id, and the workspace is named after the Slack team id. Both platforms expect a slash command to be acknowledged within a few seconds, so acknowledge first and then ask.

Settings that matter

SettingUse
observeMe: false on the botRecall learns about members only. Set it before the bot's first line is stored.
observeOthers: true for the bot in public channelsBuilds the bot's own view of each member. Only the bot needs it: a session allows at most 10 members with it, so never give it to members.
peerPerspective: bot in public channelsRead a member through the bot's public view, so nothing from a DM comes up.
tokensThe context budget. 1,000 to 2,000 holds a busy channel's recent lines, a summary and what is known about the member.
Message createdAtPass the platform's timestamp when you flush a buffer, so each message keeps the time it was written.
configuration: { reasoning: { enabled: false } } on a sessionFor channels worth keeping as history but not learning from, such as a memes channel. Their messages count only what Recall reads of them.

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, and a question at minimal or low 2 to 4.5 seconds. Learning runs in the background and never holds up a reply. When you go over the rate limit (counted per API key), Recall answers 429 with Retry-After; the SDKs wait that long and retry, but batching is what keeps you under it.

Context, search and storage are included. You pay for learning, $1.75 per million message tokens with every message counting as at least 50, and for questions. Members who only read cost nothing. An active member who posts 200 short messages a month, gets 40 replies from the bot of about 80 tokens, and runs /memory 5 times:

ItemPer month
Member's messages: 200 × 50 tokens × $1.75 / 1M$0.0175
Bot replies, read but not learned from: 40 × 80 tokens × $1.75 / 1M$0.0056
Questions at minimal: 5 × $0.0008$0.0040
Context for every replyincluded
Totalabout $0.03 per active member, or $27 for 1,000

Short chat lines count as 50 tokens each, so a chatty member costs more than their word count suggests. More in Cost and speed.

Privacy and forgetting

Members do not expect a bot to remember them unless they are told. Pin a notice in the channels the bot reads, say it in the bot's profile, and give members two commands: /memory to see what the bot knows about them, and /forget to erase it. When a member asks, or leaves the server if your notice says so, delete erases their messages and every fact about them or from their view; other members' messages and facts about others that merely mention them stay. A member who never spoke answers 404: there is nothing to forget.

Do not store what the bot does not need: skip channels it is not used in, and never record moderators' private channels. Many Discord communities include teenagers; read Privacy, consent and forgetting before you build profiles of them. Recall stores data in Frankfurt; text is processed by AI model providers in the US through OpenRouter, restricted to providers that do not collect it, and never used for training. See the DPA and Forget a person.

Pitfalls

Next: Workplace assistants · Multi-agent systems