Claude Agent Skill · by Waynesutton

Convex Agents

Install Convex Agents skill for Claude Code from waynesutton/convexskills.

convexagentsaillmtoolsragworkflows
Install
Terminal · npx
$npx skills add https://github.com/waynesutton/convexskills --skill convex-agents
Works with Paperclip

How Convex Agents fits into a Paperclip company.

Convex Agents drops into any Paperclip agent that handles convex and agents work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md516 lines
Expand
---name: convex-agentsdisplayName: Convex Agentsdescription: Building AI agents with the Convex Agent component including thread management, tool integration, streaming responses, RAG patterns, and workflow orchestrationversion: 1.0.0author: Convextags: [convex, agents, ai, llm, tools, rag, workflows]--- # Convex Agents Build persistent, stateful AI agents with Convex including thread management, tool integration, streaming responses, RAG patterns, and workflow orchestration. ## Documentation Sources Before implementing, do not assume; fetch the latest documentation: - Primary: https://docs.convex.dev/ai- Convex Agent Component: https://www.npmjs.com/package/@convex-dev/agent- For broader context: https://docs.convex.dev/llms.txt ## Instructions ### Why Convex for AI Agents - **Persistent State** - Conversation history survives restarts- **Real-time Updates** - Stream responses to clients automatically- **Tool Execution** - Run Convex functions as agent tools- **Durable Workflows** - Long-running agent tasks with reliability- **Built-in RAG** - Vector search for knowledge retrieval ### Setting Up Convex Agent ```bashnpm install @convex-dev/agent ai openai``` ```typescript// convex/agent.tsimport { Agent } from "@convex-dev/agent";import { components } from "./_generated/api";import { OpenAI } from "openai"; const openai = new OpenAI(); export const agent = new Agent(components.agent, {  chat: openai.chat,  textEmbedding: openai.embeddings,});``` ### Thread Management ```typescript// convex/threads.tsimport { mutation, query } from "./_generated/server";import { v } from "convex/values";import { agent } from "./agent"; // Create a new conversation threadexport const createThread = mutation({  args: {    userId: v.id("users"),    title: v.optional(v.string()),  },  returns: v.id("threads"),  handler: async (ctx, args) => {    const threadId = await agent.createThread(ctx, {      userId: args.userId,      metadata: {        title: args.title ?? "New Conversation",        createdAt: Date.now(),      },    });    return threadId;  },}); // List user's threadsexport const listThreads = query({  args: { userId: v.id("users") },  returns: v.array(v.object({    _id: v.id("threads"),    title: v.string(),    lastMessageAt: v.optional(v.number()),  })),  handler: async (ctx, args) => {    return await agent.listThreads(ctx, {      userId: args.userId,    });  },}); // Get thread messagesexport const getMessages = query({  args: { threadId: v.id("threads") },  returns: v.array(v.object({    role: v.string(),    content: v.string(),    createdAt: v.number(),  })),  handler: async (ctx, args) => {    return await agent.getMessages(ctx, {      threadId: args.threadId,    });  },});``` ### Sending Messages and Streaming Responses ```typescript// convex/chat.tsimport { action } from "./_generated/server";import { v } from "convex/values";import { agent } from "./agent";import { internal } from "./_generated/api"; export const sendMessage = action({  args: {    threadId: v.id("threads"),    message: v.string(),  },  returns: v.null(),  handler: async (ctx, args) => {    // Add user message to thread    await ctx.runMutation(internal.chat.addUserMessage, {      threadId: args.threadId,      content: args.message,    });     // Generate AI response with streaming    const response = await agent.chat(ctx, {      threadId: args.threadId,      messages: [{ role: "user", content: args.message }],      stream: true,      onToken: async (token) => {        // Stream tokens to client via mutation        await ctx.runMutation(internal.chat.appendToken, {          threadId: args.threadId,          token,        });      },    });     // Save complete response    await ctx.runMutation(internal.chat.saveResponse, {      threadId: args.threadId,      content: response.content,    });     return null;  },});``` ### Tool Integration Define tools that agents can use: ```typescript// convex/tools.tsimport { tool } from "@convex-dev/agent";import { v } from "convex/values";import { api } from "./_generated/api"; // Tool to search knowledge baseexport const searchKnowledge = tool({  name: "search_knowledge",  description: "Search the knowledge base for relevant information",  parameters: v.object({    query: v.string(),    limit: v.optional(v.number()),  }),  handler: async (ctx, args) => {    const results = await ctx.runQuery(api.knowledge.search, {      query: args.query,      limit: args.limit ?? 5,    });    return results;  },}); // Tool to create a taskexport const createTask = tool({  name: "create_task",  description: "Create a new task for the user",  parameters: v.object({    title: v.string(),    description: v.optional(v.string()),    dueDate: v.optional(v.string()),  }),  handler: async (ctx, args) => {    const taskId = await ctx.runMutation(api.tasks.create, {      title: args.title,      description: args.description,      dueDate: args.dueDate ? new Date(args.dueDate).getTime() : undefined,    });    return { success: true, taskId };  },}); // Tool to get weatherexport const getWeather = tool({  name: "get_weather",  description: "Get current weather for a location",  parameters: v.object({    location: v.string(),  }),  handler: async (ctx, args) => {    const response = await fetch(      `https://api.weather.com/current?location=${encodeURIComponent(args.location)}`    );    return await response.json();  },});``` ### Agent with Tools ```typescript// convex/assistant.tsimport { action } from "./_generated/server";import { v } from "convex/values";import { agent } from "./agent";import { searchKnowledge, createTask, getWeather } from "./tools"; export const chat = action({  args: {    threadId: v.id("threads"),    message: v.string(),  },  returns: v.string(),  handler: async (ctx, args) => {    const response = await agent.chat(ctx, {      threadId: args.threadId,      messages: [{ role: "user", content: args.message }],      tools: [searchKnowledge, createTask, getWeather],      systemPrompt: `You are a helpful assistant. You have access to tools to:        - Search the knowledge base for information        - Create tasks for the user        - Get weather information        Use these tools when appropriate to help the user.`,    });     return response.content;  },});``` ### RAG (Retrieval Augmented Generation) ```typescript// convex/knowledge.tsimport { mutation, query } from "./_generated/server";import { v } from "convex/values";import { agent } from "./agent"; // Add document to knowledge baseexport const addDocument = mutation({  args: {    title: v.string(),    content: v.string(),    metadata: v.optional(v.object({      source: v.optional(v.string()),      category: v.optional(v.string()),    })),  },  returns: v.id("documents"),  handler: async (ctx, args) => {    // Generate embedding    const embedding = await agent.embed(ctx, args.content);     return await ctx.db.insert("documents", {      title: args.title,      content: args.content,      embedding,      metadata: args.metadata ?? {},      createdAt: Date.now(),    });  },}); // Search knowledge baseexport const search = query({  args: {    query: v.string(),    limit: v.optional(v.number()),  },  returns: v.array(v.object({    _id: v.id("documents"),    title: v.string(),    content: v.string(),    score: v.number(),  })),  handler: async (ctx, args) => {    const results = await agent.search(ctx, {      query: args.query,      table: "documents",      limit: args.limit ?? 5,    });     return results.map((r) => ({      _id: r._id,      title: r.title,      content: r.content,      score: r._score,    }));  },});``` ### Workflow Orchestration ```typescript// convex/workflows.tsimport { action, internalMutation } from "./_generated/server";import { v } from "convex/values";import { agent } from "./agent";import { internal } from "./_generated/api"; // Multi-step research workflowexport const researchTopic = action({  args: {    topic: v.string(),    userId: v.id("users"),  },  returns: v.id("research"),  handler: async (ctx, args) => {    // Create research record    const researchId = await ctx.runMutation(internal.workflows.createResearch, {      topic: args.topic,      userId: args.userId,      status: "searching",    });     // Step 1: Search for relevant documents    const searchResults = await agent.search(ctx, {      query: args.topic,      table: "documents",      limit: 10,    });     await ctx.runMutation(internal.workflows.updateStatus, {      researchId,      status: "analyzing",    });     // Step 2: Analyze and synthesize    const analysis = await agent.chat(ctx, {      messages: [{        role: "user",        content: `Analyze these sources about "${args.topic}" and provide a comprehensive summary:\n\n${          searchResults.map((r) => r.content).join("\n\n---\n\n")        }`,      }],      systemPrompt: "You are a research assistant. Provide thorough, well-cited analysis.",    });     // Step 3: Generate key insights    await ctx.runMutation(internal.workflows.updateStatus, {      researchId,      status: "summarizing",    });     const insights = await agent.chat(ctx, {      messages: [{        role: "user",        content: `Based on this analysis, list 5 key insights:\n\n${analysis.content}`,      }],    });     // Save final results    await ctx.runMutation(internal.workflows.completeResearch, {      researchId,      analysis: analysis.content,      insights: insights.content,      sources: searchResults.map((r) => r._id),    });     return researchId;  },});``` ## Examples ### Complete Chat Application Schema ```typescript// convex/schema.tsimport { defineSchema, defineTable } from "convex/server";import { v } from "convex/values"; export default defineSchema({  threads: defineTable({    userId: v.id("users"),    title: v.string(),    lastMessageAt: v.optional(v.number()),    metadata: v.optional(v.any()),  }).index("by_user", ["userId"]),   messages: defineTable({    threadId: v.id("threads"),    role: v.union(v.literal("user"), v.literal("assistant"), v.literal("system")),    content: v.string(),    toolCalls: v.optional(v.array(v.object({      name: v.string(),      arguments: v.any(),      result: v.optional(v.any()),    }))),    createdAt: v.number(),  }).index("by_thread", ["threadId"]),   documents: defineTable({    title: v.string(),    content: v.string(),    embedding: v.array(v.float64()),    metadata: v.object({      source: v.optional(v.string()),      category: v.optional(v.string()),    }),    createdAt: v.number(),  }).vectorIndex("by_embedding", {    vectorField: "embedding",    dimensions: 1536,  }),});``` ### React Chat Component ```typescriptimport { useQuery, useMutation, useAction } from "convex/react";import { api } from "../convex/_generated/api";import { useState, useRef, useEffect } from "react"; function ChatInterface({ threadId }: { threadId: Id<"threads"> }) {  const messages = useQuery(api.threads.getMessages, { threadId });  const sendMessage = useAction(api.chat.sendMessage);  const [input, setInput] = useState("");  const [sending, setSending] = useState(false);  const messagesEndRef = useRef<HTMLDivElement>(null);   useEffect(() => {    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });  }, [messages]);   const handleSend = async (e: React.FormEvent) => {    e.preventDefault();    if (!input.trim() || sending) return;     const message = input.trim();    setInput("");    setSending(true);     try {      await sendMessage({ threadId, message });    } finally {      setSending(false);    }  };   return (    <div className="chat-container">      <div className="messages">        {messages?.map((msg, i) => (          <div key={i} className={`message ${msg.role}`}>            <strong>{msg.role === "user" ? "You" : "Assistant"}:</strong>            <p>{msg.content}</p>          </div>        ))}        <div ref={messagesEndRef} />      </div>       <form onSubmit={handleSend} className="input-form">        <input          value={input}          onChange={(e) => setInput(e.target.value)}          placeholder="Type your message..."          disabled={sending}        />        <button type="submit" disabled={sending || !input.trim()}>          {sending ? "Sending..." : "Send"}        </button>      </form>    </div>  );}``` ## Best Practices - Never run `npx convex deploy` unless explicitly instructed- Never run any git commands unless explicitly instructed- Store conversation history in Convex for persistence- Use streaming for better user experience with long responses- Implement proper error handling for tool failures- Use vector indexes for efficient RAG retrieval- Rate limit agent interactions to control costs- Log tool usage for debugging and analytics ## Common Pitfalls 1. **Not persisting threads** - Conversations lost on refresh2. **Blocking on long responses** - Use streaming instead3. **Tool errors crashing agents** - Add proper error handling4. **Large context windows** - Summarize old messages5. **Missing embeddings for RAG** - Generate embeddings on insert ## References - Convex Documentation: https://docs.convex.dev/- Convex LLMs.txt: https://docs.convex.dev/llms.txt- Convex AI: https://docs.convex.dev/ai- Agent Component: https://www.npmjs.com/package/@convex-dev/agent