Back to Copilotkit

Transcripts

showcase/shell-docs/src/content/docs/bots/transcripts.mdx

1.62.16.3 KB
Original Source

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.

<OpsPlatformCTA variant="inline" title="Join the waitlist for managed Slack and Teams agents" body="This page shows how to map users and store transcripts yourself. Join the waitlist if you want CopilotKit Intelligence to manage identity, permissions, durable state, and Slack and Teams setup around the same agent runtime." ctaLabel="Join the waitlist" surface="docs_bots_transcripts_managed_agents_waitlist" href="https://www.copilotkit.ai/opentag-managed" />

<Steps> <Step> ### 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:

```ts title="bot.ts"
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.

<Callout type="info" title="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](/bots/persistence) for guidance.
</Callout>
</Step> <Step> ### The easy path — runAgent with transcript injection
Pass `transcript: true` to `thread.runAgent()` and the bot handles everything automatically:

1. Fetches the user's prior messages (up to 50 entries by default, oldest-first).
2. Injects them into the agent's context, labelled by platform and timestamp.
3. Appends the current user turn.
4. Captures the streamed assistant reply and appends it to the transcript.

```ts title="bot.ts"
bot.onMention(async ({ thread }) => {
  await thread.runAgent({ transcript: true }); // [!code highlight]
});
```

To control how many prior messages are injected, pass a limit instead:

```ts
await thread.runAgent({ transcript: { limit: 20 } });
```

<Callout type="warn" title="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.
</Callout>
</Step> <Step> ### 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.
</Step> <Step> ### 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:**

```ts
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: "[email protected]" });
```

**List a user's history** (oldest-first, with filters):

```ts
const history = await bot.transcripts.list({
  userKey: "[email protected]",
  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):

```ts
const result = await bot.transcripts.delete({ userKey: "[email protected]" });
console.log(`Deleted ${result.deleted} entries`);
```

<Callout type="info" title="createBot and Thread reference">
  The `identity`, `transcripts`, and `runAgent` options are documented in full at [createBot](/reference/bot/functions/createBot) and [Thread](/reference/bot/classes/Thread).
</Callout>
</Step> </Steps>

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.