A project is a folder.
Hatchable is an agent-first framework for full-stack web apps. The entire contract is the file tree: paths become routes, SQL files become schema, one config file declares everything else. No build step, no boilerplate, nothing hidden.
The shape of a project
Every directory has one job. The path of a file decides what the platform does with it:
Orange: HTTP routes. Purple: agent surface. Grey: static files and shared code.
That is the whole framework. There is no router file, no controller registry, no dependency injection to configure. If a file exists at the right path with the right shape, it is live after the next deploy.
A complete app in four files
This is a working todo app. Not a fragment: the entire thing.
migrations/0001_create_todos.sql CREATE TABLE todos ( id BIGSERIAL PRIMARY KEY, title TEXT NOT NULL, done BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT now() );
api/todos.js import { db } from 'hatchable'; export const access = 'public'; export const methods = ['GET', 'POST']; export default async function (req, res) { if (req.method === 'POST') { const { rows } = await db.query( 'INSERT INTO todos (title) VALUES ($1) RETURNING *', [req.body.title] ); return res.json(rows[0]); } const { rows } = await db.query('SELECT * FROM todos ORDER BY id DESC'); res.json(rows); }
public/index.html <!doctype html> <html><body> <h1>Todos</h1> <form id="f"><input name="title" required><button>Add</button></form> <ul id="list"></ul> <script> const load = async () => { const todos = await (await fetch('/api/todos')).json(); list.innerHTML = todos.map(t => `<li>${t.title}</li>`).join(''); }; f.onsubmit = async (e) => { e.preventDefault(); await fetch('/api/todos', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ title: f.title.value }) }); f.reset(); load(); }; load(); </script> </body></html>
hatchable.toml (optional for this app; here for completeness) # nothing required yet. auth, cron, secrets and buyer-editable # config are declared here when you need them.
Zip the folder, or push it to a public GitHub repository, and import it. The platform creates the database, applies the migration, registers the route, and serves the page. You get a URL, hosting, TLS, and a per-project PostgreSQL database without writing a line of infrastructure.
[auth] enabled = true to hatchable.toml and the platform owns identity end to end; your handlers just read who is calling. See identity in the SDK reference.The file contract
The reference below is served to every connected agent as part of the project manifest, and rendered here from the same source. If it says something, both you and your agent are reading the identical rule.
Why agent-first changes the anatomy
Three parts of the tree exist because agents are first-class users of the framework, not an add-on:
mcp/*.jsmakes your app itself an agent surface. Each file declares one tool, and the platform serves them from your app's own MCP endpoint. Your users' agents can operate the app you built, with per-tool access levels. See project MCP tools.- Every project publishes a live manifest. An agent connected as the owner or an admin collaborator gets the project's current tables, routes, schedules and the names of its configured env variables, stitched fresh on every read. Values are never included: keys stay server-side and resolve through the SDK. A new chat session starts already knowing the shape of the project without being handed a single secret.
- The conventions are machine-checkable. Because routing, access and config are declarative, the deploy validator can verify the whole contract before anything ships, and tell an agent exactly what to fix. Humans get the same errors in the console's guided flow.
None of this stops you from hand-writing every file. The framework reads the same either way; that is the point.