Transcripts
Give your bot a cross-platform memory that follows users across Slack, Teams, and any other channel — with automatic context injection and GDPR-ready delete.
Where you're starting from: a bot that forgets the user entirely between messages. The agent has no awareness that this Slack user is the same person who asked a related question on Teams yesterday, or what they said there.
Where you're headed: one cross-platform memory indexed by user identity, automatically injected into every agent run, and deletable on request.
Configure identity and transcripts together#
Transcripts are indexed by a user key — a stable, platform-independent identifier you derive from the inbound author. Without an identity resolver the bot can't map a Slack user to a Teams user (or even to their own previous Slack messages under a different thread), so identity and transcripts are configured as a pair:
import { createBot } from "@copilotkit/bot";
const bot = createBot({
adapters: [/* ... */],
agent: (threadId) => /* ... */,
store: {
// Uses MemoryStore by default (entries lost on restart).
// Pass adapter: <your StateStore> for durable transcript history.
// Map any platform's author object to a stable user key.
// Return null to opt a user out of transcript tracking.
identity: ({ author }) => author.email ?? null,
transcripts: {
retention: "30d", // auto-delete entries older than 30 days
maxPerUser: 200, // keep at most 200 entries per user key
},
},
});The author object is provided by the platform adapter — on Slack it includes the user's Slack profile fields including email if your bot has the users:read.email scope. Use whatever field gives you a stable, cross-platform identifier — email is the most common choice.
Transcripts require a durable store
MemoryStore does support the transcripts API for local development, but entries are lost on restart. Back the store with a durable StateStore implementation in production so transcript history actually persists. See Persistence for guidance.
The easy path — runAgent with transcript injection#
Pass transcript: true to thread.runAgent() and the bot handles everything automatically:
- Fetches the user's prior messages (up to 50 entries by default, oldest-first).
- Injects them into the agent's context, labelled by platform and timestamp.
- Appends the current user turn.
- Captures the streamed assistant reply and appends it to the transcript.
bot.onMention(async ({ thread }) => {
await thread.runAgent({ transcript: true });
});To control how many prior messages are injected, pass a limit instead:
await thread.runAgent({ transcript: { limit: 20 } });Don't also append manually
When transcript: true is set, runAgent appends both the user turn and the assistant reply automatically. Calling bot.transcripts.append() in the same handler doubles the entries. Use one or the other — the automatic path for the common case, the manual API when you need fine-grained control.
What the agent sees — platform-labelled context#
The injected history is formatted so the agent knows which platform each message came from. A user who asked a question on Teams and is now following up on Slack produces context like:
[teams | 2026-06-20T09:14:22Z] User: Can you pull the Q2 numbers from the dashboard?
[teams | 2026-06-20T09:14:35Z] Assistant: Here are the Q2 figures: …
[slack | 2026-06-22T14:03:10Z] User: Actually, can you do that again but filter to EMEA only?The agent sees the full cross-platform trail and can reference it naturally — "You asked on Teams last week about Q2 numbers — here's the EMEA slice." Each entry carries both platform and ts so you can build custom UIs, audit logs, or export pipelines on top of the raw data.
Manual control — append, list, and delete#
For cases where runAgent doesn't fit (proactive messages, non-agent flows, GDPR tooling), the full API is on bot.transcripts:
Append a message manually:
await bot.transcripts.append(thread, {
role: "user",
content: "Can you check on my open ticket?",
});
// Provide userKey explicitly if you're outside a request context:
await bot.transcripts.append(thread, msg, { userKey: "user@example.com" });List a user's history (oldest-first, with filters):
const history = await bot.transcripts.list({
userKey: "user@example.com",
limit: 50,
platforms: ["slack", "teams"], // omit to include all platforms
roles: ["user"], // omit to include assistant turns too
threadId: thread.id, // omit to include all threads
});
for (const entry of history) {
console.log(`[${entry.platform} | ${entry.ts}] ${entry.role}: ${entry.content}`);
}Delete all history for a user (GDPR right-to-erasure):
const result = await bot.transcripts.delete({ userKey: "user@example.com" });
console.log(`Deleted ${result.deleted} entries`);You've reached point B. The bot now maintains a single cross-platform memory per user. The agent receives labelled history from every platform on every run, users can ask follow-up questions across channels, and you can delete all stored data for a user with one call.