https://dispatch.foo/a2a/ext/envelope/v1
From §8 of the Dispatch Messaging Protocol 1.0.0-draft.2.
8.3 Extension activation and placement
The three extensions carry what A2A 1.0 has no field for: DMP addressing, why a task is waiting on its owner, and handoffs of work. Each extension URI serves its own subsection together with this one.
An extension is active for a request when its URI appears in the
A2A-Extensions header or in message.extensions. The response echoes the
active URIs in A2A-Extensions, and a host MUST write extension metadata only
for active extensions. A client that activates none gets the same facts as plain
text in status.message.
Extension data sits in metadata[<URI>]. On a Message (including
status.message) and on an Artifact, the object's extensions list also
names the URI. A Task has no extensions field, so task-level data lives only
in Task.metadata[<URI>], written when the request activated the extension.
All metadata on one message together MUST be at most 64 KiB as JSON; a host
refuses more as invalid on message.metadata. The extensions fit well inside
it: work/v1 tops out near 36 KB and envelope/v1 near 15 KB with inbound refs
restricted as §8.4 says. Body (64 KiB), data (64 KiB),
metadata (64 KiB) and A2A's framing fit a 256 KiB request. Vectors:
a2a.envelope-ext.refuses-metadata-over-64-kib.
Each extension is versioned by its URI: a breaking change mints a new URI ending
/v2 (§14.4). The registry of extension URIs is
§11.10.
8.4 envelope/v1
https://dispatch.foo/a2a/ext/envelope/v1 is a profile on Message.metadata: a
DMP envelope (§4.1) riding on an A2A message.
interface EnvelopeExtV1 {
id?: string; // host to client: the DMP message id
thread?: string; // host to client: the DMP thread id
from?: string; // host to client: the sender's address; ignored inbound
to?: string[]; // client to host only: recipients (default: the owner)
kind?: 'message' | 'question' | 'answer' | 'handoff' | 'notice';
replyTo?: string; // a DMP message id the caller takes part in
blocking?: boolean; // questions only; a client's question always blocks
choices?: string[]; // at most 20, each one line of at most 200 bytes
choice?: string; // answers only; one of the question's choices
refs?: { type: string; id: string }[]; // inbound: at most 50, task and message ids only
urgent?: boolean; // inbound true is refused
wake?: 'none' | 'request'; // inbound 'request' is refused
}
Inbound. A host reads a client's envelope before anything else of the message, and MUST apply these rules on top of §4.5:
from,idandthreadare ignored: the bearer decides who sends, and the host assigns the id and the thread.urgent: trueis refused asforbiddenonurgent, andwake: 'request'asforbiddenonwake: a client may not jump a mailbox or start a session.- An
x-kind is refused asforbiddenonkind, so a client cannot put an uninterpreted kind in front of a human; any other kind outside the list above isinvalidonkind. - A message without a
taskIdstarts a new exchange. A question opens a task; amessageor anoticeopens none, and the host answers it with aMessagerather than aTask. Whenwork/v1names askill(§8.6), the skill decides what the message starts: the envelope'skindstill passes the rules above, but does not decide it. Otherwise the message is never anansweror ahandoff: either kind isinvalidonkind. A client answers a question put to it by continuing the task that asks it, with that task'staskId(§8.2), and proposes work withwork/v1. Of a continuation's envelope onlychoiceandrefscount. - Each
toentry is one line of at most 512 bytes, at most 50 of them; each choice and the choice are one line of at most 200 bytes, at most 20 choices;blockingis a boolean. A violation isinvalidon the field or its slot. - An
a2a:entry is how a peer host names this host (§8.9), so it reads as the owner, once. - A client may address only the humans on its recipient list, which is the owner
unless a deciding principal named others when approving the client, and the
work items of its own approved handoffs. Anything else is
forbiddenonto[i], the same for a member of the host and a stranger, so the answer tells a client nothing about who exists. - Refs name only
taskandmessageids the client may already see: its own tasks and the messages in their scope (§8.7), and the work items of its approved handoffs. A ref that is not an object isinvalidonrefs[i]; one of another type isinvalidonrefs[i].type, one that carries anatonrefs[i].at, and one whoseidis not one line of at most 512 bytes onrefs[i].id. An id the client may not see isnot-foundonrefs[i], as an absent one is. - Without an envelope, the message is a blocking question to the owner.
Vectors: a2a.envelope-ext.accepts-recipients-choices-and-message-refs,
a2a.envelope-ext.accepts-no-envelope, a2a.envelope-ext.refuses-urgent,
a2a.envelope-ext.refuses-a-wake-request,
a2a.envelope-ext.refuses-a-private-use-kind,
a2a.envelope-ext.refuses-an-unknown-kind,
a2a.envelope-ext.refuses-a-bad-recipient,
a2a.envelope-ext.refuses-a-ref-type-other-than-task-or-message,
a2a.envelope-ext.refuses-a-ref-anchor,
a2a.envelope-ext.refuses-more-than-20-choices,
a2a.envelope-ext.refuses-a-choice-over-200-bytes,
a2a.envelope-ext.refuses-blocking-that-is-not-a-boolean,
a2a.external.the-host-sets-id-thread-and-from,
a2a.external.defaults-to-a-blocking-question-to-the-owner,
a2a.external.a-client-reaches-only-the-owner,
a2a.external.an-a2a-recipient-reads-as-the-owner,
a2a.external.an-opening-answer-or-handoff-is-refused,
a2a.external.an-answer-from-a-client-is-never-a-close.
Outbound. A host MUST NOT write to to a client. To a peer it writes only
the peer's own address, so no other recipient leaves the host. Refs are written
as the DMP sender wrote them, as opaque strings.
External data is stored wrapped under this URI, as
{ "https://dispatch.foo/a2a/ext/envelope/v1": <value> }, so it has no
top-level type (§8.8); it is unwrapped when it goes
back to the client that sent it.