AAP v1.0

Actions for AI Agents

Your agents render UIs. Your users interact with them. AAP delivers those interactions — structured, stored, and ready to process.

View the full spec at agentactions.org

THE PROBLEM

Why AAP?

Agents render rich HTML. Users click things. How do those clicks get back to the agent?

📋

console.log scraping

Fragile, no structure, lost on refresh. Depends on browser dev tools being open.

Brittle
🔄

Polling the DOM

Complex, race conditions, not sandboxed. Breaks when the HTML changes.

Fragile
🔌

Custom WebSocket

Over-engineered for button clicks. Security nightmare with open socket per page.

Overkill
🎯

AAP

Structured actions via postMessage. Stored as JSON. Agent notified instantly.

Just right

THE FLOW

How It Works

Seven steps from agent HTML to structured interaction record.

Agent writes HTML  →  Dashboard renders in iframe  →  User clicks button
                                                              ↓
Agent reads JSON   ←  File written + notification   ←  postMessage to parent
                                                              ↓
                                                         HTTP POST to API
1

Agent creates HTML

Writes an HTML page with maestro.send() calls on interactive elements.

2

Bridge injected

Dashboard injects the AAP bridge script and renders in a sandboxed iframe.

3

User interacts

Click, submit, select, toggle. Bridge calls postMessage with structured data.

4

Stored & notified

Server stores interaction as JSON file and notifies agent via terminal.

The Bridge Script

// Injected into every canvas HTML by the provider

window.maestro = {
  send: function(action, element, data) {
    window.parent.postMessage({
      type: 'canvas:interaction',
      action: action,
      element: element || null,
      data: data || null
    }, '*');
  }
};

Usage in Canvas HTML

<button onclick="maestro.send('click', 'approve-btn', { approved: true })">
  Approve
</button>

BEST PRACTICE

Data-Driven Canvas

Canvas pages should embed structured data and render it dynamically. This enables sorting, filtering, search, and real-time interaction.

The Pattern: Embedded JSON + JS Rendering

<!-- Data block — structured, parseable, separate from presentation -->
<script type="application/json" id="page-data">
{
  "tests": [
    { "name": "auth-login", "status": "passed", "duration": 1.2 },
    { "name": "api-users", "status": "failed", "duration": 0.8 }
  ],
  "summary": { "total": 142, "passed": 135, "failed": 7 }
}
</script>

<!-- Rendering logic — reads data, builds interactive UI -->
<script>
  const DATA = JSON.parse(
    document.getElementById('page-data').textContent
  );
  // Build tables, charts, filters from DATA
  // Attach maestro.send() to interactive elements
</script>

Why Embedded JSON?

Static HTML Tables

No sorting, filtering, or search. Data locked in markup. Dead on arrival.

Fetch from External API

Violates sandbox. Requires CORS. Adds latency. Breaks offline.

Inline JS Literals

Hard to parse. No separation of data and presentation. Messy.

Embedded JSON

Clean separation. Parseable. Enables full interactivity. Works in sandbox.

Interactive Features by Data Type

Data Type Interactive Features
Tables / listsSort by column, filter by status, search, pagination
Metrics / KPIsExpand details on click, compare periods
Forms / configValidation, submit via maestro.send('submit', ...)
WorkflowsStep navigation, approve/reject actions
Trees / hierarchiesExpand/collapse, drill-down

THE PROTOCOL

Specification

Version

aap/1.0

Action Message Format

The postMessage payload sent from the canvas iframe to the parent window:

{
  "type": "canvas:interaction",
  "action": "submit",
  "element": "approve-button",
  "data": { "comments": "Looks good", "rating": 5 }
}
Field Type Required Description
typestringYesAlways "canvas:interaction"
actionstringYesAction verb (see Standard Actions)
elementstringNoElement identifier (id, name, label)
dataobjectNoArbitrary key-value payload

Standard Action Vocabulary

Action Use case
clickButton press, link activation
submitForm submission
changeInput value changed
selectOption selected from dropdown/list
toggleBoolean switch toggled
dismissModal/notification dismissed
navigateIn-canvas navigation (tab switch, page change)
customApplication-specific (use data for details)

Custom actions beyond this vocabulary are allowed — the action field is freeform.

Interaction Record Format

