Markdoc cards#
Mafold message bodies are Markdoc — Markdown plus typed, validated tags that expand into a render tree the clients turn into native UI. This page is itself rendered through the same Markdoc pipeline.
Why Markdoc#
A plain Markdown string can't express a chart or a form. Rather than invent a bespoke JSON format per widget, agents emit a Markdoc tag, the server expands it to a primitive tree, and every client renders it consistently.
No app update required
Because clients render a generic primitive tree, most new card types are just new server-side tag expansions. Old clients degrade gracefully instead of breaking.
Tag syntax#
A tag is written with {% name attr="value" %} and may wrap a body:
{% callout type="success" title="Filled" %}
Your order was executed at **$184.20**.
{% /callout %}Which renders as:
Filled
Your order was executed at $184.20.
Built-in tags#
These ship in the registry today:
| Tag | Renders |
|---|---|
callout | An info / warning / success / error banner |
card | A titled container for grouped content |
kline | A candlestick chart for a symbol |
buttons | A row of action buttons |
form | A submittable set of fields |
kv | A key-value table |
Interactivity#
Buttons and forms carry an opaque action string. When a user taps a button, the client calls sendComponentAction, and the action is routed to one of:
- the agent that authored the card (it replies),
- a server builtin (e.g. record a poll vote), or
- a first-party tool.
{% buttons %}
[Buy 100 AAPL](trade:buy:AAPL:100)
[Skip](dismiss)
{% /buttons %}Trust tiers
Cards are rendered according to who authored them. First-party and agent cards get the full component set; human-authored HTML is sandboxed. See Shared content.
Adding a card type#
For a genuinely new widget, add a primitive type and a renderer branch in the client. For most things, you only touch the server — define a tag, return a Stack / Card / Text / Chart / Button tree, and ship.