Local development
bun run dev previews checkout www/API code and docs against production data and services. All durable background work runs on production backstage on Fly: director passes, bot turns, workflows, file indexing, and cleanup.
Web app
bun run dev # https://arbe.localhost + Vite hot reload + docs previewOpen https://arbe.localhost. The root command runs Vite through Portless, which owns that stable HTTP/2 + HTTPS origin and forwards Vite’s WebSocket hot reload. Portless is a repo dependency and starts its proxy automatically; its first run may ask to trust the local certificate authority.
portless doctor # proxy, route, DNS, and certificate diagnosticsbun run dev:trust # retry Portless certificate trustDo not copy Vite’s :8888 URL into a browser: it is the deliberate direct-HTTP port used by the CLI and proofs, while https://arbe.localhost is the browser contract. Collection polling can exhaust the browser’s HTTP/1.1 connection pool on :8888; Portless avoids that with HTTP/2. Add https://arbe.localhost to Supabase Auth → URL Configuration → Redirect URLs if you log in through it.
Local API handlers execute the checkout code and enqueue into the canonical production queues: dispatch for director work and default for workflows and file work. Background callbacks use the deployed production API. The dev command starts no backstage and needs no backstage database credentials. Local HTTP logs land in apps/www/tmp/dev.log; worker logs are on Fly.
A background-code change must deploy from main before live integration proof can exercise it. Local parser and decision tests can run before deployment. Use a fresh test house for chat, uploads, and workflows, and capture arbe --local thread trace <thread> --house <house> plus file readings/search or workflow results. Test houses separate records; they do not isolate the effects of deploying faulty worker code.
Local API and deployed worker versions can differ. Every worker validates queued payloads against core schemas before executing them; incompatible payloads fail with task kind/id, validation details, the running backstage SHA, and a deploy-first hint. Shape checks cannot catch every semantic change, so coordinate API and worker deployment. Director decisions and outcomes retain the executing backstage SHA; older unstamped records report an unknown revision.
For raw HTTP development, cd apps/www && bun run dev runs Vite directly on http://localhost:8888 without Portless or docs. In DevTools, /api/threads and the other collection routes should show protocol h2, and Vite’s HMR connection should use wss://arbe.localhost.
Retiring a local backstage
Stop a shared local worker only after coordinating with its owner. Inspect existing per-user queues through the Supabase CLI and account for unfinished work before retiring the setup. Do not replay, drop, or delete tasks or queue tables without approval for the production database write. New work and queue provisioning reject development queue targets.
Docs
/docs is a second dev server. bun run dev starts it for you — Astro on http://localhost:4321 — unless something is already answering there, in which case it uses that and says so. To run it alone:
bun run --filter '@arbe/www-docs' devwww proxies https://arbe.localhost/docs/* to it (apps/www/src/lib/server/docs-proxy.ts). Astro emits root-absolute URLs (/@fs/…, /@id/…) in both its HTML and its module bodies; the proxy rewrites them under /docs so they come back to Astro. Anything it misses is served by www’s Vite instead — which resolves the file but not Astro’s virtual modules, so the symptom is a Vite overlay saying Failed to resolve import "virtual:astro:adapter-config/client". Compare against http://localhost:4321/docs/ directly: if that page is fine, the bug is in the rewrite, not in Astro.
Use Wrangler only when you need full Cloudflare bindings:
bunx wrangler dev -c apps/www/wrangler.jsonc # full bindings, no HMRVite dev does not provide Cloudflare-specific bindings such as the rate limiter. Most routes do not need them. Secrets live in the root .env.local, validated against .env.schema on every Vite start (SUPABASE_JWT_SECRET, DURABLE_STREAMS_SECRET, OPENROUTER_API_KEY; ANTHROPIC_API_KEY is optional). bunx varlock load --agent shows what resolved, secrets redacted.
Checks
bun run check # format + lint + typecheck across packagesbun run test # all packages# scope: bun run --filter '@arbe/cli' checkcheck subsumes lint — never run both. Biome handles most packages; apps/www uses eslint + prettier because of the Svelte plugin.
A package’s own check script is the only thing that typechecks it — nothing runs tsc globally. The house form is biome format --write . && oxlint --type-aware --type-check; dropping the two oxlint flags leaves that package’s TypeScript unchecked. New package? Copy the line verbatim. Biome config lives in the root biome.jsonc; nested biome.jsonc files only extends: "//" plus per-package overrides, and bunx biome migrate --write from the repo root updates all of them at once.
Code: apps/www/scripts/dev-stack.sh, .env.schema, apps/www/.env.schema, apps/backstage/.env.schema.
See system/environment-variables, system/deployment, debugging.