File-Based Agent Routing
Discover agents from the filesystem and serve them automatically with zero route boilerplate.
Define agents as folders on the filesystem. The framework scans them at build time, generates route handlers, and serves each agent at /.well-known/agents/<name>.
Setup
Wrap your Next.js config with withAgents:
import { withAgents } from "experimental-agent/next/agents";
export default withAgents({
// your normal Next.js config
reactStrictMode: true,
});If you're using durable workflows, compose with withWorkflow:
import { withAgents } from "experimental-agent/next/agents";
import { withWorkflow } from "workflow/next";
export default withAgents(withWorkflow({
reactStrictMode: true,
}));That's it. Create agent folders and they're served automatically.
Directory Structure
src/agents/
v0/
agent.ts # agent config (optional)
system/
1-instructions.md # system prompt parts
2-constraints.md
skills/
coding.md # host skillsEach top-level folder inside the agents directory becomes a named agent. The folder name is the agent name.
agent.ts
Optional. When present, must export default an agent instance:
import { agent } from "experimental-agent";
import { vercelSandbox } from "experimental-agent/sandbox";
export default agent("v0", {
model: "anthropic/claude-sonnet-4-6",
system: "You are a coding assistant.",
sandbox: vercelSandbox(),
skills: [],
});When agent.ts is absent, the framework creates an agent from the filesystem config alone (system prompts and skills).
system/*.md
Markdown files in the system/ subdirectory form the agent's system prompt. Files are sorted by name and concatenated with double newlines, so use numeric prefixes to control order:
system/
1-role.md # "You are a coding assistant."
2-constraints.md # "Never delete files without asking."
3-style.md # "Be concise. Use code blocks."Result: "You are a coding assistant.\n\nNever delete files without asking.\n\nBe concise. Use code blocks."
Empty files and non-.md files are ignored.
skills/*.md
Each .md file in the skills/ subdirectory becomes a host skill. The file's absolute path is passed to the agent at runtime:
skills/
coding.md # skill instructions for coding tasks
review.md # skill instructions for code reviewConfig-Only Agents
Agents don't need any code. A folder with just system/ and/or skills/ files creates a working agent:
agents/
helper/
system/
1-instructions.md
skills/
writing.mdThis creates an agent named "helper" that's served at /.well-known/agents/helper.
Merging
When an agent has both an agent.ts and filesystem config, the framework merges them. The agent.ts config is the base and filesystem config is layered on top:
| Config | Merge behavior |
|---|---|
| System prompt | Filesystem prompts are appended after the agent.ts prompt |
| Skills | Filesystem skills are appended after the agent.ts skills |
| Everything else | model, tools, generation, etc. from agent.ts are used as-is |
This works correctly with resolvable options. If your agent.ts system prompt is a function, the filesystem prompt is appended to the resolved value at runtime.
import { agent } from "experimental-agent";
export default agent("v0", {
system: ({ context }) => `You work on project ${context.projectName}.`,
});Always ask before deleting files.At runtime, the system prompt resolves to:
["You work on project acme.", "Always ask before deleting files."]Auto-Detection
The framework auto-detects your project layout:
| What | Detection order | Override |
|---|---|---|
| Agents directory | src/agents/ → agents/ | agentsDir option |
| App directory | src/app/ (if it has layout.tsx) → app/ | — |
import { withAgents } from "experimental-agent/next/agents";
export default withAgents({}, {
agentsDir: "custom/path/to/agents",
});Generated Files
withAgents writes two files into .well-known/agents/[agent]/[[...path]]/ inside your app directory:
| File | Purpose |
|---|---|
_registry.ts | Maps agent names to their config and dynamic import() calls |
route.ts | Next.js route handler that resolves agents and delegates to handleRequest |
These files are auto-generated on every Next.js startup and gitignored (a .gitignore is created automatically). Don't edit them.
Routing
Each agent is served at /.well-known/agents/<name> with the same API as handleRequest:
POST /.well-known/agents/v0/:sessionId # send a message
GET /.well-known/agents/v0/:sessionId # get session / reconnect stream
GET /.well-known/agents/v0/openapi.json # OpenAPI specRequesting an unknown agent name returns a 404 with a list of available agents.
Debug Mode
Pass debug: true to log discovered agents at startup:
import { withAgents } from "experimental-agent/next/agents";
export default withAgents({}, { debug: true });[withAgents] discovered agents: [ 'v0', 'helper' ]Multiple Agents
Add more folders to serve more agents:
src/agents/
v0/
agent.ts
system/1-instructions.md
code-review/
system/1-instructions.md
skills/review.md
docs-writer/
agent.tsEach gets its own endpoint:
/.well-known/agents/v0/.well-known/agents/code-review/.well-known/agents/docs-writer
Ignored Entries
- Dot-prefixed folders (
.shared/,.utils/) are ignored - Files in the agents root directory are ignored (only folders become agents)
- Non-
.mdfiles insystem/are ignored