Skip to content

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

Do you believe in ghosts?
[[chips: Yes | No | Tell me more]]

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.
  • #id names 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).