# Source: https://docs.convex.dev/agents/messages.md

# Messages

The Agent component stores message and [thread](/agents/threads.md) history to enable conversations between humans and agents.

To see how humans can act as agents, see [Human Agents](/agents/human-agents.md).

## Retrieving messages[​](#retrieving-messages "Direct link to Retrieving messages")

For clients to show messages, you need to expose a query that returns the messages. For streaming, see [retrieving streamed deltas](/agents/streaming.md#retrieving-streamed-deltas) for a modified version of this query.

See [chat/basic.ts](https://github.com/get-convex/agent/blob/main/example/convex/chat/basic.ts) for the server-side code, and [chat/streaming.ts](https://github.com/get-convex/agent/blob/main/example/convex/chat/streaming.ts) for the streaming example.

```
import { paginationOptsValidator } from "convex/server";
import { v } from "convex/values";
import { listUIMessages } from "@convex-dev/agent";
import { components } from "./_generated/api";

export const listThreadMessages = query({
  args: { threadId: v.string(), paginationOpts: paginationOptsValidator },
  handler: async (ctx, args) => {
    await authorizeThreadAccess(ctx, threadId);

    const paginated = await listUIMessages(ctx, components.agent, args);

    // Here you could filter out / modify the documents
    return paginated;
  },
});
```

Note: Above we used `listUIMessages`, which returns UIMessages, specifically the Agent extension that includes some extra fields like order, status, etc. UIMessages combine multiple MessageDocs into a single UIMessage when there are multiple tool calls followed by an assistant message, making it easy to build UIs that work with the various "parts" on the UIMessage.

If you want to get MessageDocs, you can use `listMessages` instead.

## Showing messages in React[​](#showing-messages-in-react "Direct link to Showing messages in React")

See [ChatStreaming.tsx](https://github.com/get-convex/agent/blob/main/example/ui/chat/ChatStreaming.tsx) for a streaming example, or [ChatBasic.tsx](https://github.com/get-convex/agent/blob/main/example/ui/chat/ChatBasic.tsx) for a non-streaming example.

### `useUIMessages` hook[​](#useuimessages-hook "Direct link to useuimessages-hook")

The crux is to use the `useUIMessages` hook. For streaming, pass in `stream: true` to the hook.

```
import { api } from "../convex/_generated/api";
import { useUIMessages } from "@convex-dev/agent/react";

function MyComponent({ threadId }: { threadId: string }) {
  const { results, status, loadMore } = useUIMessages(
    api.chat.streaming.listMessages,
    { threadId },
    { initialNumItems: 10 /* stream: true */ },
  );
  return (
    <div>
      {results.map((message) => (
        <div key={message.key}>{message.text}</div>
      ))}
    </div>
  );
}
```

Note: If you want to work with MessageDocs instead of UIMessages, you can use the older `useThreadMessages` hook instead. However, working with UIMessages enables richer streaming capabilities, such as status on whether the agent is actively reasoning.

### UIMessage type[​](#uimessage-type "Direct link to UIMessage type")

The Agent component extends the AI SDK's `UIMessage` type to provide convenient metadata for rendering messages.

The core UIMessage type from the AI SDK is:

* `parts` is an array of parts (e.g. "text", "file", "image", "toolCall", "toolResult")
* `content` is a string of the message content.
* `role` is the role of the message (e.g. "user", "assistant", "system").

The helper adds these additional fields:

* `key` is a unique identifier for the message.
* `order` is the order of the message in the thread.
* `stepOrder` is the step order of the message in the thread.
* `status` is the status of the message (or "streaming").
* `agentName` is the name of the agent that generated the message.
* `text` is the text of the message.
* `_creationTime` is the timestamp of the message. For streaming messages, it's currently assigned to the current time on the streaming client.

To reference these, ensure you're importing `UIMessage` from `@convex-dev/agent`.

#### `toUIMessages` helper[​](#touimessages-helper "Direct link to touimessages-helper")

`toUIMessages` is a helper function that transforms MessageDocs into AI SDK "UIMessage"s. This is a convenient data model for displaying messages.

If you are using `useThreadMessages` for instance, you can convert the messages to UIMessages like this:

```
import { toUIMessages, type UIMessage } from "@convex-dev/agent";

...
const { results } = useThreadMessages(...);
const uiMessages = toUIMessages(results);
```

### Optimistic updates for sending messages[​](#optimistic-updates-for-sending-messages "Direct link to Optimistic updates for sending messages")

The `optimisticallySendMessage` function is a helper function for sending a message, so you can optimistically show a message in the message list until the mutation has completed on the server.

Pass in the query that you're using to list messages, and it will insert the ephemeral message at the top of the list.

```
const sendMessage = useMutation(
  api.streaming.streamStoryAsynchronously,
).withOptimisticUpdate(
  optimisticallySendMessage(api.streaming.listThreadMessages),
);
```

If your arguments don't include `{ threadId, prompt }` then you can use it as a helper function in your optimistic update:

```
import { optimisticallySendMessage } from "@convex-dev/agent/react";

const sendMessage = useMutation(
  api.chatStreaming.streamStoryAsynchronously,
).withOptimisticUpdate(
  (store, args) => {
    optimisticallySendMessage(api.chatStreaming.listThreadMessages)(store, {
      threadId:
      prompt: /* change your args into the user prompt. */,
    })
  }
);
```

## Saving messages[​](#saving-messages "Direct link to Saving messages")

By default, the Agent will save messages to the database automatically when you provide them as a prompt, as well as all generated messages.

However, it is useful to save the prompt message ahead of time and use the `promptMessageId` to continue the conversation. See [Agent Usage](/agents/agent-usage.md) for more details.

You can save messages to the database manually using `saveMessage` or `saveMessages`, either on the Agent class or as a direct function call.

* You can pass a `prompt` or a full `message` (`ModelMessage` type)
* The `metadata` argument is optional and allows you to provide more details, such as `sources`, `reasoningDetails`, `usage`, `warnings`, `error`, etc.

```
const { messageId } = await saveMessage(ctx, components.agent, {
  threadId,
  userId,
  message: { role: "user", content: "The user message" },
});
```

Note: when calling `agent.generateText` with the raw prompt, embeddings are generated automatically for vector search (if you have a text embedding model configured). Similarly with `agent.saveMessage` when calling from an action. However, if you're saving messages in a mutation, where calling an LLM is not possible, it will generate them automatically if `generateText` receives a `promptMessageId` that lacks an embedding (and you have a text embedding model configured).

### Without the Agent class:[​](#without-the-agent-class "Direct link to Without the Agent class:")

Note: If you aren't using the Agent class with a text embedding model set, you need to pass an `embedding` if you want to save it at the same time.

```
import { saveMessage } from "@convex-dev/agent";

const { messageId } = await saveMessage(ctx, components.agent, {
  threadId,
  userId,
  message: { role: "assistant", content: result },
  metadata: [{ reasoning, usage, ... }] // See MessageWithMetadata type
  agentName: "my-agent",
  embedding: { vector: [0.1, 0.2, ...], model: "text-embedding-3-small" },
});
```

### Using the Agent class:[​](#using-the-agent-class "Direct link to Using the Agent class:")

```
const { messageId } = await agent.saveMessage(ctx, {
  threadId,
  userId,
  prompt,
  metadata,
});
```

```
const { messages } = await agent.saveMessages(ctx, {
  threadId, userId,
  messages: [{ role, content }],
  metadata: [{ reasoning, usage, ... }] // See MessageWithMetadata type
});
```

If you are saving the message in a mutation and you have a text embedding model set, pass `skipEmbeddings: true`. The embeddings for the message will be generated lazily if the message is used as a prompt. Or you can provide an embedding upfront if it's available, or later explicitly generate them using `agent.generateEmbeddings`.

## Configuring the storage of messages[​](#configuring-the-storage-of-messages "Direct link to Configuring the storage of messages")

Generally the defaults are fine, but if you want to pass in multiple messages and have them all saved (vs. just the last one), or avoid saving any input or output messages, you can pass in a `storageOptions` object, either to the Agent constructor or per-message.

The use-case for passing in multiple messages but not saving them is if you want to include some extra messages for context to the LLM, but only the last message is the user's actual request. e.g. `messages = [...messagesFromRag, messageFromUser]`. The default is to save the prompt and all output messages.

```
const result = await thread.generateText({ messages }, {
  storageOptions: {
    saveMessages: "all" | "none" | "promptAndOutput";
  },
});
```

## Message ordering[​](#message-ordering "Direct link to Message ordering")

Each message has `order` and `stepOrder` fields, which are incrementing integers specific to a thread.

When `saveMessage` or `generateText` is called, the message is added to the thread's next `order` with a `stepOrder` of 0.

As response message(s) are generated in response to that message, they are added at the same `order` with the next `stepOrder`.

To associate a response message with a previous message, you can pass in the `promptMessageId` to `generateText` and others.

Note: if the `promptMessageId` is not the latest message in the thread, the context for the message generation will not include any messages following the `promptMessageId`.

## Deleting messages[​](#deleting-messages "Direct link to Deleting messages")

You can delete messages by their `_id` (returned from `saveMessage` or `generateText`) or `order` / `stepOrder`.

By ID:

```
await agent.deleteMessage(ctx, { messageId });
// batch delete
await agent.deleteMessages(ctx, { messageIds });
```

By order (start is inclusive, end is exclusive):

```
// Delete all messages with the same order as a given message:
await agent.deleteMessageRange(ctx, {
  threadId,
  startOrder: message.order,
  endOrder: message.order + 1,
});
// Delete all messages with order 1 or 2.
await agent.deleteMessageRange(ctx, { threadId, startOrder: 1, endOrder: 3 });
// Delete all messages with order 1 and stepOrder 2-4
await agent.deleteMessageRange(ctx, {
  threadId,
  startOrder: 1,
  startStepOrder: 2,
  endOrder: 2,
  endStepOrder: 5,
});
```

## Other utilities:[​](#other-utilities "Direct link to Other utilities:")

```
import { ... } from "@convex-dev/agent";
```

* `serializeDataOrUrl` is a utility function that serializes an AI SDK `DataContent` or `URL` to a Convex-serializable format.
* `filterOutOrphanedToolMessages` is a utility function that filters out tool call messages that don't have a corresponding tool result message.
* `extractText` is a utility function that extracts text from a `ModelMessage`-like object.

### Validators and types[​](#validators-and-types "Direct link to Validators and types")

There are types to validate and provide types for various values

```
import { ... } from "@convex-dev/agent";
```

* `vMessage` is a validator for a `ModelMessage`-like object (with a `role` and `content` field e.g.).
* `MessageDoc` and `vMessageDoc` are the types for a message (which includes a `.message` field with the `vMessage` type).
* `Thread` is the type of a thread returned from `continueThread` or `createThread`.
* `ThreadDoc` and `vThreadDoc` are the types for thread metadata.
* `AgentComponent` is the type of the installed component (e.g. `components.agent`).
* `ToolCtx` is the `ctx` type for calls to `createTool` tools.