Crux
Getting Started

Convex

Run Crux prompts inside Convex actions and persist memory in the crux component.

This quick start gets Crux running in a Convex project. Crux prompts run inside Convex actions; persistent memory uses @use-crux/convex. For cross-action flows, swarm routing, Convex Agent integration, workspaces, skills, blackboards, and devtools, see the full Convex guide.

Crux is in alpha. Public packages are available on npm; use the install command below to get started.

Install

pnpm add @use-crux/core @use-crux/ai @use-crux/convex @ai-sdk/openai zod

Component setup

@use-crux/convex ships a Convex component that handles Crux-owned persistence such as memory records and experimental cross-action swarm state. Install it once in your convex.config.ts:

convex/convex.config.ts
import { defineApp } from "convex/server";
import crux from "@use-crux/convex/convex.config";

const app = defineApp();
app.use(crux);
export default app;

This creates the crux component namespace. The component manages its own tables: no schema definitions needed in your app.

convex/
  prompts/
    index.ts          ← Prompt definitions
    contexts.ts       ← Shared contexts
  agent/
    chat.ts           ← Action that calls generate()
  memory/
    storage.ts        ← Convex memory storage setup
  convex.config.ts    ← Component installation

Step 1: Define a prompt

convex/prompts/index.ts
import { context, prompt } from "@use-crux/convex";
import { z } from "zod";

const brand = context({
  id: "brand",
  priority: 30,
  system: "You are a helpful writing assistant.",
});

export const chat = prompt({
  id: "chat",
  use: [brand],
  input: z.object({ message: z.string(), mode: z.string() }),
  system: "Help the user with their writing task.",
  prompt: ({ input }) => input.message,
});

Step 2: Call it from a Convex action

convex/agent/chat.ts
"use node";

import { action } from "@use-crux/convex/server";
import { v } from "convex/values";
import { generate } from "@use-crux/ai";
import { openai } from "@ai-sdk/openai";

import { chat } from "../prompts";

export const respond = action({
  args: { message: v.string(), mode: v.string() },
  handler: async (ctx, args) => {
    const result = await generate(chat, {
      model: openai("gpt-4o"),
      input: { message: args.message, mode: args.mode },
    });

    return result.text;
  },
});

Convex actions that import the AI SDK or OpenAI client need the 'use node' directive, because they run in a Node.js runtime, not Convex's V8 isolate.

Persistent memory

Use convexRecordStore() for durable Convex-backed memory records:

convex/memory/storage.ts
import { convexRecordStore } from "@use-crux/convex";
import { episodes, memory, workingState } from "@use-crux/convex/memory";
import { components } from "../_generated/api";
import type { ActionCtx } from "../_generated/server";

export function createMemoryRecords(ctx: ActionCtx) {
  return convexRecordStore({ component: components.crux, ctx });
}

// Inside an action:
const records = createMemoryRecords(ctx);
const episodic = episodes({ id: "chat", embed: embedFn });
const working = workingState({ id: "session", schema });
const assistantMemory = memory({
  id: "assistant",
  records,
  namespace: "thread:1",
  blocks: [working, episodic],
});

The component handles record operations (get, put, create, delete, list) automatically. Convex storage is records-only; for dense vector search, pair the record store with an explicit VectorStore such as upstashVectorStore().

With the Convex Agent SDK

If you're using the Convex Agent component, create one Crux Convex profile and let convexAgent() resolve the Crux prompt, memory, skills, and tools for each turn:

convex/agent/setup.ts
"use node";

import { openai } from "@ai-sdk/openai";

import { createCruxConvex } from "@use-crux/convex";
import { components } from "../_generated/api";

import { chat } from "../prompts";

const languageModel = openai("gpt-4o");

const crux = createCruxConvex({
  components: {
    crux: components.crux,
    agent: components.agent,
  },
});

export const chatAgent = crux.convexAgent({
  name: "Chat",
  prompt: chat,
  languageModel,
});

await chatAgent.generateText(
  ctx,
  { threadId, userId },
  { input: { message, mode } },
);

Use crux.run(ctx, target, fn) when lower-level action code needs the same request-scoped storage and runtime binding without going through an agent:

await crux.run(ctx, { threadId, userId }, async ({ records }) => {
  await records.put(`blackboard:${threadId}`, { status: "ready" });
});

The lower-level Agent export remains available for manual Convex Agent integrations, but the profile-created agent is the default path when Crux should own prompt resolution, memory, skills, and tools for each turn. It keeps the Convex Agent call shape while Crux handles the request-scoped lifecycle internally.

Use languageModel for new convexAgent() code to match Convex Agent terminology; model remains accepted for existing call sites.

Use crux.run(ctx, target, fn) when custom actions or normal Convex Agent-shaped code need the profile's Crux runtime, storage, and bridge plumbing.

Action timeouts

Convex actions have a 5-minute timeout. For long-running multi-agent workflows:

  • Treat createComponentSwarm() from @use-crux/convex/swarm as experimental cross-action swarm routing. For launch-critical code, run immediate swarm compositions inside a Crux-aware action or use flow() for durable orchestration.
  • Use flow() from @use-crux/convex/server for suspendable flows that scheduled-resume across action boundaries
  • Use ctx.crux.runAction() across action boundaries to keep devtools traces correlated

Next steps

On this page