The stored interaction (server-side):

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "timestamp": "2026-05-18T15:30:00.000Z",
  "canvasFile": "reports/dashboard.html",
  "action": "submit",
  "element": "approve-button",
  "data": { "comments": "Looks good" },
  "summary": "User submit 'approve-button' on reports/dashboard.html with data: {comments: Looks good}"
}
Field Type Required Description
idUUIDYesUnique interaction identifier
timestampISO 8601YesWhen the interaction occurred
canvasFilestringYesRelative path of the canvas HTML file
actionstringYesThe action verb
elementstringNoElement identifier
dataobjectNoArbitrary payload
summarystringYesHuman-readable summary for the agent

Storage

  • Path: ~/.aimaestro/agents/<agentId>/canvas/interactions/
  • Filename: <ISO-timestamp>-<UUID>.json (with : and . replaced by -)
  • Strategy: One file per interaction (append-only, immutable)
  • Ordering: Sorted by filename = sorted by time

Transport

Canvas iframe
  | window.parent.postMessage()
  v
Parent window (Dashboard)
  | POST /api/agents/:id/canvas/interactions
  v
Provider API
  | Write JSON file + tmux notification
  v
Agent

API Endpoints

Submit Interaction

POST /api/agents/:id/canvas/interactions
Content-Type: application/json

{ "action": "submit", "element": "btn", "canvasFile": "page.html", "data": {} }

→ 201 { "id": "uuid", "summary": "User submit 'btn' on page.html" }
→ 400 { "error": "missing_field", "message": "action is required" }
→ 404 { "error": "not_found", "message": "Agent 'xyz' not found" }

List Interactions

GET /api/agents/:id/canvas/interactions?limit=50

→ 200 { "interactions": [ { ... }, { ... } ] }

Agent Notification

When an interaction is stored, the provider SHOULD notify the agent:

[CANVAS] reports/dashboard.html: User submit 'approve-button' on reports/dashboard.html with data: {comments: Looks good}

Notification is fire-and-forget. Failure does not affect interaction storage.

Security Considerations

  • Canvas HTML runs in sandbox="allow-scripts" iframe (no same-origin, no forms, no popups)
  • Bridge uses postMessage('*') — parent validates event.data.type before processing
  • No credentials, tokens, or authentication data should be sent via data payload
  • data is stored as-is — providers SHOULD sanitize before displaying
  • Canvas files are read-only to the user — only the agent can write them
  • Path traversal protection: canvas file paths must not contain .. or be absolute

BUILD IT

For Implementers

Building an AAP-compatible provider? Here is what you need.

Implementation Checklist

1

Inject bridge script into canvas HTML before rendering in iframe

2

Listen for postMessage with type: 'canvas:interaction'

3

Validate action field is present and non-empty

4

Generate UUID and ISO 8601 timestamp for each interaction

5

Build human-readable summary string

6

Store interaction as JSON (recommended: file-per-interaction, append-only)

7

Notify agent (optional, provider-specific mechanism)

8

Expose API for listing interactions (optional)

Relationship to AMP & AID

🎯

AAP

User → Agent
UI interactions

📬

AMP

Agent → Agent
Signed messages

🔑

AID

Agent identity
Cryptographic auth

All three are independent but complementary. They share the same agent directory structure.

Roadmap

v1.0

One-way canvas interactions (user → agent) — current

v1.1

Bidirectional — agents push updates back to canvas

v1.2

Interaction acknowledgment — agent confirms receipt

v2.0

Agent-defined UI components (widgets, forms, controls)

FOR AI AGENTS

Claude Code Plugin

The canvas-actions skill teaches Claude Code agents how to create, manage, and interact with canvas pages.

1

When to Create

Proactive triggers: "show me", "visualize", "dashboard for", "let me approve". When in doubt, create a canvas.

2

How to Build

Data-driven interactive HTML with embedded JSON. Sort, filter, search built in. Actions via maestro.send().

3

How to React

Process [CANVAS] notifications, read interaction JSON, respond to user actions in real time.

Installation

# Install via AI Maestro plugin (includes AAP + AMP + AID)
git clone https://github.com/23blocks-OS/ai-maestro-plugins.git
cd ai-maestro-plugins
./build-plugin.sh --clean
./install-plugin.sh -y

Or add AAP as a git source in your own plugin.manifest.json:

{
  "name": "agent-actions",
  "type": "git",
  "repo": "https://github.com/agentmessaging/agent-actions.git",
  "ref": "main",
  "map": {
    "skills/canvas-actions": "skills/canvas-actions"
  }
}

Proactive Trigger Keywords

When users say any of these, agents create a canvas page instead of plain text output:

"show me" "display" "visualize" "report on" "dashboard for" "let me configure" "let me approve" "compare"

Let Your Agents See the Clicks

Open protocol. MIT licensed. Works with any agent dashboard.