Back to Copilotkit

Transcripts

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

1.61.15.8 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.

<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";
import { createRedisStore } from "@copilotkit/bot-store-redis";

const bot = createBot({
  adapters: [/* ... */],
  agent: (threadId) => /* ... */,
  store: {
    adapter: createRedisStore({ url: process.env.REDIS_URL! }),
    // 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. Use Redis or PostgreSQL in production so transcript history actually persists. See [Persistence](/bots/persistence) for backend setup.
</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.