home / skills / sstobo / convex-skills / convex-agents-messages

convex-agents-messages skill

/convex-agents-messages

This skill manages messages in agent conversations, enabling saving, displaying, and retrieving UI messages for rich, responsive chat experiences.

npx playbooks add skill sstobo/convex-skills --skill convex-agents-messages

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
3.0 KB
---
name: "Convex Agents Messages"
description: "Sends, saves, retrieves, and manages messages within agent conversations. Use this when handling user messages, displaying conversation history, and working with UIMessages for rich rendering."
---

## Purpose

Messages represent individual exchanges in a conversation thread. This skill covers saving, retrieving, displaying, and managing messages.

## When to Use This Skill

- Displaying conversation history to users
- Saving user messages before generating responses
- Implementing optimistic message updates in UI
- Working with UIMessages for rich formatting
- Filtering tool messages from displayed history

## Retrieve Messages

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

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

## Display in React

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

function ChatComponent({ threadId }: { threadId: string }) {
  const { results } = useUIMessages(
    api.messages.listThreadMessages,
    { threadId },
    { initialNumItems: 20 }
  );

  return (
    <div>
      {results?.map((message) => (
        <div key={message.key}>{message.text}</div>
      ))}
    </div>
  );
}
```

## Save a Message Manually

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

export const saveUserMessage = mutation({
  args: { threadId: v.string(), prompt: v.string() },
  handler: async (ctx, { threadId, prompt }) => {
    const { messageId } = await saveMessage(ctx, components.agent, {
      threadId,
      prompt,
      skipEmbeddings: true,
    });
    return { messageId };
  },
});
```

## Optimistic Updates

Show messages immediately before they're saved:

```typescript
const sendMessage = useMutation(api.chat.generateResponse).withOptimisticUpdate(
  optimisticallySendMessage(api.messages.listThreadMessages)
);
```

## Delete Messages

```typescript
await myAgent.deleteMessage(ctx, { messageId });
await myAgent.deleteMessages(ctx, { messageIds });
await myAgent.deleteMessageRange(ctx, { threadId, startOrder, endOrder });
```

## UIMessage Type

Extended message with agent-specific fields:

```typescript
interface UIMessage {
  key: string;
  role: "user" | "assistant" | "system";
  text: string;
  status: "saved" | "streaming" | "finished" | "aborted";
  agentName?: string;
  _creationTime?: Date;
  parts?: MessagePart[];
}
```

## Key Principles

- **UIMessages vs MessageDocs**: Use UIMessages for UI, MessageDocs for backend
- **Message ordering**: Maintained automatically by order and stepOrder
- **Optimistic updates**: Better UX showing messages before save
- **Skip embeddings in mutations**: Use when saving in mutations

## Next Steps

- See **streaming** for real-time message updates
- See **files** for attaching media to messages
- See **threads** for managing message containers

Overview

This skill manages messages inside agent conversations: saving, retrieving, displaying, and deleting individual exchanges. It focuses on UIMessages for UI-friendly rendering while keeping backend MessageDocs for persistence. Use it to present conversation history, implement optimistic UI updates, and handle rich message parts.

How this skill works

The skill exposes helpers to list thread messages, save messages (optionally skipping embeddings), and delete single or ranges of messages. It provides a UI-focused message shape (UIMessages) with fields like role, text, status, agentName, and optional parts for rich rendering. Integrations include React hooks for subscribing to messages and mutation helpers that support optimistic updates.

When to use it

  • Displaying a conversation thread or chat history to end users
  • Saving user prompts before or during response generation
  • Implementing optimistic UI so messages appear immediately
  • Rendering rich messages with parts or agent-specific metadata
  • Filtering out tool or internal messages from user-facing history

Best practices

  • Use UIMessages in the frontend and keep MessageDocs for backend storage and indexing
  • Skip expensive embedding generation in synchronous mutations when not needed
  • Use optimistic updates for immediate UI feedback and reconcile with saved state on completion
  • Respect message ordering provided by order and stepOrder instead of re-sorting client-side
  • Subscribe to streaming updates for long-running responses rather than polling

Example use cases

  • A chat UI that lists the last N messages using a React hook and updates in real time
  • Saving a user prompt from a form mutation with skipEmbeddings to improve latency
  • Optimistically showing a new message while the server persists it, then replacing with saved state
  • Deleting a mistaken message or trimming a range of messages in a thread
  • Rendering assistant responses with parts (e.g., text + attachments) using the UIMessages shape

FAQ

When should I use UIMessages vs MessageDocs?

Use UIMessages for frontend display and UI needs; keep MessageDocs as the authoritative backend record for indexing, search, and persistence.

How do optimistic updates work with message saving?

Apply an optimistic update to show the message immediately, then call the saveMessage helper; once persisted, reconcile the optimistic item with the saved message returned by the server.