Build a Flight Planner

Learn how to build a VFR flight planner with subagents, planning, memory and a web UI.

This recipe starts from the navlog template. It picks up where Getting Started leaves off. If you haven't built the hello agent yet, start there.

What will we build?Copy link to section: What will we build?

The template is a VFR flight planner for a Cessna 172N at /navlog. It briefs the weather from live aviationweather.gov data (no key), sends a weather and a performance subagent out, computes the navlog in code from the 1978 172N POH tables, and files an ICAO flight plan only when the pilot asks and a person approves. A browser client, the B4.run Workbench, sits in front of it.

The unit tests and evals need no API key. Live runs need a real model and an OpenAI API key.

Create the appCopy link to section: Create the app

First, scaffold the app and install its dependencies:

bash
npm create b4-app@latest my-navlog -- --template navlog
cd my-navlog
npm install

The --template navlog flag picks the flight-planner template instead of the default. You need Node.js 24 or later and npm 11.

Look at the agentsCopy link to section: Look at the agents

The app is an npm workspace with two packages. server/ is the B4.run app, and web/ is the Workbench. The root npm install installs both.

The planning route lives in server/src/app/navlog/. Let's look at the coordinator, one of its subagents and the app config:

import { agent } from "@b4run/sdk"
 
export default agent({
  model: "gpt-5-mini",
  // A plan fans out: recall → plan → two subagents → compute → brief. That
  // legitimately exceeds LangGraph's default 25 super-steps.
  recursionLimit: 100,
  description:
    "A VFR flight planner for a Cessna 172N: briefs weather, looks up POH performance, computes the navlog in code, and files a flight plan on request.",
  tools: { deny: ["runBash"], approve: ["fileFlightPlan"] },
  systemPrompt: `You are a VFR flight-planning assistant for a Cessna 172N. Given a request:
 
1. Start with \`recall({ query: "aircraft profile and pilot preferences" })\`. The profile holds the tail number, cruise RPM and usable fuel. If none is stored, ask once, then \`remember\` what the pilot tells you.
2. Parse the request into departure, destination, optional waypoints, cruise altitude and departure time (UTC). Ask once if altitude or time is missing. Departure time: an ISO 8601 UTC instant such as 2026-10-06T14:00:00Z, or a UTC clock time such as 1400Z, which means the next occurrence. Pass it to computeNavlog as given; do not convert it yourself.
3. Record the legs as todos.
4. Dispatch \`task({ subagent: "weather", input: "<airports, waypoints, altitude, departure time>" })\` and \`task({ subagent: "performance", input: "<airports, altitude, cruise RPM>" })\`.
5. Call \`lookupAirport\` for each airport you have not already looked up, then \`computeNavlog\` with the waypoints, the altitude, the departure time, the aircraft profile and one wind entry per leg from the weather brief. Never do navigation arithmetic yourself.
6. Save the navlog with \`writeFile({ path: "reports/<departure>-<destination>.md", content: "<markdown table of the legs and totals>" })\`.
7. If a chart would help, \`renderChart({ title, series })\` with fuel remaining by checkpoint.
8. Reply with a short plain-language brief: flight category at each airport, winds at altitude, fuel burned and reserve, and anything that should give the pilot pause. Name the category at each airport both now and at the ETA. Always cite at least one POH figure from the performance brief in the form [poh/<file>.md, Figure N]. Never echo tool-call syntax such as recall({...}) in the reply.
9. When the pilot states a durable preference or an aircraft fact, call \`remember({ data, content })\`.
10. File a flight plan with \`fileFlightPlan({ flightPlan })\` only when the pilot asks. A person approves it before it runs.`,
})

The coordinator plans the flight, sends the weather and performance subagents out, and calls computeNavlog for every number on the navlog. The tools are shared in server/src/tools/, and each subagent's tools scope keeps it to its own. fileFlightPlan asks a person before each call. The config keeps tool output inline up to 12,000 characters and makes new memories wait for review. Threads persist to SQLite by default, so they survive a restart.

A few more files shape the agent:

