Skip to content

~6 min readgrounded in apps/worker/src/{upsert,get,list,retrieve,delete}.ts · apps/worker/migrations/0003_tiny_v2.sql · apps/web/app/[slug]/page.tsx · apps/web/app/api/{tiny,control,chat,delete}/route.ts

What a tiny is·

A tiny is a named AI that lives at a URL on your deployment. https://<your-app>/luna renders it, POST /api/chat with x-tiny-name: luna talks to it, and the rest of the universe can find it by meaning. Underneath, it is one row and one JSON document:

  • D1 tinys — name (the slug, primary key), user_id, system_prompt, system_knowledge, private, active, created, updated. This table is the only source of truth for existence and ownership.
  • KV tiny keyed by the same slug — the full document: everything below, including skills and branding.

name is slugified strictly (slugify(lower, strict)): letters, digits and hyphens; a name that slugifies to nothing is refused, so nothing can ever be created at /. Private tinys are also removed from the vector index, so they cannot surface in universe search.

The record·

Fields a tiny can carry, as POST /upsert accepts them (apps/worker/src/upsert.ts). The agent's create_ai / modify_ai tools and the edit UI all end up here.

Field What it is
name the slug and the URL
systemPrompt who the tiny is — becomes the head of the system prompt on every turn
systemKnowledge what the tiny knows — appended after the prompt
data free-form data the tiny may consult
worker + schema + skills an OpenAPI URL; the worker fetches the schema and parses operations into skills, which are mounted as tools whenever this tiny is chatted with or retrieved into another tiny's turn
mcpServers { "<name>": { url, headers?, disabled? } } — MCP servers mounted at chat time; headers are owner secrets
hook a webhook URL that receives the tiny's messages
private hidden from list, search and other users; only the owner (or a caller with the legacy key) may chat
hero, logo, theme https media URLs and {accent, bg} hex colours for the landing page and browser chrome
tagline, chips landing subtitle (≤ 200 chars) and 1–4 starter suggestions (1–60 chars each)
intro_vibe haptic pattern played when the tiny opens on a phone — one of tap double success warning error heartbeat sos long escalate wave
voice the realtime voice for speech sessions — alloy ash ballad coral echo sage shimmer verse marin cedar, default marin

Updates are partial-safe: every optional field is preserved when the body omits it (undefined = keep), '' clears explicitly, and an invalid value preserves the existing one rather than silently truncating. That is what lets modify_ai refine just the prompt without wiping a tiny's skills or theme.

Who may read what·

POST /api/tiny {name, key?} in the app and GET /get?name= in the worker return the same document with three levels of exposure:

  • Visitor, public tiny — everything, including systemPrompt and systemKnowledge. Public tinys are open by design: the prompt is the product. Two things are masked: hook becomes "[configured]" (webhook URLs often embed tokens) and mcpServers[*].headers becomes "[REDACTED]".
  • Visitor, private tiny — only the shell: name, private: true, the branding (hero, logo, theme, tagline, chips — cosmetic, not secret), the vCard and QR URLs. Prompt, knowledge, data, worker and skills come back empty. The landing page renders a branded lock.
  • Owner (session sub = user_id, forwarded by the app with the internal key) — everything, unmasked. isAuthorized: true in the response tells the UI to show the edit surfaces.

The endpoint that accepts a private-tiny key is rate-limited at the base per-IP allowance with no widening for signed-in callers — the window is the brute-force budget against someone else's key. See Identity.

Creating, changing, deleting·

Action Surface Rule
Create create_ai tool, or the edit UI via POST /api/control → worker POST /upsert login required; a name with no tinys row is free to claim, and the insert is ON CONFLICT DO NOTHING so two racing creators cannot both win
Update modify_ai, customize_page, set_price, or /api/control → /upsert only if the caller's user id matches the row's owner; sets updated, re-embeds public tinys, deletes private ones from the index
Delete DELETE /api/delete → worker DELETE /tiny owner only, permanent — the row, the document and the vector

Pricing (set_price) puts a tiny behind x402 — see Payments. Page customisation (customize_page) writes the branding fields above.

The landing page·

/<slug> (apps/web/app/[slug]/page.tsx) is the tiny's homepage: hero, logo, tagline, starter chips, the chat, and a themed /og/<slug> card for link previews. The address bar tints to the tiny's theme.bg. /@<github_login> at the same route is a builder profile, not a tiny — public tinys plus forged tools for that user. Unknown slugs render the friendly not-found page, including on malformed percent-encoded scanner traffic.

The tiny's document is embedded in the page as JSON for the client, with < > & and U+2028/2029 escaped so a prompt containing </script> cannot break out of the tag. Prompt, knowledge and data are treated as attacker-authored text throughout the app — they steer the model, never the page's code.

How tinys find each other·

Public, active tinys are embedded (text-embedding-3-small) into a Vectorize index on every upsert. Three surfaces read it:

  • GET /list — the public directory, served from the tinys table (public + active only, never raw KV keys, which once exposed private names). The agent's list_tiny tool.
  • POST /retrieve — semantic search over public tinys, topK: 9, with a second filter against private after the vector query so a stale vector can never leak a tiny that went private. The agent's retrieve tool — and its results' skills are mounted as tools for the rest of the turn, so a tiny that finds a weather tiny can call its operations directly.
  • ask_tiny — a nested agent with the other tiny's prompt, knowledge and data answers a message. This is how tinys consult each other; the platform records a consulted social edge each time.

Every chat turn runs a universe search for the current query and puts the related tinys into the system prompt as "related tinys whose skills are already mounted as your tools" — the collective-intelligence part: a tiny is never alone. /universe renders the same directory for people. The agent loop lists the exact tools.