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:
npm create b4-app@latest my-navlog -- --template navlog
cd my-navlog
npm installThe --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:
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 excerptsplan.mdturns on planning. Its checklist seeds each thread's todos.workspace/AGENTS.mdand the route'smemory.mdadd persistent prompt guidance.memory.tsdefines typed memory records that the agent reads withrecalland writes withremember.- 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:
npm run typegen
npm run check
npm run typechecktypegen writes server/.b4/b4.generated.d.ts. check validates routes, tools and configuration without writing files:
B4.run app is valid: 3 routes discovered.
- /navlog (agent)
- /navlog/subagents/performance (agent)
- /navlog/subagents/weather (agent)Next, run both packages' tests:
npm testThe 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:
npm run evalThe 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:
cp server/.env.example server/.env
# Add your OPENAI_API_KEY to server/.env
npm run verify
npm run dev:serververify 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:
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:3002The 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:
npm run dev:webOpen 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:
npx b4 inspect --cwd serverWhen you're ready to ship, compare Deployment Options and follow Node and Docker for the default self-hosted path.