$npx -y skills add PolarCoding85/convex-agent-skillz --skill convex-agent-skillBuild AI agents with persistent threads, tool calling, and streaming on Convex. Use when implementing chat interfaces, AI assistants, multi-agent workflows, RAG systems, or any LLM-powered features with message history.
| 1 | # Convex Agent Component |
| 2 | |
| 3 | Build AI agents with persistent message history, tool calling, real-time streaming, and durable workflows. |
| 4 | |
| 5 | ## Installation |
| 6 | |
| 7 | ```bash |
| 8 | npm install @convex-dev/agent |
| 9 | ``` |
| 10 | |
| 11 | ```typescript |
| 12 | // convex/convex.config.ts |
| 13 | import { defineApp } from 'convex/server'; |
| 14 | import agent from '@convex-dev/agent/convex.config'; |
| 15 | |
| 16 | const app = defineApp(); |
| 17 | app.use(agent); |
| 18 | export default app; |
| 19 | ``` |
| 20 | |
| 21 | Run `npx convex dev` to generate component code before defining agents. |
| 22 | |
| 23 | ## Core Concepts |
| 24 | |
| 25 | ### Agent Definition |
| 26 | |
| 27 | ```typescript |
| 28 | // convex/agents.ts |
| 29 | import { Agent } from '@convex-dev/agent'; |
| 30 | import { openai } from '@ai-sdk/openai'; |
| 31 | import { components } from './_generated/api'; |
| 32 | |
| 33 | const supportAgent = new Agent(components.agent, { |
| 34 | name: 'Support Agent', |
| 35 | languageModel: openai.chat('gpt-4o-mini'), |
| 36 | textEmbeddingModel: openai.embedding('text-embedding-3-small'), // For vector search |
| 37 | instructions: 'You are a helpful support assistant.', |
| 38 | tools: { lookupAccount, createTicket }, |
| 39 | stopWhen: stepCountIs(10) // Or use maxSteps: 10 |
| 40 | }); |
| 41 | ``` |
| 42 | |
| 43 | ### Basic Usage (Two Approaches) |
| 44 | |
| 45 | **Approach 1: Direct generation (simpler)** |
| 46 | |
| 47 | ```typescript |
| 48 | import { createThread } from '@convex-dev/agent'; |
| 49 | |
| 50 | export const chat = action({ |
| 51 | args: { prompt: v.string() }, |
| 52 | handler: async (ctx, { prompt }) => { |
| 53 | const threadId = await createThread(ctx, components.agent); |
| 54 | const result = await agent.generateText(ctx, { threadId }, { prompt }); |
| 55 | return result.text; |
| 56 | } |
| 57 | }); |
| 58 | ``` |
| 59 | |
| 60 | **Approach 2: Thread object (more features)** |
| 61 | |
| 62 | ```typescript |
| 63 | export const chat = action({ |
| 64 | args: { prompt: v.string() }, |
| 65 | handler: async (ctx, { prompt }) => { |
| 66 | const { threadId, thread } = await agent.createThread(ctx); |
| 67 | const result = await thread.generateText({ prompt }); |
| 68 | return { threadId, text: result.text }; |
| 69 | } |
| 70 | }); |
| 71 | ``` |
| 72 | |
| 73 | ### Continue Existing Thread |
| 74 | |
| 75 | ```typescript |
| 76 | export const continueChat = action({ |
| 77 | args: { threadId: v.string(), prompt: v.string() }, |
| 78 | handler: async (ctx, { threadId, prompt }) => { |
| 79 | // Message history included automatically |
| 80 | const result = await agent.generateText(ctx, { threadId }, { prompt }); |
| 81 | return result.text; |
| 82 | } |
| 83 | }); |
| 84 | ``` |
| 85 | |
| 86 | ### Asynchronous Pattern (Recommended) |
| 87 | |
| 88 | Best practice: save message in mutation, generate response asynchronously. |
| 89 | |
| 90 | ```typescript |
| 91 | import { saveMessage } from '@convex-dev/agent'; |
| 92 | |
| 93 | // Step 1: Mutation saves message and schedules generation |
| 94 | export const sendMessage = mutation({ |
| 95 | args: { threadId: v.string(), prompt: v.string() }, |
| 96 | handler: async (ctx, { threadId, prompt }) => { |
| 97 | const { messageId } = await saveMessage(ctx, components.agent, { |
| 98 | threadId, |
| 99 | prompt |
| 100 | }); |
| 101 | await ctx.scheduler.runAfter(0, internal.chat.generateResponse, { |
| 102 | threadId, |
| 103 | promptMessageId: messageId |
| 104 | }); |
| 105 | return messageId; |
| 106 | } |
| 107 | }); |
| 108 | |
| 109 | // Step 2: Action generates response |
| 110 | export const generateResponse = internalAction({ |
| 111 | args: { threadId: v.string(), promptMessageId: v.string() }, |
| 112 | handler: async (ctx, { threadId, promptMessageId }) => { |
| 113 | await agent.generateText(ctx, { threadId }, { promptMessageId }); |
| 114 | } |
| 115 | }); |
| 116 | |
| 117 | // Shorthand for Step 2: |
| 118 | export const generateResponse = agent.asTextAction(); |
| 119 | ``` |
| 120 | |
| 121 | ## Generation Methods |
| 122 | |
| 123 | ```typescript |
| 124 | // Text generation |
| 125 | const result = await agent.generateText(ctx, { threadId }, { prompt }); |
| 126 | |
| 127 | // Structured output |
| 128 | const result = await agent.generateObject( |
| 129 | ctx, |
| 130 | { threadId }, |
| 131 | { |
| 132 | prompt: 'Extract user info', |
| 133 | schema: z.object({ name: z.string(), email: z.string() }) |
| 134 | } |
| 135 | ); |
| 136 | |
| 137 | // Stream text (see STREAMING.md) |
| 138 | const result = await agent.streamText(ctx, { threadId }, { prompt }); |
| 139 | |
| 140 | // Multiple messages |
| 141 | const result = await agent.generateText( |
| 142 | ctx, |
| 143 | { threadId }, |
| 144 | { |
| 145 | messages: [ |
| 146 | { role: 'user', content: 'Context message' }, |
| 147 | { role: 'user', content: 'Actual question' } |
| 148 | ] |
| 149 | } |
| 150 | ); |
| 151 | ``` |
| 152 | |
| 153 | ## Querying Messages |
| 154 | |
| 155 | ```typescript |
| 156 | import { listUIMessages, paginationOptsValidator } from '@convex-dev/agent'; |
| 157 | |
| 158 | export const listMessages = query({ |
| 159 | args: { threadId: v.string(), paginationOpts: paginationOptsValidator }, |
| 160 | handler: async (ctx, args) => { |
| 161 | return await listUIMessages(ctx, components.agent, args); |
| 162 | } |
| 163 | }); |
| 164 | ``` |
| 165 | |
| 166 | **React Hook:** |
| 167 | |
| 168 | ```typescript |
| 169 | import { useUIMessages } from '@convex-dev/agent/react'; |
| 170 | |
| 171 | const { results, status, loadMore } = useUIMessages( |
| 172 | api.chat.listMessages, |
| 173 | { threadId }, |
| 174 | { initialNumItems: 20 } |
| 175 | ); |
| 176 | ``` |
| 177 | |
| 178 | ## Agent Configuration Options |
| 179 | |
| 180 | ```typescript |
| 181 | const agent = new Agent(components.agent, { |
| 182 | name: 'Agent Name', |
| 183 | languageModel: openai.chat('gpt-4o-mini'), |
| 184 | textEmbeddingModel: openai.embedding('text-embedding-3-small'), |
| 185 | instructions: 'System prompt...', |
| 186 | tools: { |
| 187 | /* tools */ |
| 188 | }, |
| 189 | stopWhen: stepCountIs(10), // Or maxSteps: 10 |
| 190 | |
| 191 | // Context options (see |