MIDL agent reference
The capability map for an agent building with MIDL: what the system is, every surface you can use, and the validate → repair → render loop that lets you author correctly without memorizing the spec. For a hands-on first run, do AGENT-QUICKSTART.md first; this is the companion reference.
Core principle: don't guess the spec — introspect and round-trip. The vocabulary is machine-readable (
describeVocabulary()), and every render/validate call returns structured feedback that names the fix.
1. The model in one minute
- A MIDL document is plain JSON:
{ midl: '0.1', ns: { ui: 'https://midl.ai/ui#' }, frames: [...] }. - A frame is a component:
{ id, type, ...fields, children? }.typeis namespaced (ui:Hero).childrenis an array of frame ids (composition is by reference, not nesting). - A pack turns the document into HTML. The document never changes; the pack decides the look.
@midl/pack-minimal-html— bare semantic tags, zero CSS. Best for a first run.@midl/pack-tailwind-shadcn— Tailwind + shadcn classes + a:roottheme viathemeCss.- Pack ids are literal strings built into
@wisepunk/core— not npm packages to install.
- A theme (optional) is a token map mapped to
themeCss(:root { … }), contrast-checked.
import { renderMidl } from '@wisepunk/renderer-html';
const { html, themeCss, validation } = renderMidl(doc, { pack: '@midl/pack-minimal-html', theme });
2. Discover the vocabulary (instead of reading docs)
import { describeVocabulary, EXAMPLE_DOCS } from '@wisepunk/core';
const vocab = describeVocabulary();
// vocab.components[] — one entry per ui:* type:
// { type, requiredFields, leaf, allowsChildren, container, acceptsText, textProp?,
// variants?, packs: { <id>: { tag } }, example }
So for any component you get its required fields; whether it accepts text (acceptsText + which
textProp — e.g. ui:Button → text, ui:Hero → title); whether it takes children (allowsChildren
= the validator's real rule, container = a declared child container); its variant axes (e.g.
ui:Button → variant/size, ui:Stack → direction, ui:Section → width, ui:Hero → size); the
tag each pack renders it as; and a minimal example. allowsChildren and the validator now agree
(MID-100) — introspection never forbids a pattern the validator accepts.
EXAMPLE_DOCS is an array of { name, description, doc } — full, valid documents to pattern-match
(landing, contact-form, article). Every doc is test-asserted valid against both packs, so they're
safe to copy and mutate. E.g. EXAMPLE_DOCS.find(e => e.name === 'landing').doc.
On a live deployment the same data is served (origin-rooted):
| Surface | What it gives you |
|---|---|
GET /llms.txt |
The discovery index (this, machine-readable) |
GET /api/agents/vocabulary.json |
describeVocabulary() output |
GET /api/agents/examples.json |
EXAMPLE_DOCS |
GET /api/agents/openapi.json |
OpenAPI 3.1 for the public API |
GET /.well-known/agent.json |
Site identity, tools, formats |
GET /render/<slug>.midl.json |
A page's raw MIDL document |
GET /render/<slug>.facts.json |
Extracted facts (RAG/agent view) |
3. The authoring loop: validate → repair → render
MIDL is designed so you author by round-tripping, not by getting it right blind.
- Write a document from a
describeVocabulary()entry or anEXAMPLE_DOCSdoc. - Validate it. The result names every problem (missing required field, unknown type, bad child ref):
Or over HTTP:import { validateMIDL, validateAgainstManifest, getPack } from '@wisepunk/core'; validateMIDL(doc); // structural: { valid, errors, warnings } validateAgainstManifest(doc, getPack(packId).manifest); // + pack constraints: { valid, errors, warnings }POST /api/validatewith the document. Or just readrenderMidl(doc).validation. Readwarnings, not justerrors: a prop a component won't render (e.g. asubtitleonui:Hero, a hallucinated field) surfaces as awarning— it won't fail validation but it won't render either (MID-100). - Repair structural issues automatically instead of hand-fixing:
import { repairAgainstManifest, getPack } from '@wisepunk/core'; const { doc: fixed, repairs } = repairAgainstManifest(doc, getPack(packId).manifest); - Render —
renderMidl(doc, { pack })→ HTML.validation.valid === false? Fix whatvalidation.errorssay and re-render. Trust the validator over your assumptions.
4. Authoring & serving stored pages (the write API)
To create/update pages on a deployment, use @wisepunk/sdk with a Bearer API key:
import { createClient } from '@wisepunk/sdk';
const midl = createClient({ baseUrl: 'https://your-deployment.workers.dev', apiKey: process.env.MIDL_API_KEY });
const { id, slug } = await midl.frames.create({ content: doc, slug: 'home' }); // validated server-side
const page = await midl.frames.get({ slug: 'home' }); // read it back (GET is public)
Reads (list, get) are public; writes (create, update, delete) need the key. The full HTTP contract
is GET /api/agents/openapi.json. See WRITE-API-MANUAL.md.
5. Theming
Pass options.theme (DESIGN.md tokens) to renderMidl, or let a document theme itself.
- Token values accept a plain string OR
{ value }—{ 'colors.primary': '#34d399' }works (MID-102). - Self-theming documents — a
ui:StyleTokenframe'stokensare read automatically when you don't passoptions.theme.renderMidl(doc, { pack })themes from the document itself. - Foregrounds are auto-derived to pass WCAG AA against their own background — you supply background-side
colors;
--*-foregroundis computed (and contrast-checked). To override a derived foreground (e.g. make--muted-foregroundless bright), set the*.textkey:colors.muted.text,colors.card.text,colors.primary.text, etc. - Tailwind hosts (for
@midl/pack-tailwind-shadcn): don't hand-write the var mapping —import { tailwindThemeExtend } from '@wisepunk/core'and spread it intotailwind.config'stheme.extendsobg-primary/text-muted-foreground/ the radius resolve to the varsthemeCsssets.
6. Gotchas
- Children are ids, not objects —
children: ['hero', 'cta'], and those frames exist inframes. - Packs aren't installable — pass the id string to
renderMidl; it's bundled in@wisepunk/core. minimal-htmlneeds no CSS;tailwind-shadcndoes — start withminimal-html.- Express structure in the document, not the host shell:
ui:Heading(level1–6) /ui:Hero(title→<h1>,size) for outline;ui:Label(text+for) for inputs;ui:Stack(direction: row|column) /ui:Section(width: content|full) for layout;ui:Buttonwithhrefrenders an<a>(real CTA);ui:Select(options: string[]),ui:Rating(value/max),ui:Accordion(summary→<details>). - ESM + Node 18+ for the packages.
- Don't assume — round-trip. If
validation.validis false, the document is wrong, not the renderer; ifvalidation.warningsis non-empty, a prop you set isn't being rendered.