Generated files
Everything in .ayjnt/ is regenerated on every build. This page is the reference for what each file is, whether you should commit it, and what it's used for.
Commitment status
| File | Commit? | Why |
|---|---|---|
.ayjnt/migrations.json | Yes | Source of truth for what DO schema is in production. Append-only history. |
.ayjnt/tsconfig.json | No | Regenerated each build. Path aliases. |
.ayjnt/env.d.ts | No | Regenerated each build. Types the DO bindings. |
.ayjnt/client/* | No | Regenerated per-agent hook modules. |
.ayjnt/dist/entry.ts | No | Worker entrypoint. Fed to wrangler. |
.ayjnt/dist/wrangler.jsonc | No | Generated wrangler config. |
.ayjnt/assets/__ayjnt/* | No | Bundled UI HTML + JS per agent with app.tsx. |
The ayjnt new template's .gitignore
captures this with:
.ayjnt/*
!.ayjnt/migrations.json.ayjnt/migrations.json
Committed lockfile. Source of truth for what DO schema is in
production. Rewritten by ayjnt build; never written by
ayjnt deploy.
{
"version": 1,
"migrations": [
{
"tag": "v1",
"timestamp": "2026-04-14T00:00:00Z",
"new_sqlite_classes": ["ChatAgent"]
},
{
"tag": "v2",
"timestamp": "2026-04-14T05:30:00Z",
"renamed_classes": [{ "from": "ChatAgent", "to": "TalkAgent" }]
}
],
"classes": {
"chat": {
"agentId": "chat",
"className": "TalkAgent",
"firstTag": "v1"
}
}
}-
version— schema version of this file. Must be1. -
migrations— append-only list, consumed by wrangler as themigrationsarray inwrangler.jsonc. Never edit past entries. -
classes— derived state: what each agent currently is after all migrations. Keyed byagentId, so renames are detected across builds.
.ayjnt/dist/entry.ts
The worker's actual entrypoint. Wrangler bundles this when you deploy. You never edit it directly, but reading it is a great way to understand what ayjnt does. Typical shape:
// GENERATED by ayjnt — do not edit. Regenerated on every build.
import { getAgentByName } from "agents";
import { compose, createContext, type Middleware } from "ayjnt/middleware";
import mw_0 from "../../agents/middleware.ts";
import ChatAgent from "../../agents/chat/agent.ts";
import OrdersAgent from "../../agents/orders/agent.ts";
export { ChatAgent, OrdersAgent }; // required for DO class registration
type Binding = "CHAT_AGENT" | "ORDERS_AGENT";
type Env = Record<Binding, DurableObjectNamespace> & { ASSETS: Fetcher };
const ROUTES: Route[] = [
{ prefix: "/orders", binding: "ORDERS_AGENT", middleware: [mw_0], isMcp: false, assetFlat: null },
{ prefix: "/chat", binding: "CHAT_AGENT", middleware: [mw_0], isMcp: false, assetFlat: "chat" },
];
const CLASSES: Record<Binding, any> = { CHAT_AGENT: ChatAgent, ORDERS_AGENT: OrdersAgent };
const APPS: Partial<Record<Binding, string>> = { /* flat route -> app info */ };
function matchRoute(pathname: string): Match | null { /* longest-prefix match */ }
function isHtmlRequest(request: Request): boolean { /* GET + Accept: text/html + no Upgrade */ }
export default {
async fetch(request, env, executionCtx) {
const match = matchRoute(new URL(request.url).pathname);
if (!match) return new Response("Not found", { status: 404 });
const finalize = async () => {
if (match.isMcp) return /* McpAgent.serve dispatch */;
if (match.assetFlat && isHtmlRequest(request)) return /* HTML from Assets */;
const stub = await getAgentByName(env[match.binding], match.instanceId);
return stub.fetch(new Request(/* rewritten URL */, request));
};
if (!match.middleware.length) return finalize();
const c = createContext({ /* ... */ });
return compose(match.middleware, c, finalize);
},
};Key invariants:
-
Every agent class is explicitly
exported so wrangler can find it for DO registration. - Routes are sorted longest-prefix first.
-
DO dispatch goes through
getAgentByNameto ensuresetNameis called (see Client integration ). -
MCP agents get special dispatch via their static
.serve()method.
.ayjnt/dist/wrangler.jsonc
// GENERATED by ayjnt — do not edit. Regenerated on every `ayjnt build`.
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-app",
"main": "./entry.ts",
"compatibility_date": "2026-04-14",
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [
{ "name": "CHAT_AGENT", "class_name": "ChatAgent" },
{ "name": "ORDERS_AGENT", "class_name": "OrdersAgent" }
]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["ChatAgent", "OrdersAgent"] }
],
"assets": { // only if any agent has app.tsx
"directory": "../assets",
"binding": "ASSETS",
"not_found_handling": "none",
"html_handling": "none"
}
}Fields set by ayjnt:
name— derived from your package.json.main— always./entry.ts, relative to this wrangler config.compatibility_date— today's date by default.compatibility_flags—nodejs_compatis always included.durable_objects.bindings— one per agent in the manifest.migrations— copied verbatim from.ayjnt/migrations.json.assets— only present if at least one agent has a co-locatedapp.tsx. Thehtml_handling: "none"setting is load-bearing (see Co-located UI).
.ayjnt/tsconfig.json
// GENERATED by ayjnt — do not edit. Extended by your tsconfig.json.
{
"compilerOptions": {
"paths": {
"@ayjnt/env": ["./env.d.ts"],
"@ayjnt/*": ["./client/*"]
}
}
}
Your own tsconfig.json either "extends": "./.ayjnt/tsconfig.json"
(and you get the paths for free) or inlines the same mappings with
./ prefixes.
.ayjnt/env.d.ts
// GENERATED by ayjnt — do not edit.
import type ChatAgent from "../agents/chat/agent.ts";
import type OrdersAgent from "../agents/orders/agent.ts";
export type GeneratedEnv = {
CHAT_AGENT: DurableObjectNamespace<ChatAgent>;
ORDERS_AGENT: DurableObjectNamespace<OrdersAgent>;
};.ayjnt/client/<route>/index.tsx
One file per agent — even agents without an app.tsx
get one, since it's useful to import the typed hook from
external apps too. Shape (for agents/counter/):
// GENERATED by ayjnt — do not edit.
// Typed useAgent hook for CounterAgent at /counter.
import {
useAgent as useAgentUpstream,
type UseAgentOptions,
} from "agents/react";
import type CounterAgent from "../../../agents/counter/agent.ts";
export type { default as CounterAgent } from "../../../agents/counter/agent.ts";
type Instance = InstanceType<typeof CounterAgent>;
type DefaultState = Instance extends { state: infer S } ? S : unknown;
export function useAgent<State = DefaultState>(
options?: Omit<UseAgentOptions<State>, "agent" | "basePath"> & {
name?: string;
},
): ReturnType<typeof useAgentUpstream<State>> {
const { name: overrideName, ...rest } = options ?? {};
const instanceName = overrideName ?? deriveInstance();
return useAgentUpstream<State>({
agent: "CounterAgent",
basePath: "counter/" + instanceName,
...rest,
});
}
function deriveInstance(): string {
if (typeof window === "undefined") return "default";
const prefix = "/counter";
const p = window.location.pathname;
if (p !== prefix && !p.startsWith(prefix + "/")) return "default";
const remainder = p.slice(prefix.length);
const parts = remainder.split("/").filter(Boolean);
return parts[0] ?? "default";
}.ayjnt/assets/__ayjnt/<route>/
Only exists when an agent has a co-located app.tsx.
Per-agent bundle directory. Example for agents/counter/:
.ayjnt/assets/__ayjnt/counter/
├── index.html # HTML shell referencing app.js
└── app.js # Bun-built browser bundle of app.tsx
Cloudflare Assets binding serves these files. The __ayjnt prefix keeps them out of your route namespace
— you can never create an agent at /__ayjnt/<anything>.
Regeneration cost
ayjnt build runs in ~30ms for typical projects
(scan + lockfile diff + codegen). The slowest part is always Bun.build for app.tsx bundles — that
scales with the size of your dependencies. React + Agents SDK adds
about 150–450ms per agent with UI, so five UIs is a ~1.5 second
rebuild.
If you need faster incremental rebuilds, file an issue — sharing the bundle across agents is on the roadmap.
If you suspect stale generation (after rebases, branch switches,
debugging weird cache issues), rm -rf .ayjnt/dist
.ayjnt/client .ayjnt/tsconfig.json .ayjnt/env.d.ts .ayjnt/assets
is always safe. Only .ayjnt/migrations.json must
stay put — don't delete it.