MA2HSpec v0.6FAQOH HAI (hub)GitHub
MA2H / Spec / v0.1

Superseded. The current version is v0.6. Rendered from /spec/v0.1.md.

MA2H — Multi-agent to Human Protocol

Version: 0.1 (Draft) · Date: 2026-06-03 · License: Apache-2.0 · Steward: Autonomy

⚠️ Superseded by v0.2 (2026-06-03). v0.1 is retained for history only. It has known trust-model, return-leg, concurrency, and durability gaps that v0.2 fixes — do not implement against it. See the CHANGELOG.

0. Abstract

MA2H is a vendor- and runtime-neutral protocol by which agents reach humans and receive decisions back. It defines a hub-and-spoke model: heterogeneous Agents POST one of three message types — notify, ask, task — to a central Hub; a Human triages them from one inbox; and answers to ask/task messages are routed back to the originating agent via push (webhook) or pull (polling). MA2H is the agent↔human complement to A2A (agent↔agent) and MCP (agent↔tools).

1. Conformance and Terminology

The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, MAY are to be interpreted as described in RFC 2119 / RFC 8174.

A conformant Hub MUST accept the message envelope (§4–§5), expose the transport bindings (§8), and emit the response envelope (§6) on resolution. A conformant Agent MUST send a valid envelope and MUST be able to receive a resolution by at least one of push or pull.

2. Goals and Non-Goals

Goals

Non-Goals (v0.1) — deferred to later versions, called out in §10.

3. Architecture

  ┌─────────┐   POST /v1/messages   ┌──────────────────┐   inbox    ┌────────┐
  │  Agent  │ ────────────────────▶ │       Hub        │ ─────────▶ │ Human  │
  │ (spoke) │ ◀──────────────────── │ (ingest + inbox  │ ◀───────── │        │
  └─────────┘  push webhook  /  or  │  + router)       │  resolve   └────────┘
       │       agent polls poll_url └──────────────────┘
       └── GET /v1/messages/{id}

A notify terminates at the inbox. An ask or task produces a Response (§6) when the human resolves it; the Hub delivers that Response to the agent by the binding the agent selected (§8).

4. Message Envelope (common fields)

Every message is a JSON object with these common fields:

Field Type Req Notes
ma2h_version string MUST Protocol version, e.g. "0.1".
id string MUST Stable message id (ULID/UUID). Agent-supplied or Hub-assigned; if agent-supplied it MUST be unique per agent.
type enum MUST notify | ask | task. Discriminator.
created_at string MUST RFC 3339 timestamp.
agent object MUST Origin descriptor (§4.1).
title string MUST One line for the inbox list. ≤ 200 chars.
body string SHOULD Human-readable detail. Markdown.
priority enum SHOULD low | normal | high | urgent. Default normal.
tags string[] MAY Free-form labels for filtering (["deploy","web-app"]).
context Part[] MAY Attachments: text, structured data, or files (§4.2).
expires_at string MAY RFC 3339. After this, an unresolved ask/task auto-resolves (§7).
idempotency_key string MAY Hub MUST treat a repeat key from the same agent as the same message.

4.1 agent descriptor

{
  "id": "deploybot/dev-team",        // stable logical agent identity
  "run_id": "run_01H...",         // this specific invocation (correlation for resume)
  "runtime": "github-actions",    // github-actions | cli | cloud | desktop | openclaw | other
  "project": "web-app",         // optional grouping key (≈ A2A contextId)
  "labels": { "model": "opus-4.8" } // optional free-form
}

agent.id + agent.run_id together identify the Caller to route a response back to.

4.2 Part (borrowed from A2A)

A context entry is exactly one of:

{ "kind": "text", "text": "..." }
{ "kind": "data", "data": { "...": "..." } }     // structured JSON, e.g. a diff summary
{ "kind": "file", "file": { "uri": "https://...", "name": "diff.patch", "mime_type": "text/x-diff" } }

Each Part MAY carry metadata.

5. The Three Verbs

5.1 notify

Informational. No response is expected; the message is terminal on delivery. Used for summaries, status updates, the daily executive digest. Carries only the common envelope (§4). A Hub MUST NOT route a response for a notify.

5.2 ask

A decision the human must make. The agent is blocked on, or will act on, the answer. Adds a request block:

"request": {
  "mode": "select",            // select | input | confirm
  "options": [                 // REQUIRED when mode=select (MCP enumNames + HumanLayer ResponseOption)
    { "value": "ship",  "label": "Ship to prod now",  "description": "Deploy immediately." },
    { "value": "hold",  "label": "Hold for review",   "description": "Wait for a human PR review." },
    { "value": "abort", "label": "Abort the change",  "description": "Revert and close." }
  ],
  "schema": {                  // REQUIRED when mode=input — FLAT JSON Schema (string/number/bool/enum only)
    "type": "object",
    "properties": { "reason": { "type": "string" } },
    "required": ["reason"]
  },
  "permissions": {             // borrowed from LangGraph HumanInterruptConfig
    "allow_accept": true, "allow_edit": true, "allow_respond": true, "allow_ignore": true
  },
  "default_on_expire": "hold", // value applied if expires_at passes unresolved; null = no default
  "callback": { /* §8.3 */ }
}

