Creating Templates

How to build and publish your own agent-native app template.

Overview

Templates are complete, forkable agent-native apps that solve a specific use case. The analytics, content, slides, and video templates that ship with Agent-Native are all built this way. Anyone can create a template and share it with the community.

A good template:

  • Solves a real workflow end-to-end (not a toy demo)
  • Works out of the box with example data
  • Has a comprehensive AGENTS.md so the AI agent understands the architecture
  • Includes scripts for key operations the agent can call
  • Follows the five rules: files as database, all AI through agent chat, scripts for operations, SSE sync, agent can modify code

Start from the starter

The fastest way to start is with the built-in starter template:

npx @agent-native/core create my-template

This scaffolds a minimal agent-native app with the standard directory structure, a working dev server, file watching, SSE, and an example script. Build your template on top of this.

Project structure

Every template follows the same convention:

my-template/
  client/             # React frontend (Vite SPA)
    App.tsx           # Entry point — routes, providers, file watcher
    pages/            # Route components
    components/       # UI components
    components/ui/    # Reusable primitives (shadcn/ui)
    hooks/            # React hooks
    lib/utils.ts      # cn() utility

  server/             # Express backend
    index.ts          # createAppServer() — routes + middleware
    node-build.ts     # Production entry point
    routes/           # API route handlers

  shared/             # Isomorphic types (imported by client & server)
    api.ts            # Shared interfaces

  scripts/            # Agent-callable scripts
    run.ts            # Script dispatcher (don't modify)
    *.ts              # Your scripts — one per operation

  data/               # File-based state (watched by SSE)
    .gitkeep          # Or seed data for the template

  .agents/skills/     # Agent skills — detailed guidance per topic

  AGENTS.md           # Master agent instructions
  package.json        # Scripts: dev, build, start, script, typecheck
  vite.config.ts      # Client Vite config
  vite.config.server.ts  # Server Vite config
  tsconfig.json       # TypeScript config

Build your client

The client is a standard React SPA. Use React Router for navigation, React Query for data fetching, and TailwindCSS + shadcn/ui for styling.

// client/App.tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useFileWatcher } from "@agent-native/core";
import { BrowserRouter, Routes, Route } from "react-router-dom";

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <FileWatcher />
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </BrowserRouter>
    </QueryClientProvider>
  );
}

function FileWatcher() {
  useFileWatcher({ queryClient, queryKeys: ["items", "projects"] });
  return null;
}

The useFileWatcher hook connects to /api/events and invalidates react-query caches when files change. This is how the UI stays in sync when the agent modifies data.

Add API routes

API routes serve data from files and handle mutations. They go in server/index.tsor a server/routes/ directory for larger apps:

// server/index.ts
import { createServer, createFileWatcher, createSSEHandler } from "@agent-native/core";
import { readdir, readFile, writeFile, mkdir } from "node:fs/promises";
import path from "node:path";

export function createAppServer() {
  const app = createServer();
  const watcher = createFileWatcher("./data");

  // List items from files
  app.get("/api/items", async (_req, res) => {
    const dir = "./data/items";
    await mkdir(dir, { recursive: true });
    const files = await readdir(dir);
    const items = await Promise.all(
      files.filter(f => f.endsWith(".json")).map(async f => {
        const content = await readFile(path.join(dir, f), "utf-8");
        return JSON.parse(content);
      })
    );
    res.json(items);
  });

  // Create an item (write a file)
  app.post("/api/items", async (req, res) => {
    const item = { id: crypto.randomUUID(), ...req.body, createdAt: new Date().toISOString() };
    await mkdir("./data/items", { recursive: true });
    await writeFile(`./data/items/${item.id}.json`, JSON.stringify(item, null, 2));
    res.json(item);
  });

  // SSE events (keep last)
  app.get("/api/events", createSSEHandler(watcher));
  return app;
}

Both the UI and the agent can create items — the UI via POST /api/items, the agent by writing directly to data/items/. The SSE watcher ensures both paths trigger UI updates.

Add scripts

Scripts are the agent's toolbox. Each script handles one operation — fetching data from an API, generating content, processing files, etc:

