File-based routing
Folder = agent. Nested folders = nested URLs. Route groups share middleware without leaking into the URL.
Agent-first framework for Cloudflare. File-based routing,
auto-generated wrangler config, typed inter-agent RPC, co-located
React UIs, and MCP support — so you can ship Durable Object agents
without ever opening wrangler.jsonc.
One command scaffolds a project with a working agent. A second command starts a local Cloudflare worker you can hit from curl or the Agents client SDK.
The shape you draft in agents/
maps 1:1 to URLs, DO bindings, migrations, and the worker
entrypoint. There is no config file to author — just files and
folders.
next().
useAgent() bound to this folder.
Six tools-in-one: routing, migrations, RPC, UI, MCP, dev loop. None of the glue. click any card for a visual deep-dive
Folder = agent. Nested folders = nested URLs. Route groups share middleware without leaking into the URL.
.ayjnt/migrations.json is committed. Deploy refuses to run from an out-of-sync tree.
getAgent<T>(env.BINDING, id). Native Workers RPC, method autocomplete, exceptions cross the boundary.
Drop an app.tsx next to agent.ts. Generated useAgent() hook syncs state across every tab.
Classes extending McpAgent dispatch through McpAgent.serve() automatically. Streamable HTTP + SSE handled.
ayjnt dev watches agents/, re-runs codegen on structural change, lets wrangler reload. 26ms rebuilds.
getAgent<T> returns a
typed DurableObject stub. Method autocomplete from the target
class. Errors propagate across the RPC boundary as exceptions.
Rename a method and both sides break at compile time.
// agents/orders/agent.ts
import { Agent } from "agents";
import { getAgent } from "ayjnt/rpc";
import type InventoryAgent from "../inventory/agent.ts";
export default class OrdersAgent extends Agent<Env, State> {
override async onRequest(request: Request): Promise<Response> {
const { sku, qty } = await request.json();
const inventory = await getAgent<InventoryAgent>(
this.env.INVENTORY_AGENT,
"main",
);
const remaining = await inventory.decrement(sku, qty);
return Response.json({ ok: true, remaining });
}
}
Every build is a pure function of your file tree. The pipeline is
small and inspectable — peek at .ayjnt/dist/entry.ts any time.
Every step is a pure function of its inputs. I/O lives at the edges (scan, readLockfile, writeLockfile). Everything in between is tested as pure data transforms — see src/codegen/.
Every example in the gallery has a scaffold command, the code you'd add to which file, an animated terminal walkthrough, and the deploy step.
One command scaffolds the project. One command ships it.