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:
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
| Need | Use |
|---|---|
| One request, one model call | action() + adapter generate() |
| Parallel work inside one action | Adapter composition inside action() |
| Approval or external resume | flow() from @use-crux/convex/server |
| Experimental cross-action swarm | createComponentSwarm() |
| App read/write only | Raw Convex query/mutation or Crux-aware wrapper only if context propagation matters |
Anti-Patterns
- Raw
ctx.runAction()for related AI work. - Exporting flow
.actionpublicly when auth is needed. - Building strings by hand in Convex Agent context handlers when Crux contexts can resolve them.
- Storing large workspace files in
RecordStoreinstead ofconvexAssetStore(). - Adding generic helpers to
@use-crux/convexthat assume host app table names.