Essay · 7 min read
Why we built B4.run
B4.run is a TypeScript framework for LangGraph.js agents. Here is the problem it solves and its conventions: file-system routes, local tools and typegen.
I built B4.run to make TypeScript agent applications easier to understand and maintain. I wanted to open a project, find the agent I needed to change, and see its tools and state right beside it.
If you've built an Express or Angular application, you've probably asked your team: where should this code live? A consistent answer makes the next feature easier to build and the next pull request easier to review.
Agent applications need that consistency too. Let's look at the problem, then walk through the approach I took with B4.run.
What is an agent application?Copy link to section: What is an agent application?
For this discussion, an agent is a model that can request actions through tools. A tool is application code that performs an operation, such as looking up an order or reading a document.
Here is roughly how it works:
- A user sends a request to the application.
- The runtime sends the request, instructions, and available tool definitions to the model.
- The model responds with text or requests a tool call.
- If it requests a tool, the runtime executes the function and returns its result to the model.
- The model uses that result to continue working or answer the user.
LangGraph.js provides the runtime for this loop. B4.run adds conventions and tooling for the TypeScript application around it.
The problemCopy link to section: The problem
Calling a model is only part of building an agent application. We also need to decide where tools live, how to validate their inputs, how to test the application, and how to run it in production.
Without shared conventions, those decisions pile up. A tool has a function in one file, a schema in another and an entry in a registry somewhere else. Adding a feature means remembering which files describe the same thing.
The problem gets worse as the application grows. If a support agent has ten tools, I want to find those tools by opening the support route. If I change a tool's input, I want TypeScript to help me find the code affected by that change.
This is the work I wanted the framework to handle.
GoalsCopy link to section: Goals
My goals for B4.run are to:
- Give each agent or workflow a predictable location in the project.
- Keep route-specific tools and state beside the route that uses them.
- Derive tool input schemas from TypeScript function signatures.
- Provide a local development server and generated deployment output.
- Add planning, memory, and delegation as the application needs them.
Let's start with the files a developer writes.
A route is a folderCopy link to section: A route is a folder
A route lives under src/app/. Here is a small example:
src/
app/
(public)/
hello/
[tenant]/
index.ts
state.ts
tools/
greet.tsA few things to note:
index.tsis the entry point for the route.[tenant]declares a dynamic route parameter, such as the organization using the application.(public)groups files without becoming part of the route id. The folder name grants no access by itself. Authentication is still up to your application code.tools/contains tools specific to this route.state.tscan declare the data the route accepts and maintains.
The route id is /hello/[tenant]. If you have used a filesystem router in a web framework, this should look familiar.
Define the agentCopy link to section: Define the agent
Next, let's define the route entry:
// src/app/(public)/hello/[tenant]/index.ts
import { agent } from "@b4run/sdk"
export default agent({
model: "gpt-5-mini",
systemPrompt: "You are a helpful assistant for the {tenant} organization.",
})Let's quickly review:
- First, we import the
agent()function from the SDK. It describes the agent, and the runtime uses that description when it executes the route. - Then,
modelselects the model. - Finally,
systemPromptgives the agent its instructions, with{tenant}filled in from the route context.
To let the agent do something in our application, we need a tool.
Add a toolCopy link to section: Add a tool
A tool can be an ordinary TypeScript function:
// src/app/(public)/hello/[tenant]/tools/greet.ts
export const description = "Greet a user by name."
export default async ({ name }: { readonly name: string }) => {
return `Hello, ${name}!`
}Let's break this down:
- The
descriptiontells the model what the tool is for, so it can decide when to request it. - The default export is the function that the runtime will execute.
- The input type declares a required
namestring. B4.run uses this signature to generate the schema provided to the model. - The returned string becomes the tool result, which the model can use in its response.
The model requests the call and your TypeScript function does the work. In a real application, the same pattern could call a service, query a database or create a draft for review.
During type generation, B4.run also writes .b4/b4.generated.d.ts. This gives us route and tool types without maintaining another copy of the input shape.
From source files to a running applicationCopy link to section: From source files to a running application
The CLI uses the route tree in both development and build workflows.
b4 dev starts the local runtime and reloads application code as it changes. HTTP clients create a thread, then start a run under that thread. A thread holds the conversation and its saved state. A run is one execution of the agent within that conversation.
For the LangSmith deployment target, b4 build:
- Discovers the routes under
src/app/. - Generates types for route state and tools.
- Writes
.b4/build/langgraph.jsonand the route entry modules it references.
The generated agent entry imports our descriptor and connects the discovered tools to a LangGraph graph. A route such as /hello/[tenant] is addressed as /hello/[tenant]#agent in that deployment configuration.
We keep editing TypeScript source files, and the build produces what the deployment target needs. The Deployment guide covers the available targets and their differences.
Add capabilities as you need themCopy link to section: Add capabilities as you need them
A larger application may need the agent to plan work, consult instructions, or ask another agent for help. B4.run gives each of these a place in the project.
MemoryCopy link to section: Memory
An app's workspace/AGENTS.md provides persistent instructions. B4.run reads the file into the agent's prompt under a # Memory heading on each model turn, subject to its size limit.
For example, the file might describe how the application organizes its documents. Updating the file changes what the agent sees on its next turn. This is file-based prompt memory. Typed long-term records are a separate feature, described in the Memory guide.
PlanningCopy link to section: Planning
A route's plan.md can seed a checklist for the agent. B4.run adds a writeTodos tool and stores the current todos in state. It also emits plan_update events so a client can display progress.
SkillsCopy link to section: Skills
A skill is a longer set of instructions stored at skills/<name>/SKILL.md under the route. B4.run lists the available skills and provides readSkill({ name }) so the agent can load the full instructions when needed.
A support application could use a skill to spell out the steps for investigating a billing question. The instructions stay readable in a Markdown file.
SubagentsCopy link to section: Subagents
A subagent handles a bounded task for a parent agent. For example, a support agent could ask a research agent to find relevant policy documents before drafting an answer.
Children can live under the route's subagents/ directory or be registered in the descriptor's keyed subagents object. Each child has its own instructions and tool configuration. The Subagents guide explains registration and delegation permissions.
Getting startedCopy link to section: Getting started
For a small example, pick the basic template, which is now the default. The larger research application needs --template research:
npm create b4-app@latest my-agents -- --template basic
cd my-agents
npm installUse Node.js 24 or later. Follow the generated project's environment instructions to configure your model provider key, then start the development server:
npm run devThe Getting Started guide walks through the starter and its first run. If you already have a LangGraph.js application, the migration guide maps its existing pieces to B4.run's conventions.
ConclusionCopy link to section: Conclusion
I like frameworks that make the next change easier to understand. With B4.run, a route has a folder, its tools are typed functions, and the build derives the wiring from those files.
A small application may be well served by LangGraph.js on its own. As you add routes, tools and capabilities, shared conventions cut down the setup a team has to maintain. That's why I built B4.run: so we spend more time on what an agent should do, in a structure that shows how it does it.