mode=confirm is sugar for a two-option select (approve/deny).

5.3 task

A manual action a human performs in the world, outside the agent. Distinct from ask: the human does work, not just picks an answer. Adds an action block:

"action": {
  "instructions": "Rotate API_SIGNING_KEY in the prod vault and confirm the webhook still signs.",
  "checklist": [                       // optional sub-steps the human can tick
    { "text": "Generate a new key in the secret manager", "done": false },
    { "text": "Update prod secret", "done": false }
  ],
  "verification": "Webhook test event returns 200 with a valid signature.",
  "callback": { /* §8.3 — optional; notify the agent when marked done */ }
}

6. Response Envelope (human → Hub → agent)

Emitted by the Hub when an ask/task reaches a terminal state. Delivered via §8.

{
  "ma2h_version": "0.1",
  "in_reply_to": "msg_01H...",          // the original message id
  "agent": { "id": "deploybot/dev-team", "run_id": "run_01H..." },
  "resolution": "answered",             // see §7 per type
  "response": {                         // present for ask; present-if-captured for task
    "value": "hold",                    // chosen option value, or the input object
    "edited": false,                    // true if the human edited an agent-proposed value
    "actor": "human:alice",          // who resolved it
    "resolved_at": "2026-06-03T18:04:11Z",
    "comment": "Let's get a human eye on the migration first."
  },
  "state": { "...": "..." }             // OPAQUE agent state, round-tripped verbatim (HumanLayer pattern).
                                        // The Hub MUST store and return it unmodified and MUST NOT inspect it.
}

The state field lets a stateless agent reconstruct its context on resume without the Hub understanding it.

7. Lifecycle / State Machines

notify:   created ──▶ delivered (terminal)

ask:      open ──┬─▶ answered    (terminal)   ← human chose/responded
                 ├─▶ declined    (terminal)   ← human refused (allow_respond/permissions)
                 ├─▶ cancelled   (terminal)   ← agent or Hub withdrew it
                 └─▶ expired     (terminal)   ← expires_at passed; default_on_expire applied if set

task:     open ──┬─▶ completed   (terminal)   ← human marked done
                 ├─▶ dismissed   (terminal)   ← human declined to do it
                 └─▶ expired     (terminal)

Terminal states are irreversible (A2A rule). An agent MAY cancel its own open message (§8.4); the Hub MUST mark it cancelled and stop soliciting the human.

8. Transport Bindings (HTTP/JSON)

All endpoints are HTTPS, Content-Type: application/json. Base path /v1.

8.1 Submit — POST /v1/messages

Body: a message envelope (§4–§5). On success the Hub returns 202 Accepted (HITL-Protocol handshake):

{
  "id": "msg_01H...",
  "status": "open",                                  // or "delivered" for notify
  "poll_url": "https://hub.example/v1/messages/msg_01H...",
  "review_url": "https://hub.example/inbox/msg_01H..."// human-facing deep link (out-of-band share)
}

8.2 Pull — GET /v1/messages/{id}

Returns the message plus its current status and, once terminal, the response (§6). The Hub SHOULD support long-poll via ?wait=<seconds> (cap 60): hold the connection until resolution or timeout, then return current state. This is the binding for ephemeral agents that cannot be called back.

8.3 Push — agent-supplied callback

An ask/task MAY include a callback so the Hub delivers the Response actively (A2A PushNotificationConfig):

"callback": {
  "mode": "push",                       // push | pull (pull = omit url; agent polls 8.2)
  "url": "https://agent.example/ma2h/resolve",
  "auth": { "scheme": "hmac", "secret_ref": "env:MA2H_CALLBACK_SECRET" }  // hmac | bearer | apikey
}

On resolution the Hub POSTs the Response envelope (§6) to url. The Hub MUST sign the request (§9), MUST retry with exponential backoff on non-2xx (≥ 5 attempts), and MUST include the message id so delivery is idempotent. An agent MAY register both push and pull; push is best-effort, pull is the source of truth.

8.4 Cancel — POST /v1/messages/{id}/cancel

Withdraws an open message. Idempotent.

9. Security and Privacy

10. Versioning and Roadmap

Roadmap (explicitly out of v0.1):

11. Provenance (what MA2H reuses, and from where)

MA2H is intentionally non-NIH. Conventions adopted from prior art:

From Reused in MA2H
A2A (Linux Foundation) Part union (text/data/file), callback ← PushNotificationConfig auth schemes, project ≈ contextId, terminal-state irreversibility, input-required → the ask lifecycle
HITL Protocol (rotorstar/hitl-protocol) 202 Accepted + poll_url/review_url submit handshake (§8.1)
MCP elicitation (Anthropic) select options as enum+enumNames, flat input schema for renderability, accept/decline/cancel resolutions
HumanLayer opaque state round-trip for stateless agents, typed contact channels (roadmap §10)
LangGraph request.permissions ← HumanInterruptConfig (allow_accept/edit/respond/ignore)
CHEQ (IETF draft) keep human-entered secrets out of the agent's LLM context (§9)

Appendix A — Worked Examples

See /examples: the daily executive digest as a notify, a dev-team ship/hold decision as an ask, a manual key-rotation as a task, and the corresponding response envelope.