convex-agents — скилл для Claude Code: что делает и как установить
Скилл учит ИИ строить агентов на компоненте @convex-dev/agent. Пригодится для чатов с историей тредов, RAG и вызова мутаций Convex как инструментов.
npx skills add waynesutton/builder-skills --skill convex-agents -a claude-codeЧто делает convex-agents
Скилл задаёт архитектуру агентов для бэкенда Convex. Он помогает генерировать код с тредами, реактивным списком сообщений через хук useUIMessages и инструментами createTool, которые вызывают мутации и запросы через контекст базы.
Главное архитектурное правило: генерация текста модели обязана запускаться внутри Convex action, а не в мутации. Мутация лишь сохраняет вопрос через saveMessage и ставит фоновую задачу через ctx.scheduler.runAfter. Если вызвать generateText прямо в мутации, код сразу сломается, так как мутации в Convex должны быть детерминированными и не могут делать сетевые вызовы.
Для работы требуются библиотеки @convex-dev/agent, ai, @ai-sdk/openai и zod, а также ключ OPENAI_API_KEY. Перед объявлением агентов нужно один раз запустить npx convex dev, чтобы сгенерировать components.agent.
Какие задачи решает
- подключить чат-ассистента с сохранением истории сообщений в Convex
- настроить вызов мутаций и запросов базы как инструментов модели через
createTool - реализовать реактивное отображение сообщений в интерфейсе через
useUIMessages - разбить запуск LLM на сохранение промпта в мутации и генерацию ответа в
internalAction
Примеры запросов агенту
- Добавь в проект агента поддержки на базе @convex-dev/agent с моделью gpt-4o-mini
- Создай инструмент searchOrders с валидацией через Zod, который ищет заказы пользователя
- Настрой мутацию sendMessage и фоновый internalAction для генерации ответа модели
Как установить convex-agents
1Через Skills CLI (Vercel)
npx skills add waynesutton/builder-skills --skill convex-agents -a claude-code2Вручную: положить файлы
~/.claude/skills/convex-agents/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/convex-agents/
1Через Skills CLI (Vercel)
npx skills add waynesutton/builder-skills --skill convex-agents -a codex2Вручную: положить файлы
.agents/skills/convex-agents/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add waynesutton/builder-skills --skill convex-agents -a cursor2Вручную: положить файлы
.cursor/skills/convex-agents/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install waynesutton/builder-skills convex-agents2Вручную: положить файлы
.github/skills/convex-agents/SKILL.md1Через Skills CLI (Vercel)
npx skills add waynesutton/builder-skills --skill convex-agents -a gemini-cli2Командой платформы
gemini skills install https://github.com/waynesutton/builder-skills --path skills/convex-agentsВручную: положить файлы
.devin/skills/convex-agents/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add waynesutton/builder-skills --skill convex-agents -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов
Из того же набора14
Похожие
Рядом в теме «Настройка агента: память, контекст, субагенты»
Все скиллы по теме «Настройка агента: память, контекст, субагенты» →Вопросы о convex-agents
Как установить convex-agents в Claude Code?
Выполните команду npx skills add waynesutton/builder-skills --skill convex-agents -a claude-code.
Что делает скилл convex-agents?
Он предоставляет шаблоны и правила для разработки ИИ-агентов на базе @convex-dev/agent с поддержкой тредов, вызова инструментов и стриминга.
Почему нельзя вызывать LLM напрямую внутри мутации Convex?
Мутации обязаны оставаться детерминированными и не могут выполнять сетевые запросы. Скилл настраивает схему, где мутация сохраняет промпт, а за генерацию отвечает internalAction.
Исходный текст
SKILL.md · Apache-2.0
---
name: convex-agents
description: Builds AI agents on the Convex agent component: threads, messages, tools that call queries and mutations, streaming, RAG with vector search, and workflows for multi step jobs. Use when adding a chat assistant, tool calling agent, or retrieval feature to a Convex app.
---
# Convex agents
Produces a chat or tool calling agent backed by `@convex-dev/agent`, with thread history stored in Convex and a reactive message list for the UI. The one rule: every LLM call runs inside an action. Mutations save the prompt and schedule the action; they never call a model.
## When to reach for this
- Adding a chat assistant with persistent conversation history
- Letting an LLM call your queries and mutations as tools
- Streaming a model reply to one or more clients
- Answering questions over your own documents (RAG)
- Chaining several LLM steps into a durable job that survives restarts
## Install and register
```bash
npm install @convex-dev/agent ai @ai-sdk/openai zod
npx convex env set OPENAI_API_KEY sk-...
```
```typescript
// convex/convex.config.ts
import { defineApp } from "convex/server";
import agent from "@convex-dev/agent/convex.config";
const app = defineApp();
app.use(agent);
export default app;
```
Run `npx convex dev` once so `components.agent` is generated before defining an agent.
## Define an agent
```typescript
// convex/agent.ts
import { Agent, stepCountIs } from "@convex-dev/agent";
import { openai } from "@ai-sdk/openai";
import { components } from "./_generated/api";
export const supportAgent = new Agent(components.agent, {
name: "Support Agent",
languageModel: openai.chat("gpt-4o-mini"),
instructions: "You are a support assistant. Answer briefly and cite docs when possible.",
// Lets the model call tools and then respond, up to 5 steps
stopWhen: stepCountIs(5),
});
```
`name` tags each saved message with the agent that wrote it. Everything except `name` can be overridden per call.
## Create a thread and generate a reply
Save the user prompt in a mutation, then schedule an internal action that generates the reply. Clients subscribed to the thread see the new message without the action returning anything.
```typescript
// convex/chat.ts
import { v } from "convex/values";
import { mutation, internalAction, QueryCtx, MutationCtx } from "./_generated/server";
import { components, internal } from "./_generated/api";
import { saveMessage } from "@convex-dev/agent";
import { supportAgent } from "./agent";
// Throws unless the signed in user owns the thread
async function authorizeThreadAccess(ctx: QueryCtx | MutationCtx, threadId: string) {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
const thread = await ctx.runQuery(components.agent.threads.getThread, { threadId });
if (!thread || thread.userId !== identity.subject) throw new Error("Unauthorized");
}
export const startThread = mutation({
args: {},
returns: v.string(),
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
const { threadId } = await supportAgent.createThread(ctx, { userId: identity.subject });
return threadId;
},
});
export const sendMessage = mutation({
args: { threadId: v.string(), prompt: v.string() },
returns: v.null(),
handler: async (ctx, args) => {
await authorizeThreadAccess(ctx, args.threadId);
const { messageId } = await saveMessage(ctx, components.agent, {
threadId: args.threadId,
prompt: args.prompt,
});
await ctx.scheduler.runAfter(0, internal.chat.generateReply, {
threadId: args.threadId,
promptMessageId: messageId,
});
return null;
},
});
export const generateReply = internalAction({
args: { threadId: v.string(), promptMessageId: v.string() },
returns: v.null(),
handler: async (ctx, args) => {
// promptMessageId makes retries safe: the same prompt is reused, never duplicated
await supportAgent.generateText(
ctx,
{ threadId: args.threadId },
{ promptMessageId: args.promptMessageId },
);
return null;
},
});
```
Thread ids are strings, not `v.id(...)`, since the table lives inside the component.
## List messages for the UI
```typescript
// convex/chat.ts (continued)
import { paginationOptsValidator } from "convex/server";
import { listUIMessages } from "@convex-dev/agent";
import { query } from "./_generated/server";
export const listMessages = query({
args: { threadId: v.string(), paginationOpts: paginationOptsValidator },
handler: async (ctx, args) => {
await authorizeThreadAccess(ctx, args.threadId);
return await listUIMessages(ctx, components.agent, args);
},
});
```
```tsx
// src/Chat.tsx
import { useUIMessages } from "@convex-dev/agent/react";
import { api } from "../convex/_generated/api";
function Chat({ threadId }: { threadId: string }) {
const { results, status, loadMore } = useUIMessages(
api.chat.listMessages,
{ threadId },
{ initialNumItems: 20 },
);
return (
<div>
{results.map((m) => (
<div key={m.key} data-role={m.role}>{m.text}</div>
))}
{status === "CanLoadMore" && <button onClick={() => loadMore(20)}>Older</button>}
</div>
);
}
```
`listUIMessages` merges tool calls and the assistant text that follows them into one `UIMessage`, which keeps rendering simple.
## One tool
Tools are defined with `createTool` and get a `ctx` that includes `runQuery`, `runMutation`, `userId`, and `threadId`. Annotate the handler return type to avoid circular type errors.
```typescript
// convex/tools.ts
import { createTool } from "@convex-dev/agent";
import { z } from "zod";
import { api } from "./_generated/api";
export const searchOrders = createTool({
description: "Find the current user's orders that match a search term",
args: z.object({
term: z.string().describe("Product name or order number to look for"),
}),
handler: async (ctx, args): Promise<Array<{ id: string; status: string }>> => {
return await ctx.runQuery(api.orders.search, { term: args.term });
},
});
```
Pass it to the agent with `tools: { searchOrders }` in the constructor or at the call site. For tool error handling, runtime tools with closures, and delta streaming to the client, open [references/tools-and-streaming.md](references/tools-and-streaming.md).
## Retrieval and multi step jobs
For embedding documents, searching them with `@convex-dev/rag` or a hand rolled vector index, injecting results into the prompt, and running several LLM steps as a durable `@convex-dev/workflow` job, open [references/rag-and-workflows.md](references/rag-and-workflows.md).
## Common mistakes
| Mistake | Why it breaks | Do instead |
| --- | --- | --- |
| Calling `generateText` in a mutation | Mutations cannot make network calls and must be deterministic | Save the prompt with `saveMessage`, schedule an `internalAction` |
| `v.id("threads")` for thread ids | The threads table lives in the component, so ids are strings outside it | Use `v.string()` |
| Skipping `npx convex dev` after `app.use(agent)` | `components.agent` is not generated, so types fail | Run dev once before writing agent code |
| Returning the reply text from the action to the client | Loses the reply if the client disconnects, no reactivity | Let clients read `listUIMessages`; the saved message shows up on its own |
| Tools without `.describe()` on args | The model guesses what each field means and calls tools badly | Describe every zod field |
| Tool handler with no return type annotation | TypeScript circularity errors from `ctx.runQuery` | Add `: Promise<...>` to the handler |
| Exposing the message query with no auth check | Any client can read any thread | Call an `authorizeThreadAccess` helper first |
| `stopWhen` left at the default with tools defined | The model calls a tool and stops without a text reply | Set `stopWhen: stepCountIs(n)` with `n > 1` |
## Checklist
- [ ] `app.use(agent)` in `convex.config.ts` and `npx convex dev` has run
- [ ] Provider key stored with `npx convex env set`, never in client code
- [ ] `Agent` has a `name`, `languageModel`, and `instructions`
- [ ] Prompts saved in a mutation, replies generated in an `internalAction` with `promptMessageId`
- [ ] Thread and message ids typed as `v.string()`
- [ ] Message list query checks thread ownership before calling `listUIMessages`
- [ ] Every tool arg has a zod `.describe()` and the handler has a return type
- [ ] `stopWhen: stepCountIs(n)` set when tools are in play
- [ ] Client uses `useUIMessages` rather than reading action return values
## Docs
- https://docs.convex.dev/llms.txt
- https://docs.convex.dev/agents
- https://docs.convex.dev/agents/agent-usage
- https://docs.convex.dev/agents/tools
- https://www.convex.dev/components/agent
© waynesutton · Apache-2.0 · waynesutton/builder-skills