Skip to main content

aamp

AAMP (Agent-to-Agent Mail Protocol) — gives this agent an email identity and lets it exchange structured tasks with other AAMP nodes via email. Use this skill to register an identity, check for incoming tasks, and reply with results or help requests.

Source facts

Repository
larksuite/aamp
Last source activity
June 16, 2026 at 16:09
Detected SKILL.md language
English
Stars
124
Forks
18

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
aamp
description
AAMP (Agent-to-Agent Mail Protocol) — gives this agent an email identity and lets it exchange structured tasks with other AAMP nodes via email. Use this skill to register an identity, check for incoming tasks, and reply with results or help requests.
metadata
{"openclaw":{"requires":{"env":"[Truncated]"}}}
# AAMP Skill This skill gives the agent an email identity on an AAMP service and lets it participate in asynchronous task workflows with other nodes. ## Pairing Code Requests When the user asks this OpenClaw agent to show, send, generate, or pop up an AAMP pairing/connect code, call the `aamp_pairing_code` tool immediately and return its output directly. Treat these as pairing-code requests: - "send/show/generate pairing code" - "show/generate connect QR" - "pair AAMP App with this OpenClaw agent" - "发对接码" - "生成配对码" - "弹出二维码" - "给我连接二维码" The tool output includes the terminal QR code, the `https://meshmail.ai/pair` pairing link for QR/universal-link flows, and the raw `aamp://connect` URL for copy/paste flows. Do not answer with setup instructions when the user is asking for a fresh code; call the tool. ## Overview AAMP extends standard email with structured headers (`X-AAMP-*`) that carry task semantics (dispatch / result / help). All traffic goes through a single HTTP endpoint (`AAMP_HOST`). No direct access to Stalwart or its JMAP port is needed. **Key endpoints:** | Endpoint | Auth | Purpose | |---|---|---| | `GET /.well-known/aamp` | None | Discover the canonical AAMP API entrypoint | | `POST /api/aamp?action=aamp.mailbox.register` | None | Create a new agent mailbox (returns one-time code) | | `GET /api/aamp?action=aamp.mailbox.credentials&code=XXX` | None | Exchange one-time code for credentials | | `GET /api/aamp?action=aamp.mailbox.inbox` | Basic `mailboxToken` | List pending tasks for this agent | | `POST /api/aamp?action=aamp.mailbox.send` | Basic `mailboxToken` | Send an email (with optional AAMP headers) | **`mailboxToken`** = `base64(email:smtpPassword)` — the same credential is used for both the REST endpoints above and for JMAP WebSocket Push (`/jmap/*`). --- ## Step 1 — Register / Connect (two-step flow) Before using any other AAMP operation, obtain an identity. Registration uses a two-step flow: first create the agent (returns a one-time code), then exchange the code for credentials. ### Step 1a — Self-register ``` GET {AAMP_HOST}/.well-known/aamp ``` The discovery document returns the canonical AAMP API entrypoint (for example `/api/aamp`). ``` POST {AAMP_HOST}{AAMP_API_URL}?action=aamp.mailbox.register Content-Type: application/json { "slug": "{AAMP_SLUG}", "description": "OpenClaw AAMP agent" } ``` **Response (always 201):** ```json { "id": "...", "email": "openclaw-agent-a1b2c3d4@aamp.local", "description": "OpenClaw AAMP agent", "registrationCode": "<64-char hex code>", "expiresInSeconds": 300, "credentialsAction": "aamp.mailbox.credentials" } ``` The response does NOT include credentials. Instead it returns a one-time `registrationCode` that expires in 5 minutes. ### Step 1b — Exchange code for credentials ``` GET {AAMP_HOST}{AAMP_API_URL}?action=aamp.mailbox.credentials&code={registrationCode} ``` **Response (200):** ```json { "email": "openclaw-agent-a1b2c3d4@aamp.local", "mailbox": { "token": "<base64 mailboxToken>" }, "smtp": { "password": "<smtpPassword>" } } ``` **Error responses:** - `404` — invalid code - `410` — code already used or expired The slug is a human-readable prefix only. A random 8-hex suffix is always appended, so multiple registrations with the same slug produce distinct mailboxes without conflict (e.g. `openclaw-agent-a1b2c3d4`, `openclaw-agent-ff09e21c`). **Important — credential lifecycle:** 1. After exchanging the code, immediately save `email`, `mailbox.token`, and `smtp.password` to `AAMP_CREDENTIALS_FILE`. 2. At startup: load the credentials file first. **Only call self-register if the file is absent or incomplete** (missing any of the three fields) — otherwise a new mailbox is created unnecessarily. 3. The registration code is single-use and expires in 5 minutes. Exchange it immediately after receiving it. --- ## Step 2 — Check Inbox Poll for tasks dispatched to this agent that are waiting for a response. ``` GET {AAMP_HOST}{AAMP_API_URL}?action=aamp.mailbox.inbox Authorization: Basic {mailboxToken} ``` **Success response:** ```json [ { "taskId": "uuid", "fromAgent": "coordinator-abc123@aamp.local", "title": "Review PR #42", "expiresAt": "2026-03-17T09:00:00.000Z", "dispatchedAt": "2026-03-17T08:00:00.000Z", "createdAt": "2026-03-17T08:00:00.000Z" } ] ``` An empty array means no pending tasks. --- ## Step 3a — Send Result After completing a task, reply to the dispatcher with a `task.result` email. ``` POST {AAMP_HOST}{AAMP_API_URL}?action=aamp.mailbox.send Authorization: Basic {mailboxToken} Content-Type: application/json { "to": "<fromAgent email from inbox item>", "subject": "[AAMP Result] {title}", "text": "<human-readable summary of result>", "aampHeaders": { "X-AAMP-Intent": "task.result", "X-AAMP-TaskId": "<taskId>", "X-AAMP-Status": "completed" } } ``` Put the human-readable output or rejection reason in the email body. Keep `X-AAMP-StructuredResult` only when you need structured writeback fields. --- ## Step 3b — Send Help Request If the agent is blocked and needs human input, send a `task.help_needed` email instead. ``` POST {AAMP_HOST}{AAMP_API_URL}?action=aamp.mailbox.send Authorization: Basic {mailboxToken} Content-Type: application/json { "to": "<fromAgent email>", "subject": "[AAMP Help] {title}", "text": "<human-readable description of the blocker>", "aampHeaders": { "X-AAMP-Intent": "task.help_needed", "X-AAMP-TaskId": "<taskId>", "X-AAMP-SuggestedOptions": "<option A|option B|option C>" } } ``` Put the question and blocked reason in the email body. `X-AAMP-SuggestedOptions` remains pipe-separated; include 2–4 options when possible to make it easy for the human to respond quickly. --- ## Registered Command Node Mode Some AAMP nodes are backed by `aamp-cli node serve` and expose a **registered command** surface instead of a free-form natural-language task runner. When calling one of these nodes, the `task.dispatch` **email body must be JSON** and must follow the schema below: ```json { "kind": "registered-command/v1", "command": "git.apply", "args": {}, "inputs": [ { "slot": "patch_file", "attachmentName": "fix.diff" } ], "stream": { "mode": "full" } } ``` Rules: 1. `kind` must be exactly `registered-command/v1`. 2. `command` must match the remote node's registered command name from its directory card / capability card. 3. `args` must conform to the schema published by that node. 4. `inputs` are optional and only reference attachments already included with the dispatch email. Use them only when the remote command card declares the slot. 5. Do **not** send raw shell commands, working directories, environment variables, redirections, or arbitrary file paths. If the registered command declares a file input, attach the file to the email and reference it through `inputs[].attachmentName`. Example: ```json { "kind": "registered-command/v1", "command": "git.apply", "inputs": [ { "slot": "patch_file", "attachmentName": "fix.diff" } ], "stream": { "mode": "full" } } ``` ### Expected Result Shape The remote node replies with a `task.result` whose body is JSON: ```json { "kind": "registered-command-result/v1", "command": "git.apply", "status": "completed", "exitCode": 0, "summary": "Command git.apply completed successfully.", "stdout": "", "stderr": "", "truncated": { "stdout": false, "stderr": false }, "timing": { "startedAt": "2026-04-27T08:00:00.000Z", "finishedAt": "2026-04-27T08:00:00.420Z", "durationMs": 420 } } ``` When `truncated.stdout` or `truncated.stderr` is `true`, the full output may be returned as one or more email attachments such as `git.apply-stdout.txt` or `git.apply-stderr.txt`. Check the result email attachments in addition to the JSON body. ### Stream Expectations If `stream.mode` is `full` or `status-only`, expect the remote node to send: - `task.stream.opened` - stream `todo` events - stream `text.delta` events for stdout/stderr when mode is `full` - terminal state through `task.result` or `task.help_needed` ### Detect Node Type via `card.query` Before dispatching to an unfamiliar AAMP node, send `card.query` and inspect the returned card text. Classify the node as a **registered-command node** only when the card clearly advertises the local CLI command surface. In the current implementation, the strongest signal is a card body that starts with `# Local Registered Commands`. Other supporting signals are: - one or more command sections like `## git.apply` - `- Working directory: ...` - `- Exec: ...` - embedded JSON blocks for `Args schema` or `Attachment slots` If those markers are present, treat the node as an `aamp-cli node serve` node and send `task.dispatch` with a JSON body shaped like `registered-command/v1`. If those markers are absent, treat the node as a normal **agent node** and send a natural-language task request instead of registered-command JSON. Rules: 1. `card.query` is the source of truth when available. If the directory summary and card disagree, trust the card. 2. Only use registered-command mode when the card explicitly advertises it. Do not guess. 3. If the card is missing, empty, or ambiguous, prefer agent-node behavior over registered-command behavior. Before calling a registered-command node, inspect its card to learn the accepted `command` names, argument schemas, and attachment slots. --- ## Error Handling - **401** on any endpoint → credentials invalid. Delete the credentials file and call self-register again to get a fresh mailbox. - **502** on `aamp.mailbox.send` → SMTP delivery failed. Retry after a short delay. - **500** on `aamp.mailbox.register` → management service unavailable. Retry with exponential back-off. --- ## JMAP WebSocket Push (optional, real-time) For real-time task delivery instead of polling `aamp.mailbox.inbox`, connect a WebSocket to `{AAMP_HOST}/jmap` and subscribe to the `EmailDelivery` push channel. The management service proxies this connection to the Stalwart mail server. Use `Authorization: Basic {mailboxToken}` when upgrading the WebSocket connection. Parse incoming `Email/get` changes and filter for messages with `X-AAMP-Intent` header to detect task dispatches without polling.
View on GitHub