Cloud & Tunnel Setup
Connect cloud backends like Convex, Vercel, or Lambda to your local devtools server.
When your backend runs locally, Eval CLI auto-attach works against loopback crux dev origins
without project config. When application runtime code runs in the cloud (Convex, Vercel, AWS
Lambda), the devtools server needs a public URL so that runtime can reach it.
Creating a tunnel
The --tunnel flag creates a public URL automatically:
crux dev --tunnelThe tunnel URL and scoped ingest token are displayed in the CLI output. In TUI mode, the tunnel URL
appears in the breadcrumb and the ingest token path appears as ingest token .crux/devtools/ingest-token:
OK Server ready at http://localhost:4400
* Remote observability ingest: CRUX_DEVTOOLS_TOKEN=... (saved at .crux/devtools/ingest-token)
OK Tunnel: https://your-subdomain.ngrok-free.dev?t=...The ?t=... query token authenticates browser access to the devtools UI. For server-to-server
observability posts, use the tunnel origin plus CRUX_DEVTOOLS_TOKEN instead. The ingest token is
generated once per project and reused across crux dev restarts.
Connecting your backend
Set DEVTOOLS_URL to the tunnel origin and CRUX_DEVTOOLS_TOKEN to the token printed by crux dev.
For Convex:
npx convex env set DEVTOOLS_URL 'https://your-subdomain.ngrok-free.dev'
npx convex env set CRUX_DEVTOOLS_TOKEN '...'Configure observability for this explicit tunnel/local-runtime connection:
config({
observability: {
serverUrl: process.env.DEVTOOLS_URL,
token: process.env.CRUX_DEVTOOLS_TOKEN,
},
});For other platforms, set DEVTOOLS_URL and CRUX_DEVTOOLS_TOKEN however your platform handles
environment variables (e.g. .env files, Vercel dashboard, AWS Parameter Store). The HTTP
transport also reads CRUX_DEVTOOLS_TOKEN automatically when token is omitted.
Free ngrok plans generate a new URL on every restart. If traces stop
appearing, check that DEVTOOLS_URL matches the current tunnel origin. The
CRUX_DEVTOOLS_TOKEN value persists unless .crux/devtools/ingest-token is
deleted.
Tunnel providers
The CLI tries providers in order:
- ngrok: requires
@ngrok/ngrokinstalled andNGROK_AUTHTOKENset. Most reliable for WebSocket connections. With a paid plan, you get a stable subdomain. - localtunnel: requires
localtunnelinstalled. Zero-config, no auth needed.
Install one as a dev dependency:
pnpm add -D @ngrok/ngrok # recommended
# or
pnpm add -D localtunnel # zero-config alternativeFor ngrok, set the auth token:
export NGROK_AUTHTOKEN=your_token_hereSessions & Flows
When traces arrive from a cloud backend, they can be grouped into logical sessions and structured pipelines for easier navigation in the Timeline.
Session scope
withSession groups all generate() calls inside it under a single session ID, visible as a collapsible group in the Timeline.
import { withSession, createSessionId } from "@use-crux/core";
const sessionId = createSessionId();
await withSession(sessionId, async () => {
await generate(titlePrompt, { model, input });
await generate(replyPrompt, { model, input });
});Flow scope
flow adds structured pipeline grouping within a session. Named steps via flow.step() tag every nested generate() call with a runtime stepId and the durable beta stepLabel.
import { withSession, flow, createSessionId } from "@use-crux/core";
const researchFlow = flow("research", async (flow) => {
const plan = await flow.step("plan", () =>
generate(planner, { model, input }),
);
return flow.step("search", () => generate(searcher, { model, input: plan }));
});
await withSession(createSessionId(), () => researchFlow.run());In the Timeline, the session appears as a collapsible group with each flow shown as a sub-group, and steps within each flow labeled and nested.
Cross-action flows (Convex)
For Convex, use @use-crux/convex/server action boundaries and ctx.crux.runAction() so the hidden __crux envelope carries run/span context across workers. See the Convex observability guide for cross-action flows and devtools setup.
Hierarchy in Timeline
Session "chat-abc"
├── Flow "content-pipeline"
│ ├── step:plan → trace-001
│ ├── step:research
│ │ └── Flow "research" ← nested flow
│ │ ├── step:find-sources → trace-002
│ │ └── step:synthesize → trace-003
│ └── step:write → trace-004
├── Flow "research:economy"
│ └── ...
└── trace-005 (standalone, no flow)Nested flows appear as children of their parent flow. The parentFlowId relationship is tracked automatically when you run a flow inside another flow.
Eval↔runtime comparison
Eval Cases and production runs share the same canonical trace model, so the Timeline can compare task behavior without inventing a second observability format.
Next steps
Debug why a model turn behaved badly
Use Run Detail Explain to inspect what shaped one model turn, find the source to change, and protect the behavior with Eval assertions.
Observability context propagation
How observability context and parentSpanId flow through your agents, flows, and across serverless action boundaries.