Crux
GuidesConvex

Recipes

Recommended Convex patterns and anti-patterns for Crux applications.

Short, copyable patterns for common Crux-on-Convex situations: writing memory from mutations, putting flows behind auth, running child agent actions, and choosing between durable and immediate work.

Write App-Specific Memory From A Mutation

Mutations cannot run Crux actions, but they can still write memory records your app owns:

convex/agent/memory/episodes.ts
interface DatabaseWriter {
  insert(
    table: "contextMemory",
    doc: Record<string, unknown>,
  ): Promise<unknown>;
}

export async function recordEpisode(
  db: DatabaseWriter,
  args: {
    memoryId: string;
    content: string;
    metadata?: Record<string, unknown>;
  },
) {
  const ts = Date.now();
  const key = `episodic:${args.memoryId}:${ts}-${Math.random().toString(36).slice(2, 8)}`;

  await db.insert("contextMemory", {
    key,
    prefix: `episodic:${args.memoryId}`,
    content: args.content,
    metadata: { ...args.metadata, memoryId: args.memoryId },
    createdAt: ts,
    updatedAt: ts,
  });

  return key;
}

Prefer core memory APIs from actions when possible:

await episodes({ id: "chat", embed }).record(
  { content: message, metadata: { role: "user" } },
  { records: cruxRecords(ctx), namespace: `thread:${threadId}` },
);

Keep helpers like this local to your app instead of adding app-table-specific helpers to @use-crux/convex; table names, tenancy, and authorization are application policy.

Public Flow With Auth

Wrap a flow handle instead of exporting it directly:

export const startWriter = action({
  args: writerFlow.args,
  handler: async (ctx, args) => {
    await requireDraftAccess(ctx, args.draftId);
    return writerFlow.handler(ctx, args);
  },
});

Child Agent Action

const result = await ctx.crux.runAction(
  "research",
  internal.agent.research.run,
  {
    threadId,
    question,
  },
);

Both the caller and target should use @use-crux/convex/server wrappers.

Convex Agent Tool Calls Child Action

import { createTool } from "@use-crux/convex/agent";
import { z } from "zod";

export const researchTool = createTool({
  description: "Run research for the current thread.",
  inputSchema: z.object({ question: z.string() }),
  execute: async (ctx, args) => {
    return ctx.crux.runAction("research", internal.agent.research.run, {
      question: args.question,
    });
  },
});

Durable Versus Immediate Work

NeedUse
One request, one model callaction() + adapter generate()
Parallel work inside one actionAdapter composition inside action()
Approval or external resumeflow() from @use-crux/convex/server
Experimental cross-action swarmcreateComponentSwarm()
App read/write onlyRaw Convex query/mutation or Crux-aware wrapper only if context propagation matters

Anti-Patterns

  • Raw ctx.runAction() for related AI work.
  • Exporting flow .action publicly when auth is needed.
  • Building strings by hand in Convex Agent context handlers when Crux contexts can resolve them.
  • Storing large workspace files in RecordStore instead of convexAssetStore().
  • Adding generic helpers to @use-crux/convex that assume host app table names.

On this page