<button text="Deploy">
Slack
Teams
Web
A Proposal

Interactive
Markdown

A markup language for AI agents. One format for content and interaction — everywhere.

10 min read

LLMs speak markdown. It’s simple and effective. But in a world graduating from timid LLM adoption to agent frenzy, markdown is no longer cutting it. The missing piece is interaction. What follows is a proposal for iMD — Interactive Markdown — which, if standardized, would make the lives of us builders much easier.

01 — The Problem

Content and interactivity are strangers

“Do you want to proceed?” and its Yes/No buttons are one unit of meaning. So why do we split them into separate data structures?

Today, agents treat content and interactivity as separate concerns:

Typical Agent Code
send_message(
  body: "Choose a plan:",
  format: "markdown",
  actions: [{
    type: "actions",
    elements: [{ type: "button", text: "Basic", action_id: "plan:basic" }]
  }]
)

The actions structure mirrors Slack’s Block Kit. Platform-specific implementation details have leaked into domain logic. When you need Teams, Discord, or a web UI, that coupling forces changes throughout the stack.

Actions are always appended to the end. There’s no way to place a button contextually within the content where it belongs. And when you test or debug, you’re correlating two data structures to reconstruct what the user saw.

The root cause is structural: we’ve separated things that belong together.

The fix is embarrassingly simple.

02 — The Solution

Interactive Markdown

Extend GitHub Flavored Markdown with interactive elements using familiar HTML-like syntax:

iMD
Choose a plan:

<button text="Basic" action_id="plan:basic">
<button text="Pro" action_id="plan:pro" style="primary">

One format. One data structure. One thing to log. The agent produces iMD; platform-specific converters handle the rest.

Design principles

Content-firstInteractive elements live within content, not alongside it.
Platform-agnosticDefine semantic intent, not implementation details.
Graceful degradationMessages remain useful even when interactivity isn’t supported.
Familiar syntaxHTML-like tags that any developer recognizes immediately.
Human-readableRaw iMD is understandable without rendering.
03 — Architecture

How it works

┌─────────────────────────────────────────┐
│            Agent / Backend              │
│                                         │
│   “Choose an option:                    │
│    <button text="Yes" action_id="y">    │
│    <button text="No" action_id="n">”    │
└───────────────────┬─────────────────────┘
                    │  iMD
      ┌─────────────┼─────────────┐
      ▼             ▼             ▼
┌──────────┐  ┌──────────┐  ┌──────────┐
│  Slack   │  │  Teams   │  │   Web    │
│ Block Kit│  │ Adaptive │  │  React   │
│          │  │  Cards   │  │Components│
└──────────┘  └──────────┘  └──────────┘

Adding a platform requires two things: a converter and action handlers. The agent’s code doesn’t change.

04 — AI Agents

Why this matters for agents

Include the syntax in a system prompt and the agent produces interactive messages without knowing anything about Block Kit or Adaptive Cards:

System Prompt
You can create interactive messages using iMD syntax:

<button text="Label" action_id="unique-id">
<button text="Label" action_id="id" style="primary">
<button text="Label" action_id="id" style="danger">
<button text="Label" action_id="id" value='{"key":"value"}'>

That’s it. The agent learns one syntax. The value attribute lets agents embed structured JSON context that gets returned on click.

05 — MCP Integration

Action routing

For systems using the Model Context Protocol, iMD supports a clean request-response pattern: the agent requests a token, embeds it in a button, and the MCP server handles the callback.

Agent                    MCP Server                Platform
  │                          │                         │
  │── get_action_token() ──>│                         │
  │<── { token: "abc123" } ──│                         │
  │                          │                         │
  │── send_message() ──────────────────────────────>  │
  │   "<button action_id='confirm:abc123'>"           │
  │                          │                         │
  │                          │<── button_clicked ──────│
  │                          │    action_id: abc123    │
06 — Syntax

The button element

AttributeRequiredDescription
textYesButton display label
action_idYesUnique identifier for routing click events
styleNo"primary" or "danger"
valueNoJSON payload returned on click

Tags inside fenced code blocks are not parsed. Use single quotes for value to avoid escaping JSON.

PlatformOutputLimits
SlackBlock Kit actions5 per group, 25 total
TeamsAdaptive Card actions6 per card
WebReact componentsNone
EmailFallback to linksN/A
07 — Future

What comes after buttons?

Agents frequently need structured data — not just “yes or no” but “which region, what amount, starting when.” Today most agents handle this through conversational back-and-forth. It works, but it’s slow and error-prone.

Future iMD — Forms
Configure your deployment:

<select action_id="region" placeholder="Choose region">
  <option value="us-east-1">US East</option>
  <option value="eu-west-1">EU Ireland</option>
</select>

<input action_id="instances" placeholder="Count">

The hard part isn’t imagining useful elements — it’s defining them at the right level of abstraction. Too specific and you’re back to platform coupling. Too generic and converters can’t produce good results.

There’s no guarantee that sweet spot exists for every element, and finding it will take real implementation experience — not just spec writing.

Security

Considerations

Sanitize text and value attributes for target-platform injection. Action IDs alone should never authorize — verify the clicking user and consider single-use tokens for sensitive operations. Parsers should enforce limits on attribute lengths and nesting depth.

Closing thoughts

The insight behind iMD is small but consequential: a message and its buttons are one thing, not two. Once you treat them that way, a lot of accidental complexity disappears. Agents get simpler. Multi-platform support gets cheaper. Testing gets easier.

iMD is still experimental. If you’re building agent systems that work across messaging platforms, I’d love to hear your thoughts.