showcase/shell-docs/src/content/docs/channels/history-and-transcripts.mdx
Channels exposes two different history systems:
Choose one based on the behavior you need. They are not interchangeable.
On each delivered turn, Intelligence fetches recent messages for that native conversation and seeds a fresh agent instance. The default history limit in the managed adapter is 20 messages.
The current inbound turn is not part of the fetched history. runAgent()
automatically injects it when prompt is omitted. Build an explicit prompt when
you need both the message text and hydrated attachments:
channel.onMessage(async ({ thread, message }) => {
await thread.runAgent({
prompt: message.contentParts?.length
? [
...(message.text
? [{ type: "text" as const, text: message.text }]
: []),
...message.contentParts,
]
: message.text,
});
});
Create a fresh agent for every conversationKey; the managed adapter assigns
the reconstructed messages to that instance. Sharing one mutable agent between
threads can leak or overwrite history.
thread.getMessages() returns a best-effort, oldest-first text view of recent
history:
const messages = await thread.getMessages();
const transcript = messages
.map((message) => `${message.isBot ? "Agent" : "User"}: ${message.text}`)
.join("\n");
On the managed path, history-fetch failures return [] instead of failing the
turn. The method is text-oriented: binary image, audio, and video content is
available to the agent through multimodal history, but is not reproduced as
binary data in ThreadMessage.
Use SDK transcripts when your application needs a user-centric record across
Slack and Teams. Configure both identity and transcripts; providing only
one is an error.
import { createChannel } from "@copilotkit/channels";
import { durableStateStore } from "./state-store.js";
const channel = createChannel({
name: required("CHANNEL_CODE"),
provider: "slack",
agent: makeAgent,
store: {
adapter: durableStateStore,
identity: async ({ author, message }) => {
const account = await accounts.findByExternalIdentity({
provider: message.platform,
externalUserId: author.id,
});
return account?.id ?? null;
},
transcripts: {
retention: "30d",
maxPerUser: 500,
},
},
});
import { createChannel } from "@copilotkit/channels";
import { durableStateStore } from "./state-store.js";
const channel = createChannel({
name: required("CHANNEL_CODE"),
provider: "teams",
agent: makeAgent,
store: {
adapter: durableStateStore,
identity: async ({ author, message }) => {
const account = await accounts.findByExternalIdentity({
provider: message.platform,
externalUserId: author.id,
});
return account?.id ?? null;
},
transcripts: {
retention: "30d",
maxPerUser: 500,
},
},
});
Provider user ids are not cross-platform identities. Resolve them to a stable
application user id, verified email, or another identity your application
controls. Returning null skips transcript storage for that turn.
The transcript list facet must be durable if this record must survive a restart.
The default MemoryStore is not sufficient.
runAgent bridge the transcript onceSet transcript: true to inject prior SDK transcript entries, append the
current user turn, run the agent, and capture its reply:
channel.onMessage(async ({ thread, message }) => {
await thread.runAgent({
prompt: message.text,
transcript: { limit: 20 },
});
});
Do not also call channel.transcripts.append() for the same user and assistant
turns. runAgent({ transcript: ... }) owns that bridge and manual appends would
duplicate the record.
channel.transcripts is available after the runner starts the Channel:
const recent = await channel.transcripts.list({
userKey: "usr_123",
limit: 50,
});
const { deleted } = await channel.transcripts.delete({
userKey: "usr_123",
});
list() returns entries oldest-first. You can filter by platform, thread id, or
role, but managed entries use the adapter platform "intelligence" rather than
the native "slack" or "teams" value. Resolve cross-provider identity through
userKey; do not filter managed entries by the native provider names.
delete() removes the SDK-owned transcript for that user; provider history and
any separate Intelligence retention policy remain independent.
| Need | Use |
|---|---|
| Continue the current Slack or Teams conversation | Managed conversation history |
| Inspect recent text in one provider thread | thread.getMessages() |
| Carry user context between Slack and Teams | SDK transcripts with a stable identity |
| Store workflow progress | thread.state() |
| Meet application deletion or retention requirements | A durable SDK transcript store plus your provider/Intelligence policies |
See the Transcripts reference and
persistence guide before enabling this in
production.