본문으로 건너뛰기
cccskills
무료GitHub에 공개

convex-agents

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.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

연동 도구OpenAI
설치 방법 보기

포함된 파일(6)

  • SKILL.md8.7 KB
  • agents/openai.yaml91 B
  • assets/large-logo.png3.2 KB
  • assets/small-logo.svg1.2 KB
  • references/rag-and-workflows.md9.5 KB
  • references/tools-and-streaming.md7.7 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

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

npm install @convex-dev/agent ai @ai-sdk/openai zod
npx convex env set OPENAI_API_KEY sk-...
// 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

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

// 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

// 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);
  },
});
// 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.

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

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.

Common mistakes

MistakeWhy it breaksDo instead
Calling generateText in a mutationMutations cannot make network calls and must be deterministicSave the prompt with saveMessage, schedule an internalAction
v.id("threads") for thread idsThe threads table lives in the component, so ids are strings outside itUse v.string()
Skipping npx convex dev after app.use(agent)components.agent is not generated, so types failRun dev once before writing agent code
Returning the reply text from the action to the clientLoses the reply if the client disconnects, no reactivityLet clients read listUIMessages; the saved message shows up on its own
Tools without .describe() on argsThe model guesses what each field means and calls tools badlyDescribe every zod field
Tool handler with no return type annotationTypeScript circularity errors from ctx.runQueryAdd : Promise<...> to the handler
Exposing the message query with no auth checkAny client can read any threadCall an authorizeThreadAccess helper first
stopWhen left at the default with tools definedThe model calls a tool and stops without a text replySet 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

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Keeps a change scoped to what was asked. Flags extra features, refactors, and 'while we are here' edits, and moves them to a suggestions list instead of the diff. Use when a request is small and the agent is about to expand it, when a PRD grows beyond its problem statement, or when the user says 'just do what I asked'.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

waynesutton/builder-skills4062026년 9월 28일 업데이트

convex

무료

Routes Convex work to the right builder skill and covers the handful of rules that apply everywhere: validators on every function, withIndex over filter, idempotent mutations, internal functions for scheduling. Use when a task mentions Convex and no more specific skill matches, or as the first stop before picking one.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

waynesutton/builder-skills4062026년 9월 28일 업데이트

Production patterns for Convex apps and the rules the @convex-dev/eslint-plugin enforces: validators, indexes, idempotent mutations, avoiding OCC conflicts, thin function wrappers, error handling. Use when reviewing Convex code, asking whether a pattern is right, setting up ESLint, or fixing write conflicts and slow queries.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

waynesutton/builder-skills4062026년 9월 28일 업데이트

Creates reusable Convex components with defineComponent, a clean client wrapper, their own schema, and an npm publish setup. Use when extracting a feature into a package, building something for the Convex component directory, or when a component's functions are not showing up in the parent app.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

waynesutton/builder-skills4062026년 9월 28일 업데이트

Schedules work in Convex: cron jobs in convex/crons.ts, one off scheduled functions with runAfter and runAt, batching large jobs, and cancelling or inspecting the queue. Use when something needs to run on a timer, later, or in the background, or when a cron is not firing.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

waynesutton/builder-skills4062026년 9월 28일 업데이트

Handles files in Convex: upload URLs, storing blobs from actions, serving with getUrl or an HTTP action, metadata from the _storage table, and deletion. Use when users upload images or documents, when an action fetches a file from a third party, or when a file URL expires unexpectedly.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

waynesutton/builder-skills4062026년 9월 28일 업데이트

waynesutton의 스킬 모두 보기

이 스킬의 문제 신고