diff --git a/apps/web/app/components/docs/nav.ts b/apps/web/app/components/docs/nav.ts
index e7247441..0206b9d5 100644
--- a/apps/web/app/components/docs/nav.ts
+++ b/apps/web/app/components/docs/nav.ts
@@ -24,6 +24,7 @@ export const DOCS_NAV: readonly DocsNavSection[] = [
{ label: "Agents", href: "/docs/agents" },
{ label: "Tools", href: "/docs/tools" },
{ label: "State", href: "/docs/state" },
+ { label: "Capabilities", href: "/docs/capabilities" },
{ label: "Middleware", href: "/docs/middleware" },
{ label: "Retry", href: "/docs/retry" },
],
diff --git a/apps/web/app/docs/capabilities/page.tsx b/apps/web/app/docs/capabilities/page.tsx
new file mode 100644
index 00000000..081a074e
--- /dev/null
+++ b/apps/web/app/docs/capabilities/page.tsx
@@ -0,0 +1,9 @@
+import type { Metadata } from "next"
+import Content from "../../../content/docs/capabilities.mdx"
+import { DocsPage } from "../../components/docs/DocsPage"
+
+export const metadata: Metadata = { title: "Capabilities" }
+
+export default function Page() {
+ return
+}
diff --git a/apps/web/app/llms.txt/route.ts b/apps/web/app/llms.txt/route.ts
index e98e1c17..6f455c71 100644
--- a/apps/web/app/llms.txt/route.ts
+++ b/apps/web/app/llms.txt/route.ts
@@ -15,7 +15,7 @@ function buildLlmsTxt(): string {
[
"# Dawn",
"",
- "Dawn is a TypeScript-first meta-framework for building graph-based AI agents with the ergonomics of Next.js: file-system routing, co-located tools with inferred types, scenario testing, and a local dev server with LangSmith-style endpoints.",
+ "Dawn is a TypeScript-first meta-framework for building graph-based AI agents with the ergonomics of Next.js: file-system routing, co-located tools with inferred types, scenario testing, route-level capabilities, and a local dev server with LangSmith-style endpoints.",
"",
"## Install",
"```",
@@ -29,6 +29,7 @@ function buildLlmsTxt(): string {
"- `src/app/**/index.ts` — route entry; exports exactly one of default `agent(...)`, named `workflow` (async function), named `graph` (LangGraph graph), or named `chain` (LangChain LCEL Runnable).",
"- `src/app/**/state.ts` — optional Zod route state schema.",
"- `src/app/**/tools/*.ts` — co-located tools (default export is an async function; types inferred).",
+ "- `src/app/**/plan.md`, `skills//SKILL.md`, and `subagents//index.ts` — optional agent capabilities.",
"- `src/app/**/run.test.ts` — colocated scenario tests.",
"- `.dawn/dawn.generated.d.ts` — auto-generated ambient types. Never edit by hand.",
"",
diff --git a/apps/web/content/blog/2026-05-19-app-router-for-ai-agents.mdx b/apps/web/content/blog/2026-05-19-app-router-for-ai-agents.mdx
index a724ecc4..6c846bf4 100644
--- a/apps/web/content/blog/2026-05-19-app-router-for-ai-agents.mdx
+++ b/apps/web/content/blog/2026-05-19-app-router-for-ai-agents.mdx
@@ -1,129 +1,122 @@
---
title: The App Router for AI Agents
-description: File-system routes, type-safe tools, and why the mental model matters for agent codebases that grow.
+description: File-system routes, type-safe tools, and the capability layer Dawn adds around real LangGraph.js agent applications.
date: 2026-05-19
tags: [philosophy, typescript, agents]
type: post
author: brian
-draft: true
---
-The Next.js App Router did something subtle and important.
+The Next.js App Router changed how many of us think about application structure.
-Not because it introduced a new runtime.
-And not because it changed how React rendered.
+Not because a file-system router is a new idea.
-What it changed was the answer to the question *"where does this code go?"*.
+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.
-A page is `page.tsx`. A layout is `layout.tsx`. A server action is a function in a server file. Middleware lives at `middleware.ts`. One place each concept lives, and that place is a file on disk.
+I want the same thing for agent applications.
-Agent codebases do not have that yet.
+Agent codebases need more than a runtime. They need a project shape that can hold tools, state, tests, memory, planning, skills, and sub-agents without turning into a pile of registries.
-Dawn is an attempt to fix it.
+That is the App Router idea behind Dawn.
-## tl;dr
+## Goals
-- agent codebases need a coordinate system, not just a runtime
-- file-system routes give every concern a folder, not a registry entry
-- co-location is a feature: refactors work, search works, tests do not drift
-- typescript function signatures are the tool contract — no zod, no `tool()` wrapper
-- the win is not the first agent. it is the tenth.
+Here is the practical version:
-## The missing coordinate system
+- 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 capabilities should compose through files and descriptors, not hidden setup code.
-If you have worked on a LangGraph project past the second graph, you know the shape of the problem.
+If a developer can open the file tree and understand where to add the next thing, the framework is doing useful work.
-Graphs in one folder. Tools in another. State definitions in a third. A registry tying them together.
+## What the route tree tells you
-The connections between those files are implicit. A tool is "for" a graph because it is imported by it, not because it lives next to it.
+Here is a small Dawn application:
-That works at the kitchen-table scale.
+```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
+```
-It stops working when you need to answer questions like:
+Read that tree out loud and you already know quite a bit.
-- which tools does the `support` graph have access to?
-- if I rename `lookupOrder`, what else needs to change?
-- where do I add the tenant-aware auth check for the `triage` route?
-- can I delete this tool? who calls it?
+There is a parameterized route at `/support/[tenant]`.
-These are all answerable.
+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 sub-agent.
-The answer just does not live on disk. It lives in your head, or in a wiki page, or in the import graph you have to trace by hand.
+That is not just aesthetic. It changes how the codebase behaves.
-That is the gap. The codebase does not know its own structure.
+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.
-## File-system routes as the answer
+The structure is doing work.
-The fix is borrowed wholesale from web frameworks.
+## Routes
-A route is a folder. The folder path is the endpoint. Everything that belongs to the route lives next to it.
+A Dawn route is a folder under `src/app/`. The folder path becomes the route id.
-A real Dawn project tree:
+Route groups are ignored:
```text
-my-agents/
-├── dawn.config.ts
-├── package.json
-└── src/
- └── app/
- ├── support/
- │ ├── [tenant]/
- │ │ ├── index.ts
- │ │ ├── state.ts
- │ │ ├── middleware.ts
- │ │ └── tools/
- │ │ ├── lookupOrder.ts
- │ │ └── escalate.ts
- │ └── internal/
- │ └── index.ts
- └── triage/
- ├── index.ts
- └── state.ts
+src/app/(public)/hello/[tenant]/index.ts
```
-Read that tree out loud and you have read the architecture.
-
-`support/[tenant]/` is a parameterized route. It has its own state shape, its own auth middleware, and two tools the agent can call. `support/internal/` is a separate route — a different endpoint, a different graph. `triage/` is unrelated to either.
-
-There is no registry. There is no `tools: [...]` array.
-
-The connection between a tool and the route that uses it is the *folder it is in*.
-
-That is the whole trick.
-
-## Co-location is a feature, not aesthetics
-
-Putting files near the thing they belong to is not a style preference.
+becomes:
-It changes what your tools can do.
-
-**Refactoring works.** Move a folder, move the route. Delete a folder, delete the route. There is no central registry whose edits you will forget.
-
-**Search works.** "Find references" on a tool finds the route that uses it, because the route imports it directly. "Find references" on a state field finds the agent prompt that mentions it.
-
-**Tests do not drift.** A scenario test for `support/[tenant]/` lives in `support/[tenant]/run.test.ts`. When you change the route, the test is right there in the same diff.
+```text
+/hello/[tenant]
+```
-**Onboarding works.** New engineers can open the tree and read the surface area without a Loom video. The structure is the documentation.
+The route entry is `index.ts`. It exports exactly one route shape:
-Frameworks that hide structure behind a builder API give all of this up.
+- `agent` for an LLM-driven route
+- `workflow` for a deterministic async function
+- `graph` for a raw LangGraph graph
+- `chain` for a LangChain runnable
-Once your routes are constructed at runtime from a `.register()` call, your editor cannot tell you what your codebase looks like. Only the running process can.
+The default scaffold uses an agent:
-That is a real cost. It just shows up late.
+```ts
+import { agent } from "@dawn-ai/sdk"
-## Types as the route boundary
+export default agent({
+ model: "gpt-4o-mini",
+ systemPrompt: "You are a helpful assistant for the {tenant} organization.",
+})
+```
-The other half of the coordinate system is types.
+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.
-A route is a contract. The contract should be checked at the boundary.
+## Tools
-In Dawn, a tool is just a TypeScript function:
+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: { signal: AbortSignal },
+ ctx: { readonly signal: AbortSignal },
) => {
const res = await fetch(`https://api.example.com/orders/${input.orderId}`, {
signal: ctx.signal,
@@ -132,38 +125,129 @@ export default async (
}
```
-There is no Zod schema. There is no `tool()` wrapper.
+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.
-The parameter type *is* the schema. `dawn typegen` reads it at build time and emits both a JSON schema for the LLM and a typed `ctx.tools.lookupOrder` for any code that calls it directly.
+## State and tests
-This sounds like a small convenience.
+Routes can also define `state.ts`.
-It is actually a forcing function.
+State is the JSON shape that flows through the route runtime. The scaffold uses Zod, but Dawn accepts Zod or any Standard Schema value.
-Because the type is the contract, drift is impossible. If you change the tool signature, callers fail to typecheck. If you misspell a tool name in a workflow route, the editor underlines it.
+```ts
+// src/app/support/[tenant]/state.ts
+import { z } from "zod"
-The route boundary is checked the same way the rest of your TypeScript is checked.
+export default z.object({
+ tenant: z.string(),
+ orderId: z.string().optional(),
+})
+```
-Generated types live in `.dawn/dawn.generated.d.ts`. The starter template ignores `.dawn/`, so regenerate it during development and CI unless your project explicitly chooses to commit generated artifacts.
+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.
-## What this unlocks at scale
+Tests live next to the route too:
-The interesting thing is not the first agent.
+```text
+src/app/support/[tenant]/run.test.ts
+```
-It is the tenth.
+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.
-A codebase with ten agents, forty tools, and a dozen middleware files needs the same code-intelligence features a web app does. Go-to-definition that works. Refactors that do not break things in production. Tests that run on a tight loop. Deploys that do not require a memo.
+## Capabilities
-Those features do not show up by accident.
+The route tree gives Dawn a place to add higher-level agent behavior.
-They require the codebase to have a *shape* the tools can reason about.
+Here is what we have built to date.
-That is the whole pitch.
+### 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
+```
-Dawn is not asking you to learn a new graph runtime. The graph runtime is fine. Dawn is the coordinate system around it — the answer to "where does this code go" — and the type machinery that makes the answer load-bearing.
+The useful part is that planning is not just a prompt. It is prompt, tool, state, and stream behavior composed together.
-If the mental model resonates, [`/docs/mental-model`](/docs/mental-model) is the one-page version, and [`/docs/routes`](/docs/routes) is where the conventions are spelled out in full.
+### Skills
+
+If a route has `skills//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.
+
+### Sub-agents
+
+A route can expose sub-agents 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
+- capabilities 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]'
+```
-A runtime is becoming a routing problem. A routing problem is becoming a type problem.
+For the deeper read, start with [Mental Model](/docs/mental-model), [Routes](/docs/routes), and [Capabilities](/docs/capabilities).
-That is what makes this interesting.
diff --git a/apps/web/content/docs/agents.mdx b/apps/web/content/docs/agents.mdx
index 8e3ef544..3fec7148 100644
--- a/apps/web/content/docs/agents.mdx
+++ b/apps/web/content/docs/agents.mdx
@@ -65,6 +65,12 @@ export default agent({
See [Retry](/docs/retry) for the backoff strategy, what is retried, and the streaming caveat. Dawn does not expose per-tool retry overrides today.
+## Capabilities
+
+Agent routes can opt into higher-level behavior from files and descriptor fields: memory from `workspace/AGENTS.md`, planning from `plan.md`, skills from `skills//SKILL.md`, sub-agents from `subagents/` or `agent({ subagents })`, and model reasoning effort from `reasoning`.
+
+See [Capabilities](/docs/capabilities) for the file conventions and runtime behavior.
+
## Streaming
The local dev server exposes streaming via `/runs/stream`. See [Dev Server](/docs/dev-server) for the protocol details.
@@ -75,5 +81,6 @@ The local dev server exposes streaming via `/runs/stream`. See [Dev Server](/doc
{ href: "/docs/routes", title: "Routes", subtitle: "pathname rules and route entry shapes" },
{ href: "/docs/tools", title: "Tools", subtitle: "tool input/output rules, type inference" },
{ href: "/docs/state", title: "State", subtitle: "when an agent reads or returns structured state" },
+ { href: "/docs/capabilities", title: "Capabilities", subtitle: "memory, planning, skills, and sub-agents" },
{ href: "/docs/retry", title: "Retry", subtitle: "retry config and backoff" },
]} />
diff --git a/apps/web/content/docs/capabilities.mdx b/apps/web/content/docs/capabilities.mdx
new file mode 100644
index 00000000..1bf46d53
--- /dev/null
+++ b/apps/web/content/docs/capabilities.mdx
@@ -0,0 +1,132 @@
+# Capabilities
+
+Capabilities are Dawn's way to add agent behavior from files and descriptors.
+
+The route still owns the boundary. Capabilities add tools, prompt fragments, state channels, and stream events around that route.
+
+Use them when the behavior is part of how the agent works, not just a one-off tool call.
+
+## What ships today
+
+Dawn currently includes four built-in capabilities:
+
+- **Memory** — load `workspace/AGENTS.md` into the agent prompt.
+- **Planning** — seed and maintain a route plan with `plan.md`.
+- **Skills** — expose route-local `SKILL.md` instructions through `readSkill`.
+- **Sub-agents** — let a parent agent delegate through `task({ subagent, input })`.
+
+Agent routes also support `reasoning: { effort }` on the descriptor. That is not a capability marker, but it fits the same goal: route-local agent behavior that stays visible in code.
+
+## Memory
+
+If `workspace/AGENTS.md` exists, Dawn injects its contents into the agent prompt under a `# Memory` heading on every model turn.
+
+The file is the memory. Dawn re-reads it each turn, so an agent that updates the file sees the updated memory on the next turn.
+
+```text
+workspace/
+ AGENTS.md
+```
+
+Keep this file concise. Dawn skips empty files and files larger than 64 KiB.
+
+## Planning
+
+Place a `plan.md` file in a route directory to opt into planning.
+
+```text
+src/app/support/[tenant]/
+ index.ts
+ plan.md
+```
+
+The seed file uses markdown checklist syntax:
+
+```md
+- [ ] Understand the request
+- [ ] Check account context
+- [ ] Draft the response
+```
+
+Dawn adds:
+
+- a `writeTodos` tool
+- a `todos` state channel
+- a planning prompt fragment
+- a `plan_update` stream event when the plan changes
+
+`writeTodos` is full-replace, not incremental. The agent passes the entire todo list each time.
+
+## Skills
+
+Place skills under a route's `skills/` directory.
+
+```text
+src/app/support/[tenant]/
+ skills/
+ refund-policy/
+ SKILL.md
+```
+
+Each `SKILL.md` needs frontmatter with a `description` field:
+
+```md
+---
+description: Instructions for handling refund-policy questions.
+---
+
+Use this skill when the user asks about refunds, exchanges, or exceptions.
+```
+
+Dawn adds a `# Skills` prompt section listing the available skills and a `readSkill({ name })` tool. The body of the skill is loaded only when the agent asks for it.
+
+The skill name defaults to the directory name. You can override it with a `name` field in frontmatter.
+
+## Sub-agents
+
+Sub-agents give a parent agent a visible delegation boundary.
+
+The convention path is:
+
+```text
+src/app/support/[tenant]/
+ index.ts
+ subagents/
+ research/
+ index.ts
+ tools/
+ searchDocs.ts
+```
+
+The parent route gets a `# Subagents` prompt section and a `task({ subagent, input })` tool. The `subagent` value is the child folder name, such as `"research"`.
+
+You can also expose child descriptors through `agent({ subagents: [...] })` when you need to wire a specialist that does not live under the `subagents/` convention.
+
+Each sub-agent can have its own prompt, tools, state, and capabilities. Dawn guards nested dispatch with a maximum depth of 3.
+
+## Reasoning effort
+
+OpenAI-backed agent routes can request a reasoning effort:
+
+```ts title="src/app/support/[tenant]/index.ts"
+import { agent } from "@dawn-ai/sdk"
+
+export default agent({
+ model: "gpt-5.1",
+ reasoning: { effort: "high" },
+ systemPrompt: "You are a careful support assistant.",
+})
+```
+
+Non-reasoning models ignore the value. Use it when a specific route needs more reasoning budget for tool-heavy or planning-heavy work.
+
+## Related
+
+
+
diff --git a/apps/web/content/docs/mental-model.mdx b/apps/web/content/docs/mental-model.mdx
index 94b7aabe..cd7cea60 100644
--- a/apps/web/content/docs/mental-model.mdx
+++ b/apps/web/content/docs/mental-model.mdx
@@ -15,6 +15,7 @@ Dawn writes those conventions once. You write the agent.
- A route's `index.ts` exports one of: `agent`, `workflow`, `graph`, `chain`.
- *Tools* live next to routes. Their parameter types are inferred at build time.
- *State* is the JSON payload passed through the route runtime.
+- *Capabilities* add route-level agent behavior such as memory, planning, skills, and sub-agents.
- *Middleware* is an optional app-level request gate for the local Dawn runtime.
- LangGraph runs the graph. Dawn does everything else.
@@ -28,6 +29,8 @@ A *route entry* is the route's default behavior. Each route has exactly one `ind
*State* is an optional typed shape declared in `state.ts` next to the route. Dynamic segments — `[tenant]`, `[...rest]` — are preserved in the route id and generated route metadata; callers pass the corresponding values in the JSON input they send to the runtime.
+*Capabilities* are opt-in behavior discovered from files and descriptors. `workspace/AGENTS.md` provides memory, `plan.md` provides planning, `skills//SKILL.md` provides route-local skills, and `subagents/` provides child specialists.
+
*Middleware* runs before route execution in the local Dawn runtime. It lives at `src/middleware.ts` (or root `middleware.ts`) and can reject a request or attach request-scoped context for tools.
## The runtime
@@ -94,7 +97,7 @@ You bring your own LangGraph workflows, your own LCEL chains, your own model pro
Pick the path that fits where you are.
1. **I want to build something now.** → [Getting Started](/docs/getting-started)
-2. **I want to understand a piece in depth.** → [Routes](/docs/routes), [Agents](/docs/agents), [Tools](/docs/tools), [State](/docs/state), [Middleware](/docs/middleware), [Retry](/docs/retry)
+2. **I want to understand a piece in depth.** → [Routes](/docs/routes), [Agents](/docs/agents), [Tools](/docs/tools), [State](/docs/state), [Capabilities](/docs/capabilities), [Middleware](/docs/middleware), [Retry](/docs/retry)
3. **I want to see the surface.** → [CLI](/docs/cli), [Dev Server](/docs/dev-server), [Deployment](/docs/deployment)
## Related
@@ -103,6 +106,7 @@ Pick the path that fits where you are.
{ href: "/docs/routes", title: "Routes", subtitle: "the folder-as-route convention" },
{ href: "/docs/agents", title: "Agents", subtitle: "the default route entry" },
{ href: "/docs/tools", title: "Tools", subtitle: "co-located tools and type inference" },
+ { href: "/docs/capabilities", title: "Capabilities", subtitle: "memory, planning, skills, and sub-agents" },
{ href: "/docs/dev-server", title: "Dev Server", subtitle: "the runtime protocol" },
{ href: "/docs/recipes", title: "Recipes", subtitle: "task-oriented how-tos" },
{ href: "/docs/faq", title: "FAQ", subtitle: "one-line answers to the recurring questions" },
diff --git a/apps/web/content/docs/recipes/index.mdx b/apps/web/content/docs/recipes/index.mdx
index 903ab59c..4554168f 100644
--- a/apps/web/content/docs/recipes/index.mdx
+++ b/apps/web/content/docs/recipes/index.mdx
@@ -4,6 +4,8 @@ Recipes answer the question: *how do I do X?* Each one is a single sitting — a
Concept pages explain the pieces. Recipes show the pieces wired up.
+For memory, planning, skills, and sub-agents, start with [Capabilities](/docs/capabilities). Those features are route-level building blocks rather than single recipes today.
+
## Available recipes
- [Add a tool](/docs/recipes/add-a-tool) — author a tool, get type-safe access from a route
@@ -17,5 +19,6 @@ Concept pages explain the pieces. Recipes show the pieces wired up.