Setup
Install @crux/convex, register the component, and create a shared Crux store helper.
Convex Setup
Install the runtime packages you need on the server:
pnpm add @crux/core @crux/ai @crux/convex @ai-sdk/openai zodIf you use the Convex Agent component, also install it:
pnpm add @convex-dev/agentInstall The Component
@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 '@crux/convex/convex.config'
const app = defineApp()
app.use(crux)
export default appRun Convex codegen after adding the component so components.crux exists:
npx convex devCreate A Store Helper
Create one small helper and reuse it everywhere you need a Crux store.
import type { ActionCtx, MutationCtx } from '../_generated/server'
import { components } from '../_generated/api'
import { cruxConvexStore } from '@crux/convex'
export function cruxStore(ctx: ActionCtx | MutationCtx) {
return cruxConvexStore({
component: components.crux,
ctx,
})
}Use this store for memory blocks, blackboards, plans, prompt cache data, and workspace metadata. Use a separate dedicated store or component instance 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 for Crux-native Convex Agent code.
import { createCruxConvex } from '@crux/convex'
import { components } from '../_generated/api'
export const crux = createCruxConvex({
components: {
crux: components.crux,
agent: components.agent,
},
})Use crux.convexAgent({ prompt, model, ... }) for agents, crux.store(ctx) for a request-scoped store, and crux.run(ctx, target, fn) for lower-level integrations that need Convex-bound memory/tools outside the high-level wrapper.
If tests, migrations, or alternate storage need to replace the default component store, configure one profile-level store factory:
export const crux = createCruxConvex({
components: { crux: components.crux, agent: components.agent },
store: {
create(ctx, defaults) {
return defaults.createComponentStore(ctx)
},
},
})Runtime Layout
Keep definitions portable and boundaries explicit:
convex/
prompts/
support.ts # prompt(), context(), agent() definitions
crux/
store.ts # cruxStore(ctx) for storage-only code
profile.ts # createCruxConvex({ components }) for Convex Agent code
chat/
actions.ts # @crux/convex/server action() entrypoints
agent/
tools.ts # @crux/convex/agent tools
workflows/
writerFlow.ts # @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 '@crux/convex/server'
import { generate } from '@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
@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 httpThis 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 store 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.