Crux
GuidesObservability

Cloud & Tunnel Setup

Connect cloud backends like Convex, Vercel, or Lambda to your local devtools server.

When your backend runs locally, Quality 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 --tunnel

The tunnel URL is displayed in both the CLI output and the TUI header:

OK Server ready at http://localhost:4400
OK Tunnel: https://your-subdomain.ngrok-free.dev

In TUI mode (crux dev --tunnel --tui), the tunnel URL appears in the dashboard header bar alongside the local URL.

Connecting your backend

Set the DEVTOOLS_URL environment variable to the tunnel URL. For Convex:

npx convex env set DEVTOOLS_URL https://your-subdomain.ngrok-free.dev

Add devtools.serverUrl only for this explicit tunnel/local-runtime connection:

crux.config.ts
config({
  devtools: { serverUrl: process.env.DEVTOOLS_URL },
})

For other platforms, set DEVTOOLS_URL however your platform handles environment variables (e.g. .env files, Vercel dashboard, AWS Parameter Store).

Free ngrok plans generate a new URL on every restart. If traces stop appearing, check that DEVTOOLS_URL matches the current tunnel URL shown in the CLI output.

Tunnel providers

The CLI tries providers in order:

  1. ngrok — requires @ngrok/ngrok installed and NGROK_AUTHTOKEN set. Most reliable for WebSocket connections. With a paid plan, you get a stable subdomain.
  2. localtunnel — requires localtunnel installed. Zero-config, no auth needed.

Install one as a dev dependency:

pnpm add -D @ngrok/ngrok    # recommended
# or
pnpm add -D localtunnel     # zero-config alternative

For ngrok, set the auth token:

export NGROK_AUTHTOKEN=your_token_here

Sessions & 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 '@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 stepId and stepLabel.

import { withSession, flow, createSessionId } from '@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 @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.

Quality↔runtime comparison

Quality evaluation cells and runtime flows share the same step labels in the trace model, so the Timeline can compare a production run against evaluation runs of the same flow.

Next steps

On this page