Setup
Install @use-crux/convex, register the component, and create shared Crux storage helpers.
Install the runtime packages you need on the server:
pnpm add @use-crux/core @use-crux/ai @use-crux/convex @ai-sdk/openai zodIf you use the Convex Agent component, also install it:
pnpm add @convex-dev/agentInstall The Component
@use-crux/convex ships a Convex component for Crux-owned persistence such as memory records and experimental cross-action swarm state.
import { defineApp } from "convex/server";
import crux from "@use-crux/convex/convex.config";
const app = defineApp();
app.use(crux);
export default app;Run Convex codegen after adding the component so components.crux exists:
npx convex devThe packaged component already uses Convex-valid module paths, so no consumer-side rewriting is needed before codegen or deployment.
Create A Record Helper
Create one small helper and reuse it everywhere you need Crux records.
import type { ActionCtx, MutationCtx } from "../_generated/server";
import { components } from "../_generated/api";
import { convexRecordStore } from "@use-crux/convex";
export function cruxRecords(ctx: ActionCtx | MutationCtx) {
return convexRecordStore({ component: components.crux, ctx });
}Use these records for memory blocks, blackboards, plans, prompt cache data, and workspace metadata. Use a separate dedicated component instance or vector namespace for semantic cache entries if you need isolated vector search behavior.
Create An Agent Profile
If you use the Convex Agent component, create one profile that owns both component references. This is the preferred boundary when Crux should own the profile-backed prompt, memory, skills, tools, and post-turn persistence lifecycle.
import { createCruxConvex } from "@use-crux/convex";
import { components } from "../_generated/api";
export const crux = createCruxConvex({
components: {
crux: components.crux,
agent: components.agent,
},
});Use crux.convexAgent({ prompt, languageModel, ... }) when Crux should resolve the prompt for each Convex Agent turn. Use crux.storage(ctx) for request-scoped storage and crux.run(ctx, target, fn) for lower-level integrations that need Convex-bound memory/tools outside the profile-backed helper.
If tests, migrations, or alternate storage need to replace the default component-backed storage, configure one profile-level storage factory:
export const crux = createCruxConvex({
components: { crux: components.crux, agent: components.agent },
storage: {
create(ctx, defaults) {
return defaults.createComponentStorage(ctx);
},
},
});Lower-Level Runtime Work
Use createCruxConvex().run(ctx, target, fn) when custom Convex actions or
normal Convex Agent-shaped code need Crux runtime plumbing without the
profile-backed convexAgent() helper. The same profile exposes
storage(ctx) and bridge(http, cruxConfig) for lower-level storage and
devtools integration.
Runtime Layout
Keep definitions portable and boundaries explicit:
convex/
prompts/
support.ts # prompt(), context(), agent() definitions
crux/
storage.ts # cruxRecords(ctx) for record-only code
profile.ts # createCruxConvex({ components }) for profile-backed and lower-level runtime code
chat/
actions.ts # @use-crux/convex/server action() entrypoints
agent/
tools.ts # @use-crux/convex/agent tools
workflows/
writerFlow.ts # @use-crux/convex/server flow()Node Runtime
Actions that import AI SDK providers, OpenAI clients, or other Node-only packages need 'use node':
"use node";
import { action } from "@use-crux/convex/server";
import { generate } from "@use-crux/ai";
import { openai } from "@ai-sdk/openai";Queries and mutations that only read/write Convex data can stay in the default Convex runtime.
Best Practices
- Install the component once and access it through
components.crux. - Put prompt/context/agent definitions in regular modules so they remain testable outside Convex.
- Put AI execution entrypoints behind
@use-crux/convex/serverboundaries. - Keep tenant checks, auth, and app table writes in your app, not in reusable Crux helpers.
Devtools Runtime Bridge
For local development, Convex exposes Runtime Bridge commands over HTTP instead of holding a long-lived WebSocket inside actions.
import { httpRouter } from "convex/server";
import { cruxConfig } from "./crux/config";
import { crux } from "./crux/profile";
const http = httpRouter();
crux.bridge(http, cruxConfig);
export default http;This registers GET /crux/bridge, POST /crux/bridge, and OPTIONS /crux/bridge. The Go devtools backend calls that endpoint for read-only live-runtime commands such as store.read. The profile bridge uses the same request-scoped storage path as crux.run() and crux.convexAgent(), so memory and blackboard resources can be inspected by resource id without separately registering every store. The manifest uses the actual HTTP Actions URL from the request unless you pass an explicit url, and invalid command bodies return structured bridge errors. Treat the bridge as trusted local-dev infrastructure; do not expose it as a public application RPC surface.