AML — Anychat Markup Language¶
Teach any LLM agent to speak rich messaging in one page.
AML is plain conversational text plus lightweight [[...]] markers.
Anychat compiles AML into AMF messages and renders each
marker with the richest feature the user's channel supports — an Apple time
picker, RCS chips, or a numbered SMS list — so the agent never adapts its
output to the channel.
Three properties make AML LLM-friendly:
- Plain text is always valid AML. A model that emits no markers still works everywhere.
- Markers degrade safely. Unknown or malformed markers are dropped with a warning — never a hard failure, never leaked to the user.
- Answers come back as ids. When the user answers a marker (tap or text), the agent receives the option id it chose, on every channel.
Use AML by returning text/anychat-aml from your webhook, by compiling via
POST /v4/compile, or
automatically inside the Anychat agent runtime.
The skill¶
If you run your own LLM agent, paste the speak Anychat skill — the
machine-readable cheatsheet below — into your system prompt. It is published
as AML_SKILL_PROMPT in the
@anychat-ai/messaging-api
package so your prompt and Anychat's parser version together.
Marker reference¶
Quick replies¶
Tappable chips (≤5 recommended, short labels). Label = value sets a
distinct value to receive when tapped: [[chips: Yes=opt-yes | No=opt-no]].
Ask the user to choose — ask.choice¶
[[ask.choice #treatment | Which treatment are you interested in?
botox = Botox ~ Smooths fine lines | media: https://example.com/botox.jpg
filler = Dermal filler ~ Restores volume]]
One option per line: id = Label ~ description | media: url. The #id
names the intent; the chosen option's id comes back in the result event.
Renders as a list picker (AMB), chips or cards (RCS), or a numbered list
(SMS) — and the user can always just type their answer instead of
picking; unambiguous replies resolve to the same result event. A
single option never renders as a picker: it becomes one chip, or a
"reply YES to take it" sentence on SMS.
Offer times — ask.time¶
[[ask.time #booking | When works best?
tue-345 = 2026-06-10T15:45:00-07:00 ~ Tue 3:45 PM
wed-10 = 2026-06-11T10:00:00-07:00 ~ Wed 10:00 AM]]
Slot lines are id = startISO ~ label. The ISO time is authoritative (include
the UTC offset); the label is optional — Anychat generates one per locale and
channel when omitted. Renders as an Apple time picker, RCS slot chips, or a
numbered SMS list. A single slot renders as one chip (or a "reply YES to
take it" sentence on SMS) rather than a one-row picker.
Confirm — confirm¶
[[confirm #meet-bob | Confirm your meeting with Bob for Tue at 3:45 PM PT?
title = Intro call with Bob
when = 2026-06-10T15:45:00-07:00
tz = America/Los_Angeles
yes = Confirm
no = Pick another time]]
Result is optionIds: ["yes"] or ["no"] — on SMS, "YES"/"NO" replies
resolve identically. Supplying when/title lets capable channels attach
the event to the confirmation.
Showcase products or options — showcase¶
[[showcase #pick
prod-1 = Botox ~ From per-unit pricing | media: https://example.com/botox.jpg | price: 12.00 USD
prod-2 = Dermal filler ~ Volume restore | media: https://example.com/filler.jpg]]
Renders a carousel with Select chips, or a numbered digest on SMS. The #id
is optional — include it to receive selection results. A single-item
showcase is informational: it renders as a plain card (no Select chip),
the visual anchor for the thing being discussed rather than a menu.
Everything else¶
[[card | Title ~ Short description | media: https://example.com/img.jpg]]
[[media: https://example.com/photo.jpg]]
[[link: See pricing | https://example.com/pricing]]
[[dial: Call us | +14155551212]]
[[event: Intro call | 2026-06-10T15:45:00-07:00 | 2026-06-10T16:15:00-07:00 | Optional description]]
[[place: Our clinic | 37.7749,-122.4194 | 123 Main St, San Francisco]]
[[ask.location #loc | What's your ZIP code so I can find the nearest location?]]
[[template: welcome-back | firstName=Ann]]
link and dial attach as chips to the surrounding message. event and
place degrade to add-to-calendar / map links on plain channels.
template references a message template registered with your agent.
Multiple bubbles¶
Separate distinct messages with [[next-message]]:
Got it — you're all set for Tuesday.
[[next-message]]
Anything else I can help with?
[[chips: Reschedule | No, thanks]]
Grammar notes¶
- Markers are block-level:
[[name #id flags | head fields]]with optional newline-separated option lines. Head fields are|-separated. #idnames an intent. Omit it and one is generated — supply it whenever your agent wants to recognize the answer.- Option lines are
id = Label ~ description | key: value | key: value. - Inline markdown in text (bold, italic, links, lists) is rendered or stripped per channel.
- Unknown markers are dropped with a warning; agent runtimes may register
extensions (the Anychat runtime adds
[[products: ...]], resolved from the agent's product catalog).