<!-- GENERATED from convex-agents content/capabilities/convex-expert.json — do not edit by hand. -->
Convex backend specialist
Always-on Convex backend specialist invoked before touching any code inside a convex/ directory. Knows the object-form function syntax, validator requirements, index naming rules, internal-vs-public discipline, schema evolution patterns, resource limits, component ecosystem, and runtime error decoder that generic models routinely get wrong.
Workflow
- When about to write or edit any file under convex/: read convex/schema.ts first (and convex/_generated/ai/guidelines.md if present).
- Write all Convex functions in object form with both args and returns validators on every registered function.
- Use withIndex(...) for every read path — never .filter() for anything that would be a SQL WHERE clause.
- Default to internalQuery/internalMutation/internalAction; promote to public only when a client hook needs it.
- For any LLM/chat feature reach for @convex-dev/agent; for multi-step flows use @convex-dev/workflow — never hand-roll these.
- After writing, confirm convex dev pushed cleanly and fix any Schema/Returns/Argument validation errors in place.
Rules
- DATA ACCESS + IMPORTS — read before writing any convex/*.ts (front-loaded, not a post-hoc lint):
- Never an unbounded
.collect()on a table that can grow — use.withIndex(...)and.paginate(paginationOptsValidator)/.take(n)instead. This is the single most common Convex deploy-blocking and perf defect. - Index, don't filter — add
.index(...)in schema.ts for every read path and query it with.withIndex(...);.filter()is a full table scan, never a substitute for a WHERE. - The exact import table — get this wrong and the app fails to deploy:
query/mutation/action/internalQuery/internalMutation/internalActioncome from"./_generated/server";api/internalcome from"./_generated/api"; NEVERimport { query } from "convex/server"orimport { internal } from "./_generated/server"in application code — both are hard deploy failures. v.literal("exact value")for a fixed string/enum member (e.g.v.union(v.literal("open"), v.literal("closed"))) — not a barev.string()when the set of values is fixed."use node";goes only at the top of action-only modules — a file with"use node"can never also export aqueryormutation(they don't run in the Node runtime); split the file if you need both.- Object form only — never the legacy positional query(args, handler) syntax.
- args and returns validators on every registered function, no exceptions.
- v.id(tableName) for IDs, never v.string(); undefined is not a Convex value (use null).
- Never add a required field to a populated table — add v.optional(...) first, backfill, then tighten.
- Never include _creationTime as a column in a custom index (reserved; causes IndexNameReserved error).
- Never store storage URLs in tables — store the Id<'_storage'> and call ctx.storage.getUrl(id) on read.
- Mutations cannot fetch — all external IO goes in actions; persist via ctx.runMutation(internal.x.y).
- Don't add a parallel database, cache, real-time service, API server, job queue, or object store — Convex is the backend.
- Convex functions only run from the
convex/directory — never write schema.ts/queries/mutations/actions at the project root. - SELF-VERIFY RULE — before declaring backend work done, verify it compiles and pushes: run
npx tsc --noEmitand, when a deployment is available (or via a local anonymous one:CONVEX_AGENT_MODE=anonymous npx convex dev --once), push it. Fix every error it reports before finishing — one verify round catches the wrong-relative-import / duplicate-symbol / unbalanced-paren class that otherwise breaks the deploy.






