History and transcripts

Distinguish Intelligence-managed conversation history from the optional SDK transcript store, and use each without duplicating turns.


Channels exposes two different history systems:

  • Intelligence-managed conversation history rebuilds the selected provider thread for the agent.
  • SDK transcripts create an application-owned, identity-keyed record that can span providers and conversations.

Choose one based on the behavior you need. They are not interchangeable.

Use managed conversation history#

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

Read the current provider thread#

thread.getMessages() returns a best-effort, oldest-first text view of recent history:

read-thread.ts
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.

Add cross-platform SDK transcripts#

Use SDK transcripts when your application needs a user-centric record across Slack and Teams. Configure top-level identifyUser and store.transcripts. When identity resolves to null, the transcript bridge skips personal reads and writes.

Resolve the current actor first

Read Identity and Memory before using a provider actor as an application user. A shared provider conversation has no personal owner, and each event resolves its actor independently.

channel.ts
import { createChannel } from "@copilotkit/channels";
import { durableStateStore } from "./state-store.js";

const channel = createChannel({
  name: required("CHANNEL_CODE"),
  identifyUser: async ({ provider, actor }) => {
    const account = await accounts.findByExternalIdentity({
      provider,
      externalUserId: actor.id,
    });
    return account ? { id: account.id, name: account.name } : null;
  },
  agent: makeAgent,
  store: {
    adapter: durableStateStore,
    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.

Let runAgent bridge the transcript once#

Set transcript: true to inject prior SDK transcript entries, append the current user turn, run the agent, and capture its reply:

channel.ts
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.

Query and delete the application record#

channel.transcripts is available after the runner starts the Channel:

transcripts.ts
const recent = await channel.transcripts.list({
  userId: "usr_123",
  limit: 50,
});

const { deleted } = await channel.transcripts.delete({
  userId: "usr_123",
});

list() returns entries oldest-first. You can filter by platform, thread id, or role. Managed entries use the native "slack" or "teams" provider. Resolve cross-provider identity through the application userId; use the platform filter only when you intentionally want one provider's entries.

delete() removes the SDK-owned transcript for that user; provider history and any separate Intelligence retention policy remain independent.

Pick the right source#

NeedUse
Continue the current Slack or Teams conversationManaged conversation history
Inspect recent text in one provider threadthread.getMessages()
Carry user context between Slack and TeamsSDK transcripts with a stable identity
Store workflow progressthread.state()
Meet application deletion or retention requirementsA durable SDK transcript store plus your provider/Intelligence policies

See the Transcripts reference and persistence guide before enabling this in production.