Dawn

Essay · 6 min read

The App Router for AI Agents

File-system routes, type-safe tools, and the capability layer Dawn adds around real LangGraph.js agent applications.

May 19, 2026

The Next.js App Router changed how many of us think about application structure.

Not because a file-system router is a new idea.

The important part was that it gave common web application concepts a place to live. A page is a file. A layout is a file. A route group is a folder. You can open the tree and get a useful read on the application.

I want the same thing for agent applications.

Agent codebases need more than a runtime. They need a project shape that can hold tools, state, tests, memory, planning, skills, and subagents without turning into a pile of registries.

That is the App Router idea behind Dawn.

Goals

Here is the practical version:

  • A route should be a folder under src/app/.
  • The route path should be the agent endpoint.
  • Tools should live next to the route that uses them.
  • Tool types should come from TypeScript, not duplicated schemas.
  • Tests should live beside the route they protect.
  • Agent behavior should compose through files and descriptors, not hidden setup code.

If a developer can open the file tree and understand where to add the next thing, the framework is doing useful work.

What the route tree tells you

Here is a small Dawn application:

text
src/
  app/
    support/
      [tenant]/
        index.ts
        state.ts
        plan.md
        run.test.ts
        tools/
          lookupOrder.ts
          escalate.ts
        skills/
          refund-policy/
            SKILL.md
        subagents/
          research/
            index.ts
            tools/
              searchDocs.ts

Read that tree out loud and you already know quite a bit.

There is a parameterized route at /support/[tenant].

It has its own route entry in index.ts, its own state schema, a seeded plan, a scenario test, two route-local tools, one route-local skill, and a research subagent.

That is not just aesthetic. It changes how the codebase behaves.

You can move the route. You can delete it. You can review it in a pull request. You can ask, "what tools does this agent have?" and answer the question from the folder.

The structure is doing work.

Routes

A Dawn route is a folder under src/app/. The folder path becomes the route id.

Route groups are ignored:

text
src/app/(public)/hello/[tenant]/index.ts

becomes:

text
/hello/[tenant]

The route entry is index.ts. It exports exactly one route shape:

  • agent for an LLM-driven route
  • workflow for a deterministic async function
  • graph for a raw LangGraph graph
  • chain for a LangChain runnable

The default scaffold uses an agent:

ts
import { agent } from "@dawn-ai/sdk"
 
export default agent({
  model: "gpt-4o-mini",
  systemPrompt: "You are a helpful assistant for the {tenant} organization.",
})

If you need full LangGraph control, export a named graph. If you need a deterministic flow, export a workflow. The route folder is the stable boundary either way.

Tools

In Dawn, a tool is a TypeScript file in tools/.

ts
// src/app/support/[tenant]/tools/lookupOrder.ts
export const description = "Look up an order by id."
 
export default async (
  input: { readonly orderId: string },
  ctx: { readonly signal: AbortSignal },
) => {
  const res = await fetch(`https://api.example.com/orders/${input.orderId}`, {
    signal: ctx.signal,
  })
  return (await res.json()) as { readonly status: string }
}

There is no tool() wrapper in the route.

There is no duplicate input schema.

Dawn reads the function signature during type generation and build. The input type becomes the JSON schema exposed to the model, and the generated dawn:routes module gives route code typed access to ctx.tools.lookupOrder.

This is the part I care about most: the TypeScript type is the contract.

State and tests

Routes can also define state.ts.

State is the JSON shape that flows through the route runtime. The scaffold uses Zod, but Dawn accepts Zod or any Standard Schema value.

ts
// src/app/support/[tenant]/state.ts
import { z } from "zod"
 
export default z.object({
  tenant: z.string(),
  orderId: z.string().optional(),
})

Dawn generates route state types from this file and the route path. If you rename a dynamic segment, or change the state shape, TypeScript can catch the places that still assume the old shape.

Tests live next to the route too:

text
src/app/support/[tenant]/run.test.ts

That sounds small, but it matters. The test changes in the same pull request as the route. It does not live in a distant test directory that slowly stops explaining the feature it was written for.

Agent behavior

The route tree gives Dawn a place to add higher-level agent behavior.

Here is what we have built to date.

Memory

If workspace/AGENTS.md exists, Dawn injects it into the agent prompt under # Memory on every model turn.

The file is the memory. The agent can update it, and the next turn sees the updated content.

Planning

If a route has plan.md, Dawn adds a planning prompt, a writeTodos tool, a todos state channel, and a plan_update stream event.

The seed file uses normal markdown checklist syntax:

md
- [ ] Review the customer request
- [ ] Check order history
- [ ] Write a concise response

The useful part is that planning is not just a prompt. It is prompt, tool, state, and stream behavior composed together.

Skills

If a route has skills/<name>/SKILL.md, Dawn lists the available skills in the prompt and gives the agent a readSkill({ name }) tool.

That lets the agent load longer instructions only when it needs them.

Subagents

A route can expose subagents through child routes under subagents/ or through the subagents field on agent({...}).

Dawn adds a task({ subagent, input }) tool and a # Subagents prompt section. The parent agent can delegate to a specialist without the specialist becoming a random helper function hidden in another folder.

The boundary stays visible on disk.

Reasoning effort

For OpenAI-backed agent routes, the descriptor can include:

ts
reasoning: { effort: "high" }

Non-reasoning models ignore it. For models that support the setting, the option stays close to the route that needs it.

What this buys you

The benefit is not the first route.

The first route is always easy.

The benefit shows up when the app has ten routes, forty tools, a few specialists, and enough state that a hand-written registry starts to feel like another product you have to maintain.

At that point, the App Router idea earns its place:

  • file moves are meaningful
  • type generation has one source of truth
  • tests have a home
  • generated deployment artifacts are predictable
  • memory, planning, skills, and subagents compose around the route instead of around a hidden framework object

This does not make Dawn necessary for every project.

If you have one graph and two tools, raw LangGraph.js may be exactly right.

But if your agent application is starting to look like an application, it needs application structure.

That is what Dawn is trying to provide.

Getting started

The fastest way to try the shape is still the scaffold:

bash
pnpm create dawn-ai-app my-agents
cd my-agents
pnpm dev

Then run the example route:

bash
echo '{"tenant":"acme"}' | pnpm exec dawn run '/hello/[tenant]'

For the deeper read, start with Mental Model, Routes, Memory, Planning, Skills, Subagents, and Reasoning Effort.

Start building.

Scaffold a Dawn app, open the example, and see whether the shape fits your team in under five minutes.

$ pnpm create dawn-ai-app
Star on GitHub