// scripts/import-data.ts
import { parseArgs } from "@agent-native/core";
import { writeFile, mkdir } from "node:fs/promises";

export default async function importData(args: string[]) {
  const { url, name } = parseArgs(args);
  if (!url) { console.error("--url is required"); process.exit(1); }

  const res = await fetch(url);
  const data = await res.json();

  const slug = name ?? "imported";
  await mkdir("./data/imports", { recursive: true });
  await writeFile(`./data/imports/${slug}.json`, JSON.stringify(data, null, 2));
  console.log(`Imported ${Array.isArray(data) ? data.length + " records" : "data"} to data/imports/${slug}.json`);
}
# The agent can run this
pnpm script import-data --url https://api.example.com/data --name users

Scripts should write their output to data/ — the SSE watcher will notify the UI. Use console.log for output the agent can see. Use console.error and process.exit(1) for errors.

Add data models

Seed your template with example data so it works immediately. Put JSON files in data/ matching the structure your API routes expect:

data/
  items/
    example-1.json     # {"id": "example-1", "title": "...", "status": "active"}
    example-2.json
  config.json          # App-level config
  sync-config.json     # (optional) Firestore sync glob patterns

Keep your data models simple — flat JSON files, one per entity. The agent can grep, read, and modify them. Deeply nested structures or binary formats make it harder for the agent to work with the data.

Write AGENTS.md

This is the most important file in your template. AGENTS.md tells the AI agent how your app works, what it can and can't do, and how to make changes:

# My Template — Agent-Native App

## Architecture

This is an **@agent-native/core** application.

### Core Principles

1. **Files as database** — All state in `data/`. No traditional DB.
2. **All AI through agent chat** — No inline LLM calls.
3. **Scripts for operations** — `pnpm script <name>` for complex work.
4. **SSE sync** — File watcher keeps UI in sync.
5. **Agent can update code** — Edit components, routes, scripts.

### Directory Structure

\`\`\`
client/          # React SPA
server/          # Express API
scripts/         # Agent-callable scripts
data/            # File-based state
\`\`\`

### Available Scripts

- `pnpm script import-data --url <url>` — Import data from API
- `pnpm script generate-report --id <id>` — Generate a report

### Data Model

Items are stored as `data/items/<id>.json`:
\`\`\`json
{ "id": "...", "title": "...", "status": "active" }
\`\`\`

### Key Patterns

- API routes in `server/routes/` serve files from `data/`
- UI delegates AI work via `sendToAgentChat()`
- Scripts write results to `data/` — SSE updates the UI

Be specific about your data models, available scripts, and key patterns. The better your AGENTS.md, the better the agent will work with your template.

Add skills

For complex topics that don't fit in AGENTS.md, create skills in .agents/skills/. Each skill is a Markdown file with detailed guidance for a specific topic:

# .agents/skills/bigquery/SKILL.md

## BigQuery Integration

### Column Reference
- `event_name` — The event type (string)
- `event_timestamp` — Microsecond timestamp (int64)
- `user_pseudo_id` — Anonymous user ID (string)

### Common Queries
...

### Gotchas
- Always use `event_date` partition filter to avoid full table scans
- Timestamps are in microseconds, not milliseconds

Skills let you give the agent deep domain knowledge for specific integrations or patterns without bloating your main AGENTS.md.

Onboarding & API keys

If your template needs API keys or external service configuration, document them in a .env.example file:

# .env.example
BIGQUERY_PROJECT_ID=your-project-id
STRIPE_SECRET_KEY=sk_live_...
OPENAI_API_KEY=sk-...

When users fork your template, they copy .env.example to .envand fill in their own values. Keep the number of required keys minimal — the template should work with example data before any keys are configured.

Publishing

To share your template:

  1. Push your template to a public GitHub repo
  2. Make sure it works with pnpm install && pnpm dev
  3. Include seed data in data/ so it works without API keys
  4. Write a clear README explaining what the template does and how to configure it

Community templates can be shared via GitHub. The agent-native CLI supports creating from any git repo:

npx @agent-native/core create my-app --template github:user/repo