Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Convex Agents

ASecurity

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.

405 stars
0 votes
0 copies
0 views
Added 9/28/2026
ai-agentstypescriptbashreactapi

Works with

cliapi

Security Analysis

A96/100
mediumInstalls packages at runtime which could introduce malicious dependencies

Scanned 9/28/2026

Install to Claude Code

$npx -y skills add waynesutton/builder-skills --skill convex-agents --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Convex Agents?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Convex Agents
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/waynesutton-convex-agents-builder-skills/badge)](https://www.skillsdirectory.com/skills/waynesutton-convex-agents-builder-skills)

More formats (shields.io, HTML) on the badges page.

Files
SKILL.md
---
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

Attribution

waynesuttonwaynesutton
View sourceMore from waynesutton →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Ultra-compressed communication mode that cuts output tokens while keeping technical accuracy. Levels: lite, full, ultra and the wenyan variants. Use for /caveman, "caveman mode", "talk like caveman", "be brief" or "less tokens".

1074701 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

695601 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3351 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

691 votes

math-skill

A comprehensive mathematical reasoning skill for AI assistants — handles arithmetic to research-level problems with rigorous step-by-step reasoning, systematic verification, and transparent uncertainty handling

381 votes
View all in ai-agents →