text
server/src/app/navlog/
  plan.md                       # seeds each thread's todo list
  memory.md                     # route-specific prompt guidance
  memory.ts                     # typed cross-session memory
  subagents/weather/            # METARs, TAFs, winds aloft, advisories
  subagents/performance/        # POH tables for the actual fields
  skills/brief-weather/         # loaded on demand: go/no-go brief
  skills/poh-lookup/            # loaded on demand: reading and citing the POH
  evals/navlog-quality.eval.ts
server/src/tools/
  computeNavlog.ts              # the navlog, computed in code
  fileFlightPlan.ts             # records an ICAO flight plan, after approval
  getMetar.ts getTaf.ts getWindsAloft.ts getAdvisories.ts lookupAirport.ts
  readDoc.ts                    # reads a POH table or regulation excerpt
  renderChart.ts                # draws a bar chart the user sees
server/src/lib/                 # the math, POH tables and parsers the tools call
server/workspace/
  AGENTS.md                     # prompt guidance injected every turn
  poh/ regs/                    # the POH tables and regulation excerpts
  • plan.md turns on planning. Its checklist seeds each thread's todos.
  • workspace/AGENTS.md and the route's memory.md add persistent prompt guidance.
  • memory.ts defines typed memory records that the agent reads with recall and writes with remember.
  • The agent loads skills under skills/ by name when it needs them.

Verify and testCopy link to section: Verify and test

Run these from the workspace root. Each script delegates to the package that owns it:

bash
npm run typegen
npm run check
npm run typecheck

typegen writes server/.b4/b4.generated.d.ts. check validates routes, tools and configuration without writing files:

text
B4.run app is valid: 3 routes discovered.
- /navlog (agent)
- /navlog/subagents/performance (agent)
- /navlog/subagents/weather (agent)

Next, run both packages' tests:

bash
npm test

The server tests are unit tests of the navlog math, the POH tables, the weather parsers and the tools, with fetch stubbed. The web tests cover the Workbench. Neither needs an API key.

Then run the quality eval:

bash
npm run eval

The eval plans three flights. Its scorers require both subagents, a well-formed navlog whose totals add up with a legal reserve, a POH citation, no filing unless asked, and a passing quality grade from a model judge. Scripted fixtures cover the agent and the judge, so the eval runs without an API key; the weather tools still reach aviationweather.gov.

Run it liveCopy link to section: Run it live

These fixture-backed runs prove the app works without a key. To plan a new flight, we need a real model.

Copy the server's environment example, add your API key, run the preflight and start the dev server:

bash
cp server/.env.example server/.env
# Add your OPENAI_API_KEY to server/.env
npm run verify
npm run dev:server

verify checks the app, its types, its dependencies, Node and the provider environment. The dev server listens on http://127.0.0.1:3002 and serves Agent Protocol and AG-UI.

In a second terminal, ask for a flight:

bash
cd server
echo '{"messages":[{"role":"user","content":"Plan a VFR flight from KSTP to KRST at 4500 feet, departing 1400Z, in N738ZU (172N, 2400 RPM, 50 gal usable), and save the navlog."}]}' | npx b4 run /navlog --url http://127.0.0.1:3002

The navlog lands in server/workspace/reports/. Thread state is saved to server/.b4/checkpoints.sqlite, so threads survive dev-server restarts.

See it in a UICopy link to section: See it in a UI

The Workbench is a chat UI over the same agent. Leave the server running and start it in another terminal:

bash
npm run dev:web

Open http://localhost:3010 and choose Plan a flight. You'll see the plan card and the weather and performance subagent cards update as the agent works, tool cards, the flight-plan approval and a panel for reviewing memory. The Workbench talks to B4.run over AG-UI and holds no model credentials. Those stay in server/.env.

To build your own client, follow Flight Planner Web UI.

The scaffold also installs the Inspector. Open it in a third terminal to review the records the agent saves with remember:

bash
npx b4 inspect --cwd server

When you're ready to ship, compare Deployment Options and follow Node and Docker for the default self-hosted path.