CLI Reference
B4.run ships a single b4 binary with seventeen commands: add, approvals, build, check, client-tools, dev, docs, eval, inspect, memory, routes, run, start, test, threads, typegen, and verify. Most commands read b4.config.ts from the current working directory or from --cwd <path>. b4 dev always uses the current working directory.
Running the CLICopy link to section: Running the CLI
Always run B4.run through the b4 bin: npx b4 <command>, pnpm exec b4 <command> or a package.json script. @b4run/cli installs the bin into node_modules/.bin, and it resolves wherever the package lives.
b4 checkCopy link to section: b4 check
b4 check validates the app structure and configuration.
b4 checkIt loads b4.config.ts, resolves the app root, runs discoverRoutes, and parses each route's tool definitions with discoverToolDefinitions. It reports:
- A
b4.config.tsthat fails to load. - An app root
package.jsonwithout"type": "module"(B4_E1006). B4.run loadsb4.config.tsand every routeindex.tsas ES modules. Without the field, Node compiles them as CommonJS, and B4.run won't recognise a route exported withexport default agent(...). - A route directory whose
index.tshas no recognisable export (B4_E1007), with the file path and the exports it did find. If a nestedpackage.jsonwithout"type": "module"made Node load the module as CommonJS, the message names that file. To keep a directory that isn't a route out of discovery, prefix it with_. - A route whose
index.tsexports more than one ofagent,workflow,graph,chain(B4_E1008), with the file path and the kinds it exported. For both codes,b4 checklists every offending route at once. When an app has both, it lists the unrecognised ones first, with a count of the multi-kind ones. - A tool file that fails to parse.
- An agent route tool whose name starts with
client_(B4_E1001). That prefix is reserved for client-provided tools, and route preparation refuses it at run time too. - A stale build manifest (see below).
- A
memory.distill.retrywith a key other thanmaxAttempts, or amaxAttemptsthat isn't a whole number of at least 1; an unknown key inmemory.distill; a non-objectmemoryormemory.distill; and a near miss onmemoryitself, such asDistill,distil, or aretryormaxAttemptsplaced there (B4_E1009).b4 memory consolidateandreflectrefuse the same config before building a model. - An
agentsMdthat isn't an object, an unknown key in it such aswriteable, or awritablethat isn't a boolean (B4_E1010). Route preparation refuses the same config. - An unknown
build.targetsentry and, when"hono"is one of them, every feature that target cannot serve (B4_E1005).b4 buildapplies the same gate, so you learn about all of them before you build.
When .b4/build/modules.mjs exists (the node target writes it), b4 check compares its routes with the routes on disk. A mismatch, such as a route renamed since the last build, fails with the missing and extra route ids and asks you to re-run b4 build.
When "hono" is a configured target, b4 check compares .b4/build/modules.edge.mjs the same way. It's a separate artifact with its own copy of every route's static imports, and an app that builds for hono alone has no modules.mjs. A missing manifest is fine, and so is a leftover edge manifest when hono isn't a target.
The output is a B4.run app is valid: N routes discovered. line followed by a per-route - <pathname> (<kind>) summary. On any violation, it exits non-zero with a detailed message.
Troubleshooting importsCopy link to section: Troubleshooting imports
When a route, tool, or config module fails to load with the ESM error does not provide an export named X, B4.run prints the offending package with a likely cause and fix, in place of the raw SyntaxError. There are two usual causes:
-
An older
@langchain/coregot hoisted. Runnpm ls @langchain/coreto find the duplicate, then upgrade or dedupe so the installed version satisfies B4.run's peer range (^1.2.12). -
A CommonJS dependency imported with named bindings. Under B4.run's ESM resolver a CommonJS package only has a default export, so
import { thing } from "x"fails. Use a default import and destructure instead:tsimport pkg from "x" const { thing } = pkgOr import the package's ESM build if it ships one.
b4 verifyCopy link to section: b4 verify
b4 verify runs five checks in one call: app, routes, typegen, deps and runtime. Run it before b4 dev, b4 start, or a deploy. A green b4 verify means "this app will boot in this environment."
b4 verify
b4 verify --jsonFlags:
--cwd <path>: operate on a different app root.--json: print a structured report ({ status, appRoot, checks, counts }) in place of the text output. A failed check carrieserror.message, pluserror.codewhen the failure has an error-code entry (for exampleB4_E1006for an app root that isn't an ES module). Text mode shows the same code in a[B4_Exxxx] See <docs>footer.--env-file <path>: path to a.envfile (overridesb4.config.tsenvand the default./.env).
The deps check looks for missing packages and missing env vars. It's provider-aware: it works out which API-key env vars to look for from the providers your routes use. An Anthropic-only app needs ANTHROPIC_API_KEY, an OpenAI app needs OPENAI_API_KEY, a multi-provider app needs both, and a local Ollama app needs none. A missing key is only a warning, because the runtime environment may supply it.
The runtime check gates environment readiness:
- Node: asserts the running Node version is at least
24.0.0, the active LTS line (it bundles npm 11 or later and shipsnode:sqliteunflagged). An older version failsverifywith a non-zero exit. - Docker: runs only when
b4.config.tsconfigures a sandbox provider. It runs the provider's daemon preflight and fails if the daemon is unreachable.
See Deployment for the recommended workflow.
b4 routesCopy link to section: b4 routes
b4 routes lists every route B4.run discovered, with its pathname.
b4 routes
b4 routes --jsonOutput:
Discovered 2 B4.run routes in /path/to/app
/navlog -> src/app/navlog/index.ts
/admin/users -> src/app/(internal)/admin/users/index.tsUse it to confirm that route groups and dynamic segments parse the way you expect.
b4 typegenCopy link to section: b4 typegen
b4 typegen regenerates .b4/b4.generated.d.ts plus the per-route .b4/routes/<routeSlug>/tools.json and .b4/routes/<routeSlug>/state.json manifests.
b4 typegenThe success log reports route, tool-schema, and stateful-route counts. b4 build reads the tools.json files to emit LangGraph entries.
Tool input types compile with the app's own tsconfig.json (the one nearest the app root, honoring extends, paths, and baseUrl). If a declared input type resolves to any, say through a path alias the tsconfig doesn't declare, the command fails and names the tool file and the unresolved type. It won't write an empty schema.
b4 buildCopy link to section: b4 build
b4 build writes deployment artifacts for each target in build.targets (default: ["node", "langsmith"]).
b4 build
b4 build --clean
b4 build --out-dir dist/vercelFlags:
--cwd <path>: operate on a different app root.--clean: wipe.b4/build/before writing.--out-dir <dir>: where theverceltarget publishes its Build Output tree, relative to the app root (default.vercel/output). It overridesbuild.vercel.outDirfor this run, and it's an error when"vercel"isn't a configured target.
Here's what each target writes:
node:.b4/build/server.mjs, which bootsserveRuntime(the real B4.run runtime), and a hardenedDockerfilein the app root (or in.b4/build/if the app root already has one). Run the build output withnode .b4/build/server.mjs(the generatedDockerfile's entry point) ordocker build/docker run.b4 startdoesn't use the build output: it serves the app's source tree, so it reads route modules, middleware and the thread access policy from source.langsmith:.b4/build/langgraph.jsonplus per-route entry files under.b4/build/<routeSlug>.ts, which you hand to LangSmith. The JSON includesgraphs(keyed by<routeId>#<kind>),dependencies: ["."],env(.env.exampleif present, else.env), andnode_version: "22". For anagentroute, the entry imports the defaultagent()descriptor, turns it into a LangGraph graph, and wires in every tool B4.run discovered for the route.hono(opt-in):.b4/build/app.mjs, a Hono app around B4.run's web-standard fetch handler,export defaulted for Cloudflare Workers, Vercel, or Bun. It also writes.b4/build/modules.edge.mjs(the module manifest, free of Node built-ins),.b4/build/stores.mjs(a per-request Postgres store factory), and awrangler.tomlscaffold if the app root has none. Deploy withwrangler deploy.vercel(opt-in): a Vercel Build Output API tree. See below.
Pick the targets with build.targets in b4.config.ts, for example { build: { targets: ["node"] } }. The list replaces the defaults, so an app that also deploys to the edge names both: { build: { targets: ["node", "hono"] } }. Target-specific settings sit next to the list, for example { build: { targets: ["vercel"], vercel: { outDir: "dist/vercel" } } }.
The vercel targetCopy link to section: The vercel target
The tree holds config.json and the runtime Node 24 function at functions/b4.func/ (the self-contained index.mjs plus its .vc-config.json). The function serves the same edge subset as hono. The tree lands in .vercel/output, where Vercel deploys from, unless build.vercel.outDir (or --out-dir) points elsewhere.
With build.vercel configured, the target also:
- copies a static directory into
static/, - bundles each declared function into
functions/<name>.func/, and - orders
routesas your routes, the filesystem, the runtime, then the SPA fallback.
The runtime function is named b4.func (or whatever functionName says), so it never shadows static/index.html.
The target stages and validates the whole tree, then swaps it in with one rename, so the output directory can't contain the app root. The validator checks that config.json is version 3 and still routes to the runtime function, so a composed config with extra routes and functions passes.
The target also checks the app-root vercel.json for a buildCommand that runs b4 build and fluid: true:
- With no
vercel.json, it writes a recommended one. - If your
vercel.jsonis missing either setting, it prints a warning and writes the recommended file to.b4/build/vercel.jsonfor reference. - It fails on
fluid: false.
A prebuilt flow (vercel deploy --prebuilt from CI, no Vercel Git integration) never runs buildCommand, so set build.vercel.reconcileVercelJson: false there. The target then leaves any committed vercel.json alone, including one with fluid: false. Keep Fluid compute on in the Vercel project either way, because the function is built for that concurrency model. See the vercel target and Vercel.
Edge feature gateCopy link to section: Edge feature gate
The hono target serves a subset of B4.run. The build fails with B4_E1005, naming every offending config key and file at once, when the app uses any of these:
- the sandbox,
backends.filesystemorbackends.exec,- a config-supplied store,
toolOutput,- a
workspace/directory, or - route-level long-term memory.
The vercel target applies the same gate, and so does b4 check whenever hono or vercel is a configured target. See Edge and Hono.
If you upgrade an app that sets both toolOutput and "hono", a build that passed before now fails. Remove the key, or drop "hono" from build.targets. See Upgrading.
See Deployment for how the targets fit together.
b4 startCopy link to section: b4 start
b4 start serves the app in production with the real B4.run runtime (Agent Protocol, AG-UI, and /healthz). It binds 0.0.0.0:8000 by default.
b4 start
b4 start --host 127.0.0.1 --port 3000Flags:
--host <host>: host to bind. Default:0.0.0.0(or theHOSTenv var).--port <number>: port to bind. Default:8000(or thePORTenv var).
This is what the node build target's generated Dockerfile runs (CMD ["node", ".b4/build/server.mjs"]), and it's the only server that runs the execution sandbox in production. See Node and Docker.
b4 runCopy link to section: b4 run
b4 run invokes one route, reading JSON from stdin and writing JSON to stdout.
echo '{"messages":[{"role":"user","content":"Hello"}]}' | b4 run '/navlog'Flags:
--cwd <path>: operate on a different app root.--url <url>: run against a live dev server rather than the in-process runtime.
The route argument can be the route id (/navlog) or its entry file (src/app/navlog/index.ts). Dynamic segment values come from the JSON input. With --url, b4 run POSTs to /threads/<t-cli-uuid>/runs/wait with { route: "<routeId>#<kind>", input }.
Without --url, b4 run regenerates the .b4/ typegen output first, so a tool you just added or changed is bound with its current input schema. You don't need to run b4 typegen yourself. If regeneration fails, for example because a tool's input type can't be resolved, b4 run prints a warning on stderr and runs the route with the schemas already on disk.
b4 testCopy link to section: b4 test
b4 test runs every colocated run.test.ts scenario in the app.
b4 test
b4 test src/app/(public)/helloFlags:
--cwd <path>: operate on a different app root.
The optional positional [path] argument limits the run to scenarios under a subdirectory. To target a live dev server, add .server(url) to the scenario's builder chain inside run.test.ts. (b4 test has no --url flag.) Before running in-process scenarios, b4 test regenerates the .b4/ typegen output the same way b4 run does. It exits non-zero on any failure and prints a diff for each scenario that didn't match. See Testing for how to write scenarios.
b4 evalCopy link to section: b4 eval
b4 eval runs every colocated *.eval.ts over its dataset, reports per-case scores, then gates on the aggregate.
b4 eval
b4 eval src/app/chat
b4 eval --live
b4 eval --record
b4 eval --jsonFlags:
--cwd <path>: operate on a different app root.--live: run against the real model (requiresOPENAI_API_KEY). Never use it in CI.--record: record real-model responses into sibling fixture files (requiresOPENAI_API_KEY). Never use it in CI. Mutually exclusive with--live.--json [file]: write a JSON report. Defaults to.b4/eval-report.json.
The optional positional [path] limits discovery to a subdirectory. By default each case replays its aimock fixtures, which is deterministic and safe for CI. --live calls the real provider while you tune prompts locally. --record saves real-model responses as fixture files you commit, and plain b4 eval replays them. A failing gated eval exits non-zero, so CI fails when quality drops below the bar. Informational evals (no gate/threshold) never affect the exit code. See Evals for how to write them.
b4 devCopy link to section: b4 dev
b4 dev starts the local runtime with hot reload and the Agent Protocol (AP) HTTP endpoints. It always binds 127.0.0.1.
b4 dev
b4 dev --port 3001Flags:
--port <n>: HTTP port. Default: dynamically allocated. Pass--portfor a stable address.--env-file <path>: path to a.envfile (overridesb4.config.tsenvand the default./.env).
The default port changes from run to run, so pass --port (or read the port b4 dev prints) before you copy a curl example.
If the loaded environment has LANGSMITH_API_KEY and LANGCHAIN_TRACING_V2 isn't set, b4 dev turns on LangSmith tracing by setting LANGCHAIN_TRACING_V2=true. Set LANGCHAIN_PROJECT to choose the project that receives the traces. See Observability for tracing and Agent Protocol for the protocol reference.
b4 memoryCopy link to section: b4 memory
b4 memory inspects and manages the app's long-term memory
store, the typed records the agent writes with its generated remember tool.
Use it to review and promote the candidate writes that the default memory: { writes: "candidate" }
config holds back from recall.
b4 memory list
b4 memory search <query>
b4 memory inspect <id>
b4 memory approve <id>
b4 memory reject <id>
b4 memory forget <id>
b4 memory prune [--cap <n>] [--namespace <prefix>]
b4 memory consolidate [--dry-run] [--namespace <prefix>] [--model <id>] [--provider <id>] [--max-batches <n>]
b4 memory reflect [--dry-run] [--namespace <prefix>] [--model <id>] [--provider <id>] [--max-batches <n>]Subcommands:
list: list pending candidate records.search <query>: list candidates whose content or namespace matches<query>.inspect <id>: print one record as formatted JSON.approve <id>: promote a candidate to anactiverecord sorecallsurfaces it.reject <id>: drop a candidate without promoting it.forget <id>: delete a record by id.prune: run episodic retention by hand. It deletes expired records (TTL) and enforces the per-namespace episode cap.--cap <n>overrides the cap for this pass, and--namespace <prefix>limits it to namespaces that match the prefix.consolidate: run distillation's compaction pass. It groups old episodic records by namespace and ISO week, summarizes each group with one model call, then supersedes the sources and gives them a TTL sopruneremoves them later.reflect: run distillation's insight pass. It derives durable insights from each namespace's records newer than its watermark. Insights are written ascandidateby default. Approve them withapprove <id>or the Inspector.
consolidate and reflect share the same flags. Below the configured
thresholds, both print one line and exit 0 without creating a model or
needing an API key. So it's safe to run the cron recipe
(b4 memory consolidate && b4 memory reflect) nightly on any app. Their flags:
--dry-run: report the plan without making any model call.--namespace <prefix>: scope the pass.--model <id>/--provider <id>: overridememory.distill.--max-batches <n>: cap the work (and spend) for one invocation.
These are the only b4 memory subcommands that spend model tokens. See Distillation for the full configuration
block.
Flags:
--cwd <path>: operate on a different app root.
b4 inspectCopy link to section: b4 inspect
b4 inspect opens the B4.run Inspector, a localhost-only
browser UI for browsing, searching, and approving the app's
long-term memory records.
b4 inspectThe inspector binds to 127.0.0.1 on a free port and prints the URL to open.
Flags:
--cwd <path>: operate on a different app root.--port <number>: bind the inspector to a stable localhost port.--env-file <path>: path to a.envfile (overridesb4.config.tsenvand the default./.env).
b4 addCopy link to section: b4 add
b4 add fetches a blueprint (an integration guide written for coding agents) and prints it to stdout, so you can hand it to your coding agent.
b4 add # list available blueprints, grouped by category
b4 add pgvector # print the pgvector blueprint
b4 add <url> # fetch a third-party blueprint from any URLb4 add only prints the guide. Your agent applies the changes, and you review them. To use a self-hosted catalog, set B4_BLUEPRINTS_URL.
See Blueprints for authoring and catalog details.
b4 approvalsCopy link to section: b4 approvals
b4 approvals manages the records behind
approval grants.
b4 approvals prune
b4 approvals prune --retention <ms>prune deletes grant records voided because the thread moved past the prompt
longer ago than the retention window. A consumed grant is voided when its
resumed turn completes; an outstanding grant, or a consumed grant whose resume
never completed, is never deleted, however old. The window is
approvals.grantRetentionMs (7 days by
default); --retention <ms> overrides it for one pass. The runtime runs the
same pass on its own, at most once an hour, so this is for cron or a one-off.
It prints pruned: <n>. With grants on and no store file yet, it creates the
default store file (.b4/interrupt-grants.sqlite) and prunes nothing; with
grants off and no store file, it says so and exits cleanly.
Flags:
--retention <ms>: retention window for this pass, a positive integer of at most one year.--cwd <path>: operate on a different app root. Give it before the subcommand (b4 approvals --cwd app prune), as withb4 memory.
b4 docsCopy link to section: b4 docs
b4 docs prints the B4.run docs bundled with the installed CLI, so you (or a
coding agent) can read the docs for the version you're running without going
to the network. With no topic, it lists the available topics and the index.
With a topic, it prints that doc to stdout.
b4 docs
b4 docs README
b4 docs cliThe optional positional [topic] picks one doc by slug (with or without the
.md suffix). An unknown topic exits non-zero and lists the available topics.
In a source checkout, build the CLI first (pnpm --filter @b4run/cli build)
so the bundled docs exist.
b4 client-toolsCopy link to section: b4 client-tools
b4 client-tools manages the records behind
client-provided tools on the AG-UI endpoint.
b4 client-tools prune
b4 client-tools prune --retention <ms>prune deletes records that can no longer affect a turn: answered or voided
client calls settled longer ago than the retention window, outstanding client
calls whose expiry is older than it, and server tool calls settled longer ago
than it. An outstanding, unexpired client call and an unsettled server call
are never deleted. The window is
server.agui.clientToolRetentionMs (7 days
by default) and never shorter than clientToolTtlMs; --retention <ms>
overrides it for one pass. The runtime runs the same pass on its own when a
turn settles, at most once an hour, so this is for cron or a one-off. It
prints pruned: <n>, or says so when the app has no client tool store.
Flags:
--retention <ms>: retention window for this pass, a positive integer of at most one year.--cwd <path>: operate on a different app root. Give it before the subcommand (b4 client-tools --cwd app prune), as withb4 memory.
b4 threadsCopy link to section: b4 threads
b4 threads inspects and reattaches to Agent Protocol threads on a running B4.run server.
b4 threads tail <thread-id>
b4 threads tail <thread-id> --url http://127.0.0.1:3000
b4 threads tail <thread-id> --header "authorization: Bearer $TOKEN"
b4 threads tail <thread-id> --jsontail rejoins a thread through
GET /threads/:thread_id/runs/stream (the
reattach endpoint). Use it to recover after a disconnect, a reload, or a lost
terminal. It prints a snapshot first, then follows whatever comes next:
- When a turn is streaming in that server process, the snapshot carries the
committed transcript plus the turn's output so far, and the command then tails
live frames until the turn emits
done. - When no turn is live there, the snapshot is the durable one (the latest checkpoint plus any parked human-in-the-loop prompts), and the command exits right away. This path reads from the checkpoint, so it works across restarts and replicas.
The header line reports status, whether the attach is live, the turn's
run_started_at, and the checkpoint anchor. If run_started_at changes
between reattachments, a different turn now holds the thread. anchor ties a
resumed turn back to the run it continues.
Attaching never cancels anything or takes the thread's run slot, so it's safe
to tail a thread someone else is driving. To stop a run, use
POST /threads/:thread_id/cancel.
Flags:
--url <url>: base URL of the running B4.run server (defaulthttp://127.0.0.1:3000).--header <name: value>: extra request header, repeatable. Use it when the server's middleware authenticates the thread's route.--json: print raw SSE frames as JSON, one per line.
tail exits with:
1when the stream ends without a terminaldoneframe, or the server detaches the viewer (at the per-thread viewer cap, or when the consumer falls behind). Reconnect for a fresh snapshot.2when the server is unreachable, the thread doesn't exist, or the thread has never run (so there's no route to authorize against).
Exit codesCopy link to section: Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Validation failure (e.g. b4 check) or scenario failure (e.g. b4 test) |
| 2 | Configuration / runtime error (missing b4.config.ts, bad appDir, scenario load failure) |
Non-zero exit codes from underlying tools (Commander, child processes) may be propagated unchanged.