Skip to main content

The Agent Mediated Messaging Protocol (AMMP)
draft-rapp-ammp-00

Document Type Active Internet-Draft (individual)
Authors Daniel Rapp , J. Trent Adams
Last updated 2026-10-22
RFC stream (None)
Intended RFC status (None)
Formats
Stream Stream state (No stream defined)
Consensus boilerplate Unknown
RFC Editor Note (None)
IESG IESG state I-D Exists
Telechat date (None)
Responsible AD (None)
Send notices to (None)
draft-rapp-ammp-00
Network Working Group                                            D. Rapp
Internet-Draft                                                  T. Adams
Intended status: Standards Track                        Proofpoint, Inc.
Expires: 24 April 2027                                   21 October 2026

              The Agent Mediated Messaging Protocol (AMMP)
                           draft-rapp-ammp-00

Abstract

   The Agent Mediated Messaging Protocol (AMMP) is a wire contract,
   carried over a filesystem namespace, between an untrusted per-agent
   connector and a trusted, credentialed, store-and-forward messaging
   runtime.  It gives a sandboxed AI agent the ability to send and
   receive messages (email, and under an optional profile, messages to
   other agents) through a runtime that does not trust it: every
   outbound message begins as an inert request file that the runtime
   copies into private staging, validates independently, and applies
   policy to before anything is transmitted, and every inbound message
   is authenticated and screened by the runtime before a notice
   describing it appears in the agent's namespace.

   This document specifies the directory layout and its filesystem
   obligations — ownership, write and delete rights, and the path,
   symlink, and concurrency discipline a runtime observes; the JSON
   message shapes for submit requests, results, deliver notices, spooled
   message bodies, and an optional binding record; the correlation and
   versioning rules, including a direction-aware unknown-member
   tolerance rule; and two conformance classes.  An optional profile
   gives agent-to-agent traffic within one principal its own tree, and
   specifies how a Runtime signs and verifies that traffic when it
   travels between hosts as mail.

   AMMP is deliberately not a mail protocol: it neither defines nor
   replaces SMTP, IMAP, or provider-side infrastructure, and composes
   with them rather than displacing them.  Its single design center is
   that no security property depends on the agent — or on the connector
   speaking for it — behaving correctly.

About This Draft

   This document and the accompanying schemas and conformance fixtures
   are published under the Apache License 2.0.

Discussion Venues

   This note is to be removed before publishing as an RFC.

Rapp & Adams              Expires 24 April 2027                 [Page 1]
Internet-Draft                    AMMP                      October 2026

   Source for this draft, its schemas, its conformance fixtures and an
   issue tracker can be found at https://github.com/proofpoint/amap-
   spec.

Status of This Memo

   This Internet-Draft is submitted in full conformance with the
   provisions of BCP 78 and BCP 79.

   Internet-Drafts are working documents of the Internet Engineering
   Task Force (IETF).  Note that other groups may also distribute
   working documents as Internet-Drafts.  The list of current Internet-
   Drafts is at https://datatracker.ietf.org/drafts/current/.

   Internet-Drafts are draft documents valid for a maximum of six months
   and may be updated, replaced, or obsoleted by other documents at any
   time.  It is inappropriate to use Internet-Drafts as reference
   material or to cite them other than as "work in progress."

   This Internet-Draft will expire on 24 April 2027.

Copyright Notice

   Copyright (c) 2026 IETF Trust and the persons identified as the
   document authors.  All rights reserved.

   This document is subject to BCP 78 and the IETF Trust's Legal
   Provisions Relating to IETF Documents (https://trustee.ietf.org/
   license-info) in effect on the date of publication of this document.
   Please review these documents carefully, as they describe your rights
   and restrictions with respect to this document.  Code Components
   extracted from this document must include Revised BSD License text as
   described in Section 4.e of the Trust Legal Provisions and are
   provided without warranty as described in the Revised BSD License.

Table of Contents

   1.  Introduction — Motivation and Problem Statement . . . . . . .   5
     1.1.  Why the existing options are insufficient . . . . . . . .   6
     1.2.  The approach  . . . . . . . . . . . . . . . . . . . . . .   6
     1.3.  Why a filesystem namespace rather than an API, RPC, or
           queue . . . . . . . . . . . . . . . . . . . . . . . . . .   7
     1.4.  Design goals  . . . . . . . . . . . . . . . . . . . . . .   7
     1.5.  Non-goals . . . . . . . . . . . . . . . . . . . . . . . .   8
     1.6.  Why This Work Is Brought to the IETF  . . . . . . . . . .   8
   2.  Conventions and Terminology . . . . . . . . . . . . . . . . .  10
     2.1.  Terminology . . . . . . . . . . . . . . . . . . . . . . .  10
   3.  Architecture and Trust Model  . . . . . . . . . . . . . . . .  13

Rapp & Adams              Expires 24 April 2027                 [Page 2]
Internet-Draft                    AMMP                      October 2026

     3.1.  Actors  . . . . . . . . . . . . . . . . . . . . . . . . .  13
     3.2.  The load-bearing invariant  . . . . . . . . . . . . . . .  14
     3.3.  Why a filesystem namespace  . . . . . . . . . . . . . . .  14
     3.4.  Relationship to Other Agent Protocols (Informative) . . .  15
     3.5.  Runtimes Built from Several Components (Informative)  . .  16
   4.  Volume Layout . . . . . . . . . . . . . . . . . . . . . . . .  17
     4.1.  Obligations . . . . . . . . . . . . . . . . . . . . . . .  18
     4.2.  Deletion rights . . . . . . . . . . . . . . . . . . . . .  24
   5.  Outbound: Submit Request  . . . . . . . . . . . . . . . . . .  27
     5.1.  Field semantics and obligations . . . . . . . . . . . . .  28
   6.  Outbound: Result  . . . . . . . . . . . . . . . . . . . . . .  30
     6.1.  Field semantics and obligations . . . . . . . . . . . . .  30
   7.  Inbound: Deliver Notice and Inbound Message . . . . . . . . .  32
     7.1.  Field semantics and obligations . . . . . . . . . . . . .  33
     7.2.  Inbound attachment Descriptors  . . . . . . . . . . . . .  37
     7.3.  The Inbound Message (body spool)  . . . . . . . . . . . .  38
   8.  Task Correlation  . . . . . . . . . . . . . . . . . . . . . .  40
   9.  Binding Record  . . . . . . . . . . . . . . . . . . . . . . .  40
     9.1.  Field semantics and obligations . . . . . . . . . . . . .  41
   10. The Peer Directory  . . . . . . . . . . . . . . . . . . . . .  41
   11. The Fleet Roster  . . . . . . . . . . . . . . . . . . . . . .  44
   12. The Peer-Origin Profile (Optional)  . . . . . . . . . . . . .  45
     12.1.  Scope  . . . . . . . . . . . . . . . . . . . . . . . . .  45
     12.2.  The Peer Tree  . . . . . . . . . . . . . . . . . . . . .  46
     12.3.  Origin Class Follows the Tree  . . . . . . . . . . . . .  47
     12.4.  Addressing . . . . . . . . . . . . . . . . . . . . . . .  49
     12.5.  Correlation and Replies  . . . . . . . . . . . . . . . .  50
     12.6.  Peer Traffic Between Hosts . . . . . . . . . . . . . . .  53
       12.6.1.  The Statement  . . . . . . . . . . . . . . . . . . .  53
       12.6.2.  Carriage, and the Candidate the Verifier Sees  . . .  54
       12.6.3.  Verification . . . . . . . . . . . . . . . . . . . .  57
       12.6.4.  Outcomes . . . . . . . . . . . . . . . . . . . . . .  59
       12.6.5.  The Replay Store, Windows and Recovery . . . . . . .  60
       12.6.6.  Correlation and Edges Between Hosts  . . . . . . . .  63
       12.6.7.  Keys . . . . . . . . . . . . . . . . . . . . . . . .  64
       12.6.8.  Control Notices  . . . . . . . . . . . . . . . . . .  66
       12.6.9.  Optional: Sender Age, and Deployment Admission
               Policy  . . . . . . . . . . . . . . . . . . . . . . .  67
       12.6.10. The Intake Location  . . . . . . . . . . . . . . . .  68
     12.7.  Sender Exposure  . . . . . . . . . . . . . . . . . . . .  69
     12.8.  Delivery Outcomes, Retention, Deletion and
            Presentation . . . . . . . . . . . . . . . . . . . . . .  70
   13. Versioning and Conformance  . . . . . . . . . . . . . . . . .  71
     13.1.  Versioning . . . . . . . . . . . . . . . . . . . . . . .  71
     13.2.  Required versus optional . . . . . . . . . . . . . . . .  74
     13.3.  Conformance  . . . . . . . . . . . . . . . . . . . . . .  77
   14. Security Considerations . . . . . . . . . . . . . . . . . . .  80
     14.1.  Path authority and traversal . . . . . . . . . . . . . .  81

Rapp & Adams              Expires 24 April 2027                 [Page 3]
Internet-Draft                    AMMP                      October 2026

     14.2.  Symlinks, descriptors, and open discipline . . . . . . .  81
     14.3.  Time-of-check to time-of-use, and ingest by copy . . . .  82
     14.4.  Concurrency  . . . . . . . . . . . . . . . . . . . . . .  83
     14.5.  Attribution spoofing . . . . . . . . . . . . . . . . . .  83
     14.6.  Untrusted content  . . . . . . . . . . . . . . . . . . .  83
     14.7.  Recipient policy and header injection  . . . . . . . . .  84
     14.8.  The outbound digest is not a trust boundary  . . . . . .  85
     14.9.  Confirmation-oracle avoidance  . . . . . . . . . . . . .  85
     14.10. Byte-absence and artifact-absence as the access gate . .  85
     14.11. Fail-closed version handling, and the cost of
             tolerance . . . . . . . . . . . . . . . . . . . . . . .  86
     14.12. Inbound integrity and the writability of the inbound
             tree  . . . . . . . . . . . . . . . . . . . . . . . . .  87
     14.13. Retention, deletion, and lost verdicts . . . . . . . . .  87
     14.14. What is wire-verifiable, and what is a Runtime
             obligation  . . . . . . . . . . . . . . . . . . . . . .  87
     14.15. The extension space, and outcomes as claims  . . . . . .  89
     14.16. Special-use domain names . . . . . . . . . . . . . . . .  89
     14.17. The Peer-Origin Profile  . . . . . . . . . . . . . . . .  89
   15. IANA Considerations . . . . . . . . . . . . . . . . . . . . .  91
     15.1.  Message Header Fields  . . . . . . . . . . . . . . . . .  91
     15.2.  Possible Future Registries . . . . . . . . . . . . . . .  91
   16. References  . . . . . . . . . . . . . . . . . . . . . . . . .  91
     16.1.  Normative References . . . . . . . . . . . . . . . . . .  91
     16.2.  Informative References . . . . . . . . . . . . . . . . .  93
   Appendix A.  Submit-Request Schema  . . . . . . . . . . . . . . .  94
   Appendix B.  Result Schema  . . . . . . . . . . . . . . . . . . .  96
   Appendix C.  Deliver-Notice Schema  . . . . . . . . . . . . . . .  97
   Appendix D.  Inbound-Message Schema . . . . . . . . . . . . . . .  99
   Appendix E.  Binding-Record Schema  . . . . . . . . . . . . . . . 101
   Appendix F.  Peer-Origin Profile Schemas  . . . . . . . . . . . . 102
   Appendix G.  Peer-Directory Schema  . . . . . . . . . . . . . . . 115
   Appendix H.  Fleet-Roster Schema  . . . . . . . . . . . . . . . . 117
   Appendix I.  Golden Examples  . . . . . . . . . . . . . . . . . . 119
     I.1.  Valid Submit Request with attachments . . . . . . . . . . 119
     I.2.  Valid Deliver Notice with a clean attachment
           Descriptor  . . . . . . . . . . . . . . . . . . . . . . . 119
     I.3.  Valid Inbound Message . . . . . . . . . . . . . . . . . . 120
     I.4.  Valid Result — a policy hold with per-attachment
           diagnostics . . . . . . . . . . . . . . . . . . . . . . . 121
     I.5.  Invalid Deliver Notice — a content_ref that fails the
           binding . . . . . . . . . . . . . . . . . . . . . . . . . 121
     I.6.  Valid Deliver Notice carrying an unrecognized member  . . 122
     I.7.  Invalid Submit Request carrying an unrecognized member  . 122
     I.8.  Invalid Peer Notice — a deliver-kind document under the
           peer tree . . . . . . . . . . . . . . . . . . . . . . . . 123
   Appendix J.  Conformance Suite  . . . . . . . . . . . . . . . . . 123

Rapp & Adams              Expires 24 April 2027                 [Page 4]
Internet-Draft                    AMMP                      October 2026

   Appendix K.  A Deferred Seam-Visible "Handled" Signal
           (Non-Normative) . . . . . . . . . . . . . . . . . . . . . 135
   Appendix L.  Historical Background  . . . . . . . . . . . . . . . 137
     L.1.  draft-rapp-ammp-00 — brought to the IETF (contract
           3.1.0)  . . . . . . . . . . . . . . . . . . . . . . . . . 137
     L.2.  How the versioning rules came about . . . . . . . . . . . 138
     L.3.  3.1.0 — the protocol is AMAP; the outbound extension space;
           the peer-origin profile . . . . . . . . . . . . . . . . . 140
     L.4.  3.0.0 — direction-aware tolerance, and six closed safety
           holes . . . . . . . . . . . . . . . . . . . . . . . . . . 143
     L.5.  2.3.0 — inbound byte access in force  . . . . . . . . . . 146
     L.6.  2.2.0 — deletion rights, read-only inbound  . . . . . . . 146
     L.7.  2.1.0 — the body spool, and three optional Result
           members . . . . . . . . . . . . . . . . . . . . . . . . . 146
     L.8.  2.0.0 — attachments, and contract_version required  . . . 147
     L.9.  Fixture identifier normalization  . . . . . . . . . . . . 147
   Authors' Addresses  . . . . . . . . . . . . . . . . . . . . . . . 147

1.  Introduction — Motivation and Problem Statement

   AI agents increasingly need to correspond, by email above all.  They
   are tasked over it, they return results over it, and they correspond
   over it with people and with systems that speak no other protocol.
   An agent is nonetheless an untrusted process: it can be prompt-
   injected by the very content it is asked to read, it can be
   defective, and it can be compromised outright.  Granting such a
   process email capability directly is dangerous along three concrete
   axes.

   *Inbound.* Delivered mail is untrusted content routed directly into
   the agent's reasoning.  A message body, a subject line, a sender
   display name, or an attachment filename can carry instructions the
   agent was never meant to follow.  The delivery path is therefore an
   injection vector by construction, and no amount of care inside the
   agent removes that property.

   *Outbound.* An agent that can send mail can exfiltrate anything it
   has read, to any address it chooses, and can do so under an identity
   that recipients associate with its operator.  Outbound mail is
   simultaneously a data-loss channel and an impersonation channel.

   *Credentials and identity.* Any mailbox credential, API token, or
   send endpoint held inside the agent's sandbox can be read, copied, or
   abused by whatever compromises the agent.  Worse, an agent holding a
   credential has no identity of its own that a recipient — or its own
   operator — can verify independently of that credential.

Rapp & Adams              Expires 24 April 2027                 [Page 5]
Internet-Draft                    AMMP                      October 2026

1.1.  Why the existing options are insufficient

   Handing the agent mailbox credentials, or a mail-send API, makes the
   agent a _trusted_ participant in the mail system.  That is precisely
   the wrong posture for a component whose defining risk is that it may
   be compromised: the trust must be withdrawn exactly when it is least
   possible to detect that it should be.  Wrapping the credential in a
   helper process that the agent calls does not change the analysis by
   itself, because a callable send capability is still a capability the
   agent holds; what matters is whether the capability crosses the
   boundary at all.

   The common alternative is per-deployment glue: a bespoke script or
   service wired between one agent harness and one mail system.  Such
   glue may even be correct, but it produces no reviewable contract, no
   interoperability, and no shared conformance bar.  Nothing tells a
   second implementer what the first one guarantees, and nothing tells
   either of them what a third-party reviewer should check.

   Adjacent agent protocols do not fill the gap, because they are
   solving a different problem: integration and interoperation _among
   credentialed participants_, rather than isolation of an _untrusted_
   one.  Section 3.4 positions AMMP against that landscape in detail.

1.2.  The approach

   The premise is isolation.  The agent runs in a sandbox, and the only
   path from that sandbox to its correspondence is the one this document
   specifies.  Isolation is what makes an agent safe to connect, and it
   is also what makes it operable: a process that is contained can be
   replaced, replayed and audited as a unit.  This document specifies
   what crosses the boundary, not the sandbox itself.

   AMMP interposes a *trusted runtime* between the agent and the systems
   that carry its messages, holding each message before forwarding it.
   The runtime holds every credential, owns the agent's sending
   identity, and makes every policy decision.  The agent interacts with
   the runtime only through a channel that carries *no capability*: it
   writes an inert request file into a directory, and the runtime —
   independently, on its own schedule, over its own private copy of
   those bytes — decides what, if anything, happens next.  In the other
   direction the runtime authenticates and screens each message before
   writing a notice describing it into the agent's namespace, and
   withholds the bytes of anything it will not vouch for.

   The payoff, which the rest of this document formalizes, is the load-
   bearing invariant:

Rapp & Adams              Expires 24 April 2027                 [Page 6]
Internet-Draft                    AMMP                      October 2026

      No security property depends on the agent — or on the connector
      speaking for it — behaving correctly.  Enforcement is entirely
      runtime-side.  A compromised agent or connector can at most write
      a well-formed request that the runtime then refuses.

   AMMP is the wire contract at exactly that trust boundary, and is
   owned by neither side of it.  That ownership choice is what allows a
   runtime and a connector built by different parties, on different
   schedules, to interoperate and to co-evolve against a versioned
   contract with an offline conformance suite.

1.3.  Why a filesystem namespace rather than an API, RPC, or queue

   The medium _is_ the boundary.  An API, an RPC endpoint, or a message
   queue is a live capability: something the agent invokes, whose
   invocation is itself the grant.  Its security therefore rests on
   authenticating and authorizing each call, on the client library
   behaving, and on the transport being what it claims to be.  A file
   written into a directory grants nothing at all.  It has no callee, no
   session, and no side effect until a separate, trusted process chooses
   to read it — at a time of that process's choosing, into memory of its
   own, under validation of its own.  The runtime is free to ignore the
   file, to refuse it, to hold it for human review, or to consume it and
   answer with a verdict.  Section 3.3 develops this point, along with
   the obligations it puts on the runtime in exchange.

1.4.  Design goals

   *  *Capability-free isolation.* The channel available to an untrusted
      agent grants no send capability, no credential, and no reach
      outside its own namespace.

   *  *Vendor-neutral interoperability.* A runtime and a connector built
      independently interoperate because both are built against this
      contract rather than against each other.

   *  *Versioned co-evolution with offline conformance.* Either side
      proves conformance against a fixture suite with no counterpart
      present, and version changes are deliberate, legible, and fail
      closed rather than silent.

   *  *Composition with real email.* Interactions remain federated, of-
      record, and human-inclusive, rather than confined to a closed
      agent-only channel.

   *  *Portability across agent runtimes.* Nothing in the contract
      presumes a particular agent harness, language, or tool interface.

Rapp & Adams              Expires 24 April 2027                 [Page 7]
Internet-Draft                    AMMP                      October 2026

1.5.  Non-goals

   *  *AMMP is not a mail transport.* It neither defines nor replaces
      SMTP, IMAP, or any provider-side mail infrastructure.  It
      describes what a runtime and a connector exchange _locally_;
      whatever the runtime does with a mail provider is out of scope.

   *  *AMMP is not agent-to-agent RPC.* The core contract is local to
      one deployment's filesystem, between two components on either side
      of a single trust boundary.  The optional peer-origin profile
      (Section 12) lets Agents of one principal write to one another,
      but as messages: between hosts they travel as ordinary mail,
      signed by the sending Runtime.  The profile defines no call or
      return semantics, no discovery mechanism, and no transport of its
      own.

   *  *AMMP is not a policy language.* What a runtime's policy decides —
      which recipients are permitted, which content is held for review,
      how a sender's standing is computed — is deliberately out of
      scope.  The wire defines only the shapes of requests and verdicts,
      never the computation that produces them.

   Every obligation in this document is grounded in what the contract
   actually provides.  Where a capability is sketched but not in force,
   it is marked as such (Appendix K), and where a property is a runtime
   obligation rather than a wire-verifiable fact, this document says so
   (Section 14.14).

1.6.  Why This Work Is Brought to the IETF

   This section is to be removed before publishing as an RFC.

   AMMP began as an implementation: a Runtime and Connectors built
   separately against a shared contract, with an offline conformance
   suite so that each side could be tested without the other.  It has
   been exercised in deployments of several cooperating Agents from more
   than one vendor, but every deployment so far has run under a single
   operator.  The authors bring it to the IETF for the things that
   implementation alone cannot supply:

   *  *Review.* The design rests on a security argument (Section 3.2),
      and that argument needs review by people who did not make it,
      particularly from the mail, security and identity communities.

   *  *Interoperation.* A contract owned by neither side is useful only
      if independent implementers build both sides.  The authors are
      looking for implementers and for interoperation testing across
      organizations.

Rapp & Adams              Expires 24 April 2027                 [Page 8]
Internet-Draft                    AMMP                      October 2026

   *  *Collaborators.* The work should be shaped by more than one
      organization, and by the people who operate mail at scale.

   *  *Alignment with related work.* AMMP deliberately adds no new
      transport and no new identity scheme: email stays SMTP, and how an
      Agent's identity is established is left to work designed for that
      purpose.  Where AMMP touches such work, it should follow it rather
      than invent its own.

   Much of this document records why a rule is the way it is, because
   the reasoning is what a reviewer needs in order to disagree with it.
   None of it is offered as settled.  The questions below are ones the
   authors already know are open; others will surely be raised, and
   every one of them deserves a fresh look now that more people are
   reading the work.

   *  *The wire version.* The wire version starts at "2" rather than
      "1", because implementations already emit and check "2"
      (Section 13.1).  Whether to reset it is a fair question.

   *  *Registries.* Whether the wire version, the Result reason_code
      vocabulary, or the Control Notice's outcome and cause vocabularies
      should be governed by IANA (Section 15).

   *  *The name of the attestation member.* In this document it is the
      identity stamp a Runtime applied to an outbound message
      (Section 6.1).  In IETF usage, attestation usually means remote
      attestation of platform state, and the member may need another
      name.

   *  *Identity between Runtimes.* The peer-origin profile signs traffic
      between hosts with Ed25519 keys configured per Runtime
      (Section 12.6.7).  Whether that identity should instead be carried
      by an existing mechanism is open, and the candidates differ in
      what they would replace: a workload-identity system such as SPIFFE
      would replace the configured keys (distribution and attestation),
      while a standard signed envelope such as JWS with EdDSA (RFC 7515,
      RFC 8037) or COSE (RFC 9052) would replace the statement's own
      encoding, which is close to a detached JWS already.  DKIM and ARC
      sign on behalf of a mail domain rather than a Runtime, so they
      neither bind a message to the issuing Runtime nor carry the
      statement's members.

   *  *Document structure.* Whether the peer-origin profile belongs in
      this document or in a companion document.

Rapp & Adams              Expires 24 April 2027                 [Page 9]
Internet-Draft                    AMMP                      October 2026

   *  *Internationalized addresses.* Addresses are restricted to a
      narrow ASCII addr-spec (Section 5.1), and internationalized
      addresses are out of scope for now.

   *  *A consumption signal.* Whether a Connector should be able to tell
      its Runtime that a Notice has been handled (Appendix K).

2.  Conventions and Terminology

   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
   "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
   "OPTIONAL" in this document are to be interpreted as described in BCP
   14 [RFC2119] [RFC8174] when, and only when, they appear in all
   capitals, as shown here.

   Every message defined by this document is a JSON text as defined by
   [RFC8259], encoded in UTF-8 [RFC3629].  The schemas in Appendix A
   through Appendix H are written in the JSON Schema 2020-12 dialect
   [JSON-SCHEMA-2020-12].

   Long lines in the JSON examples and schemas in this document are
   folded using the single-backslash strategy of [RFC8792].  Each folded
   block begins with that strategy's header line, which is part of the
   folded content: it is what allows the original to be reconstituted
   mechanically, so it is kept inside the block.

   Sections and subsections explicitly marked _(informative)_ or _(non-
   normative)_ carry no requirements; Section 3.4, Section 3.5,
   Appendix K, and Appendix L are the only such parts of this document.

2.1.  Terminology

   *  *Admitting Component* — in the peer-origin profile, a Runtime
      component that admits messages from a transport not restricted to
      authorized peer senders; a mail deliverer is the usual example.
      It does not hold write authority over the peer tree
      (Section 12.3).

   *  *Agent* — the untrusted process whose messaging capability this
      protocol mediates: email, and under the peer-origin profile,
      messages to other Agents.  It holds no credential, has no send
      path of its own, and reaches the protocol only through its
      Connector.  Nothing in this document places a requirement on the
      Agent, because no security property may depend on it.

   *  *Binding Record* — the optional, informational artifact at the
      Volume root naming which mailbox the Volume is bound to
      (Section 9).

Rapp & Adams              Expires 24 April 2027                [Page 10]
Internet-Draft                    AMMP                      October 2026

   *  *Connector* — the per-Agent component that speaks the Agent-side
      half of this contract: it writes Submit Requests, reads Results
      and Notices, and surfaces them to its Agent in whatever form its
      harness uses.  It is semi-trusted at best; for the purpose of
      every security property in this document it is treated as
      untrusted, and the requirements placed on it are well-formedness
      and non-crashing obligations, never obligations another party's
      safety rests on.

   *  *Control Notice* — in the peer-origin profile, the signed report a
      receiving Runtime sends to a configured sending Runtime about a
      Peer Statement it did not place (Section 12.6.8).

   *  *Descriptor* — one entry in an attachments[] array, describing an
      attachment (its display filename, media type, size, and —
      depending on direction — digest, Disposition, or content
      reference).  A Descriptor is metadata, not a path.

   *  *Disposition* — the Runtime's assertion about an inbound
      attachment's handling: clean, stripped, quarantined, or unscanned
      (Section 7.2).

   *  *Drop* — to discard an artifact without surfacing it.  Where this
      document says a consumer drops an artifact, the consumer MUST NOT
      surface the artifact to its Agent and MUST NOT act on it.  A drop
      is a consumer's decision about one artifact; it is unrelated to
      the outbound tree, into which a Connector writes requests.

   *  *Fail Closed* — to refuse to process an artifact rather than
      proceed on a partial or guessed interpretation of it.

   *  *Inbound Message* — the Runtime-authored artifact carrying a
      delivered message's body text and headers, spooled under a
      Notice's identifier and fetched on demand (Section 7.3).

   *  *Lane* — one class of traffic between an Agent and its Runtime, or
      between two Agents: the mail lane (inbound/ and outbound mail)
      and, under the peer-origin profile, the peer lane (peer/).  The
      Peer Directory states, per peer, which lanes are open.

   *  *Namespace* — the per-Agent subtree <volume-root>/<agent-id>/,
      mounted into that Agent's sandbox.  It is the unit of isolation
      and the sole authority for attributing an artifact to an Agent.

   *  *Notice* (Deliver Notice) — the Runtime-authored artifact
      announcing that a message was delivered to the Agent's mailbox
      (Section 7).  It is a pointer and summary, not the message.

Rapp & Adams              Expires 24 April 2027                [Page 11]
Internet-Draft                    AMMP                      October 2026

   *  *Ordinal* — the integer naming a Sidecar blob.  It equals the
      index of the corresponding Descriptor in its array, and is the
      sole binding authority between a Descriptor and its bytes.

   *  *Origin Class* — the class of origin a consumer assigns to an
      inbound document, derived only from the Tree it was read from:
      mail for inbound/, and same-principal peer origin for peer/
      (Section 4.1, Section 12.3).

   *  *Peer Notice* — the Runtime-authored artifact, in the peer-origin
      profile, announcing a message from a same-principal Agent; the
      Deliver Notice shape with kind "peer", placed under peer/notices/
      (Section 12.2).

   *  *Peer Statement* — the signed JSON object a sending Runtime
      attaches, in an AMAP-Peer-Statement header field, to peer traffic
      it sends to another host (Section 12.6.1).

   *  *Result* — the Runtime-authored verdict for one consumed Submit
      Request (Section 6).

   *  *Runtime* — the trusted, credentialed, Agent-agnostic, store-and-
      forward messaging component that speaks the other half: it holds
      the messaging credentials, owns each Agent's identity, makes every
      policy decision, and is the only party that transmits or receives
      messages.  It holds each message before forwarding it: an outbound
      message is copied into private staging and checked before anything
      is transmitted, and an inbound message is screened before a Notice
      describing it appears.

   *  *Sandbox* — the isolation boundary within which an Agent and its
      Connector run.  The Runtime mounts the Agent's Namespace into it
      and nothing else of the Volume; no credential, send path or other
      messaging capability exists inside it.  This document does not
      specify the sandbox mechanism: it presumes one, and every property
      it states holds only as well as the Sandbox holds.

   *  *Sidecar* — attachment bytes stored beside a Submit Request or a
      Notice, in a directory named after that artifact, with each blob
      named by a 0-based ordinal.

   *  *Staging* — Runtime-private storage outside the Volume,
      unreachable from any Agent's mount, into which the Runtime copies
      Agent-authored bytes before acting on them (Section 4.1).

   *  *Submit Request* — the Connector-authored artifact requesting that
      a message be sent (Section 5).  It is inert: it carries no
      capability and effects nothing by existing.

Rapp & Adams              Expires 24 April 2027                [Page 12]
Internet-Draft                    AMMP                      October 2026

   *  *Tree* — one of the Runtime-owned inbound-class subtrees of a
      Namespace: inbound/, and, under the peer-origin profile, peer/.
      Each Tree is an Origin Class.

   *  *Volume* — the runtime-owned filesystem tree carrying this
      protocol, containing one Namespace per Agent plus an optional
      Binding Record at its root.  Its mount path is a deployment
      choice; this document writes <volume-root>/ for it and nothing in
      the protocol derives from its name.  No implementation may depend
      on that name or any other.

3.  Architecture and Trust Model

3.1.  Actors

    +=============+===============+====================+==============+
    | Actor       | Trust         | Holds              | Reaches      |
    +=============+===============+====================+==============+
    | *Runtime*   | trusted       | messaging          | the mail     |
    | (host-side) |               | credentials, Agent | provider,    |
    |             |               | identity, policy,  | the Volume   |
    |             |               | the record of what |              |
    |             |               | was sent           |              |
    +-------------+---------------+--------------------+--------------+
    | *Connector* | semi-trusted, | nothing            | its own      |
    | (per Agent) | Agent-side    | credentialed       | Namespace on |
    |             |               |                    | the Volume;  |
    |             |               |                    | its Agent's  |
    |             |               |                    | input and    |
    |             |               |                    | output       |
    +-------------+---------------+--------------------+--------------+
    | *Agent*     | *untrusted*   | only what the      | the          |
    |             |               | Connector surfaces | Connector;   |
    |             |               |                    | its sandbox  |
    +-------------+---------------+--------------------+--------------+

                                  Table 1

   The Runtime is trusted by construction: it is the party holding the
   credentials, and a compromise of it is a compromise of the messaging
   capability itself.  The Connector is the interesting case.  It runs
   Agent-side, is reachable by whatever compromises the Agent, and is
   therefore treated as untrusted for every property that protects the
   Runtime, other Agents, or the outside world.  Requirements this
   document places on a Connector fall into two distinct classes, and
   the difference matters when reading them:

Rapp & Adams              Expires 24 April 2027                [Page 13]
Internet-Draft                    AMMP                      October 2026

   *  *Well-formedness and liveness obligations* — emit valid documents,
      tolerate a missing artifact, do not crash on hostile input.  A
      Connector that violates one harms itself and its own Agent, not
      any other party.

   *  *Obligations that protect an Agent from its own inbox* — the
      untrusted-content posture of Section 7.  These are necessarily
      Connector-implemented, because the Connector is the only component
      on the Agent's side of the boundary, and they are stated as such
      rather than as properties the Runtime relies on.

   No requirement in this document places a Runtime-side, other-Agent-
   side, or outside-world safety property in a Connector's hands.  The
   Connector's obligation not to rewrite a published Submit Request
   might appear to be one; Section 4.1's ingest-by-copy rule removes
   that dependency, leaving the obligation as pure well-formedness.

3.2.  The load-bearing invariant

      *Invariant.* No security property may depend on the Agent or the
      Connector behaving.  Enforcement is entirely Runtime-side.  A
      compromised Agent or Connector can at most write a well-formed
      request that the Runtime then refuses.

   Everything else in this document is downstream of that sentence.  It
   is why the Agent's declared identity is a cross-check rather than an
   attribution source (Section 4.1); why the Runtime re-derives every
   path from data it controls rather than from a file's contents
   (Section 4.1); why recipient policy is evaluated Runtime-side over
   the exact recipient set that will be transmitted to (Section 5.1);
   why the digest an Agent supplies for its own attachment bytes is an
   integrity aid and not a trust boundary (Section 5.1); and why the
   absence of bytes, rather than a flag inside a document, is the access
   gate on inbound attachments (Section 7.2).

   The invariant also bounds what this document can promise.  A wire
   contract cannot make a Runtime honest, and a fixture suite cannot
   observe a filesystem.  Section 14.14 states which properties are
   wire-verifiable and which are Runtime obligations that a green
   conformance run does not prove.

3.3.  Why a filesystem namespace

   The Runtime mounts each Agent's Namespace into that Agent's sandbox,
   and the Agent's entire protocol surface is that directory.  This is a
   deliberate choice of medium, with three consequences.

Rapp & Adams              Expires 24 April 2027                [Page 14]
Internet-Draft                    AMMP                      October 2026

   *Writing a file carries no capability.* It invokes nothing.  There is
   no callee to authenticate, no session to hijack, no method whose mere
   reachability is a grant.  The request exists as bytes until a trusted
   process elects to look at them.  Compare an API or queue, where the
   act of enqueuing is itself the privileged act and every security
   property depends on the authorization check in front of it being
   correct and unbypassable.

   *Isolation is structural rather than enforced by policy.* A Connector
   scoped to <volume-root>/<agent-id>/ cannot name another Agent's
   Namespace at all.  Attribution follows from _where a file appeared_,
   not from what the file says about itself, so spoofing attribution
   requires defeating the mount rather than lying in a JSON member.

   *The medium's costs are real and are paid explicitly.* A shared
   filesystem between a trusted and an untrusted party is itself an
   attack surface: symlinks, directory swaps, special files, hard links,
   and unbounded sizes are all available to the untrusted side, and
   every path-based check the trusted side performs is racy against a
   concurrent rename.  This document therefore does not treat "it is
   just a directory" as a simplification.  Section 4.1 states the path,
   open, copy, and concurrency discipline a Runtime observes in exchange
   for the medium's advantages, and Section 14 revisits each as a
   threat.

3.4.  Relationship to Other Agent Protocols (Informative)

   _This subsection is informative.  It contains no requirements and
   creates no dependency on any external protocol.  Nothing here claims
   that AMMP replaces, subsumes, or competes with any protocol named
   below._

   AMMP occupies a narrow position: it is the local seam at a single
   trust boundary, where one side holds every credential and the other
   holds none.  The adjacent protocols in the agent ecosystem sit at
   different layers, and the common question of "why not use one of them
   here" has a specific answer in each case.

   *Model Context Protocol (MCP)* [MCP] connects an agent host to tools
   and context through JSON-RPC servers.  It is an integration
   interface: live capabilities are exposed across it and exercised by
   the agent through it.  That makes MCP complementary to AMMP rather
   than an alternative.  A Connector often surfaces AMMP's operations to
   its Agent through MCP, or through whatever tool interface its harness
   uses natively; AMMP is the capability-free seam sitting behind that
   surface.  The contrast worth stating is exactly the one that
   motivates the two coexisting: an MCP-style interface grants live
   capability across the boundary it spans, while AMMP's channel

Rapp & Adams              Expires 24 April 2027                [Page 15]
Internet-Draft                    AMMP                      October 2026

   deliberately grants none — the Agent-side surface can be as rich as
   the harness likes, because what crosses the trust boundary underneath
   it is an inert file.  MCP originated at Anthropic.

   *Agent2Agent (A2A)* [A2A] is networked agent-to-agent RPC between
   mutually authenticated services, with agent cards, tasks, and
   HTTP/SSE/JSON-RPC transport.  It presumes both endpoints are
   credentialed participants that can authenticate to each other.
   AMMP's design center is the opposite: an untrusted agent that holds
   no credentials at all, with enforcement placed entirely on the
   trusted side of a local boundary.  The two are orthogonal, and
   compose without friction — an agent that speaks A2A to its peers can
   use AMMP for its mail capability, and neither protocol needs to know
   about the other.  A2A originated at Google and is hosted by the Linux
   Foundation.

   *The two protocols abbreviated "ACP" are not the same protocol*, and
   the collision is worth disambiguating in a document a reviewer may
   arrive at with one of them in mind.

   *  The *Agent Communication Protocol* (IBM/BeeAI) is of historical
      interest here: it merged into A2A under the Linux Foundation,
      announced in August 2025, with its repository archived
      [ACP-MERGER].

   *  The *Agent Client Protocol* [ACP-CLIENT] is a distinct and
      currently active protocol at a different layer — an interface
      between a host or editor and an agent — originated by Zed
      Industries.  It is unrelated to the merged Agent Communication
      Protocol and unrelated to AMMP.

3.5.  Runtimes Built from Several Components (Informative)

   _This subsection is informative.  It contains no requirements._

   The Runtime is defined by its role, not as a single process.  A
   Runtime may be built from several trusted components: for example,
   one that drains Agent Namespaces and applies policy, and another that
   holds the mail credentials and talks to the provider.  Every
   obligation this document places on the Runtime binds the composite as
   seen from the Agent's Namespace.  How the components divide the work,
   and how they communicate, is Runtime mechanism and out of scope.
   They may communicate by any means, for example SMTP [RFC5321] between
   hosts or LMTP [RFC2033] within one; this document specifies only the
   Agent boundary.

   Some existing obligations are easy to lose when the work is divided:

Rapp & Adams              Expires 24 April 2027                [Page 16]
Internet-Draft                    AMMP                      October 2026

   *  exactly one Result per consumed request, including when an inner
      component fails or never answers (Section 6.1);

   *  accepted only once the provider has accepted the message for relay
      (Section 6.1);

   *  the recipient set handed to the provider is exactly the set on
      which recipient policy was evaluated (Section 5);

   *  nothing quarantined or unverified becomes a Notice (Section 7),
      and clean is asserted only for bytes the publishing component has
      verified, with every content_ref bound to the Notice it sits in
      (Section 7.2);

   *  attribution is carried between components by Runtime
      configuration, never by anything the Agent wrote (Section 4.1);
      and

   *  only the component facing the Agent's Namespace writes the Agent's
      lanes.

4.  Volume Layout

   The Runtime owns the Volume and mounts each Agent's Namespace into
   that Agent's sandbox.  A Connector has access *only* within its own
   <agent-id>/, and only to the paths below.  Within that Namespace,
   write access may be narrower still: the inbound/ subtree is Runtime-
   owned and MAY be mounted read-only.

   The Volume root's own name is a *deployment choice and is not part of
   this protocol* — nothing derives from it, and no implementation may
   depend on it. <volume-root>/ below stands for whatever a deployment
   calls it.

Rapp & Adams              Expires 24 April 2027                [Page 17]
Internet-Draft                    AMMP                      October 2026

   <volume-root>/
     identity.json           # runtime/provisioner: Binding Record (S9)
     <agent-id>/
       outbound/
         req-<id>.json          # agent -> runtime: Submit Request
         req-<id>.attachments/  # agent -> runtime: Sidecar bytes,
                                #   ordinals 0 1 2 ...
         results/<id>.json      # runtime -> agent: verdict for req <id>
         processed/req-<id>.json  # runtime: consumed requests
         ext/<name>/            # connector-owned side channel a
                                #   runtime MAY read by out-of-band
                                #   agreement; <name> is a STABLE,
                                #   opaque connector id
       inbound/
         notices/notice-<id>.json  # runtime -> agent: delivered event
         notices/<notice-id>.attachments/  # bytes asserted clean,
                                           #   ordinals 0 1 2 ... (S7.2)
         notices/processed/     # OPTIONAL - connector archive,
                                #   writable-inbound deployments only
         messages/notice-<id>.json  # runtime -> agent: full message
         cursor.json            # runtime: inbound poll cursor
       peer/                    # OPTIONAL - peer-origin profile:
                                #   same layout as inbound/
       directory.json           # OPTIONAL - runtime: Peer Directory
       audit/
         log.jsonl              # runtime: append-only action log

           Figure 1: Volume layout (illustrative — not a fixture)

   There is exactly one inbound Sidecar directory — inbound/
   notices/<notice-id>.attachments/ — and both a Deliver Notice's and an
   Inbound Message's content_ref resolve there.  No second, message-
   specific Sidecar directory exists.

4.1.  Obligations

   *Attribution by Namespace.* <agent-id> is assigned by the Runtime,
   not declared by the Agent.  Attribution is _the Namespace a file
   arrived in_; an agent_id member inside the file is a cross-check
   only.  A Runtime MUST reject a Submit Request whose agent_id, when
   present, does not match the Namespace it was found in, and MUST NOT
   use that member to attribute the request.  A Runtime MUST know the
   <agent-id> of every Namespace it serves: from its own configuration
   or, where its own layout carries it, from the Namespace's path, and
   MUST NOT take it from anything the Agent writes.  A Runtime that
   cannot name the Namespace in which it found a Submit Request MUST
   reject that request rather than accept its agent_id unchecked.

Rapp & Adams              Expires 24 April 2027                [Page 18]
Internet-Draft                    AMMP                      October 2026

   *Isolation is structural.* A Connector scoped to <agent-id>/ cannot
   read another Agent's drafts or Notices.

   *All writes are atomic.* Every artifact in the tree above is
   published by writing a .tmp sibling and then renaming it into place
   (os.replace or the equivalent).  Readers MUST ignore *.tmp entries.

   *Single writer, single drainer.* At any moment a Namespace has
   exactly one Connector instance writing outbound/ and exactly one
   Runtime instance draining it.  Every concurrency-sensitive rule in
   this document — the atomic .tmp-then-rename discipline above, the
   commit sentinel below, and the derivation of req_id from max(pending,
   processed, results) + 1 (Section 5.1) — is sound only under that
   assumption and provides no mutual exclusion of its own.  Each side
   MUST ensure single-instance operation over a Namespace by a mechanism
   outside this protocol, such as an OS-level lock held for the process
   lifetime.  A Runtime MUST NOT run two concurrent drains of one
   Namespace, and a Connector MUST NOT run two concurrent writers into
   one.  Behavior under violation is unspecified: duplicated delivery,
   clobbered published requests, and false verdicts are all reachable.

   *Filename identifiers are the authority for paths.* An identifier
   taken from a file's _contents_ — a req_id member in a request body,
   for instance — MUST NOT determine a write path.  The Runtime derives
   identifiers from the validated filename, on a restricted character
   set.

   *Write-side path discipline.* Every write, rename, directory
   creation, or archive move into or within a Namespace MUST satisfy all
   three of the following *conjunctively*. Each closes a distinct gap
   the other two leave open, and none is sufficient alone.

   *  *(a) Full parent-chain pinning.* The write resolves its full
      parent chain through pinned directory descriptors from the
      Namespace root — openat, mkdirat, renameat off a descriptor the
      Runtime opened once per Namespace — and MUST NOT re-resolve a path
      string for the act itself.  A path-based containment check
      followed by a path-based act is a race, not a check: the parent
      directory the check inspected and the parent directory the act
      touches are not guaranteed to be the same object, because the
      Agent can swap a parent directory for a symlink in between.
      Applying a no-follow flag to the _final_ path component alone does
      not close this gap — it constrains only the last component, not a
      swapped parent — and rename and directory-creation calls have no
      no-follow equivalent at all, so a bare "writes do not follow
      symlinks" requirement is not sufficient on its own.

Rapp & Adams              Expires 24 April 2027                [Page 19]
Internet-Draft                    AMMP                      October 2026

   *  *(b) No-follow while building the chain.* Each directory component
      that goes into that parent chain MUST itself have been opened with
      O_NOFOLLOW|O_DIRECTORY at the moment it was added to the pinned
      chain.  A descriptor counts as pinned only if the open that
      produced it refused to follow a symlink at that step.  Requirement
      (a) without this is equally insufficient, and the gap is the
      symmetric one: nothing otherwise stops the Runtime from
      _acquiring_ a "pinned" descriptor by following an Agent-planted
      symlink in the first place — the Agent replaces a Namespace
      subdirectory with a symlink before the Runtime's once-per-
      Namespace open, or before a later re-open after a restart — and a
      descriptor obtained that way is pinned to the wrong object, so
      every write built off it inherits the redirection no matter how
      faithfully (a) is followed from there.

   *  *(c) Non-following final component.* The final path component MUST
      NOT be followed if a symlink exists at that name: the open MUST
      carry O_NOFOLLOW and MUST fail closed on ELOOP rather than writing
      through whatever the name currently resolves to.  Two cases differ
      in what else they require, and both are covered:

      -  *A newly created file* — the .tmp sibling the atomicity rule
         above already requires, and any Sidecar blob — MUST
         additionally be created with O_EXCL (O_WRONLY|O_CREAT|O_EXCL,
         never O_TRUNC in place of O_EXCL), failing closed on EEXIST.

      -  *An append-mode or directory-creating write that has no .tmp
         sibling* — audit/log.jsonl is the case this document names —
         cannot use O_EXCL, because the target legitimately already
         exists.  It MUST instead open with
         O_NOFOLLOW|O_APPEND|O_NONBLOCK (or use mkdirat with the parent
         pinned per (b)), MUST NOT block on a special file, and MUST
         verify through the resulting descriptor (fstat, never a pre-
         open stat) that the object is a regular file it may append to;
         it SHOULD refuse a target whose link count exceeds 1.  The non-
         blocking open is load-bearing and is not a performance concern:
         O_NOFOLLOW does not apply to a FIFO — a FIFO is not a symlink —
         and opening one for writing blocks until a reader appears, so
         without O_NONBLOCK the fstat is never reached and an Agent that
         plants a FIFO at this name stalls the drain indefinitely.  The
         first branch's O_EXCL refuses that case on its own; the second
         branch has no O_EXCL to lean on and states it explicitly.
         Requiring O_EXCL in the second branch would forbid appending to
         an existing log at all; requiring nothing there would leave the
         one write this document names that has no .tmp sibling
         unprotected.

Rapp & Adams              Expires 24 April 2027                [Page 20]
Internet-Draft                    AMMP                      October 2026

      Requirement (c) is needed in both of its branches because an Agent
      can pre-plant a symlink at a filename the Runtime is about to
      write. req_id is Agent-chosen, and every Runtime-written filename
      in the layout tree above — results/<id>.json, the .tmp sibling of
      processed/req-<id>.json, inbound/notices/notice-<id>.json,
      inbound/messages/notice-<id>.json, audit/log.jsonl — is reachable
      to an Agent that can predict or observe it.  Absent (c), such a
      name gets its bytes written through the planted symlink with the
      Runtime's own privileges, even though the parent chain that
      reached the containing directory was itself correctly pinned under
      (a) and (b).

      This discipline governs every Runtime-side write named in the
      layout tree, not only the initial consumption of an Agent-authored
      artifact.

   *Read-side discipline.* The same rule applied to reads.  When the
   Runtime opens any path the Agent can influence — every req-<id>.json,
   every Sidecar ordinal, and, in a deployment that leaves inbound/
   writable, anything it later reads back from there — it MUST open
   without following symbolic links at any component (per-component
   O_NOFOLLOW, or an equivalently pinned directory-descriptor walk from
   the Namespace root, matching the write-side rule above), MUST NOT
   block on a special file (a non-blocking open), MUST verify that the
   opened object is a regular file through the open descriptor (fstat
   and an S_ISREG test — never a pre-open stat, which is a race), SHOULD
   refuse a file whose link count exceeds 1, and MUST enforce a
   deployment-configured per-file size cap (a bounded read) and a per-
   request Sidecar count cap.  The too_large value in the Result
   vocabulary (Section 6.1) presumes that cap: enforcing one is an
   obligation, not an option.

   A Connector's one Agent-influenceable read path — resolving a
   notice_id to inbound/messages/notice-<id>.json — MUST be built only
   from a pattern-validated notice_id (the schemas pin the pattern) and
   MUST confine resolution to the Connector's own inbound/ tree
   (Section 7.3).

   *Sidecar ordinal binding.* Sidecar blobs are named by Runtime-
   assigned 0-based ordinals equal to the Descriptor's array index,
   which is the sole binding authority.  Nothing else names a blob; a
   Descriptor's filename is display-only, and MUST NOT be used as a
   path.

Rapp & Adams              Expires 24 April 2027                [Page 21]
Internet-Draft                    AMMP                      October 2026

   *Commit sentinel.* All Sidecar bytes are written — .tmp sibling, then
   rename — *before* req-<id>.json; the JSON file's presence is the only
   publish signal.  The Runtime MUST verify that every referenced
   ordinal exists and MUST re-verify size and digest before acting on
   the request, against the snapshot it took at ingest (below), never
   against the live path.

   *Inbound Sidecar directories are keyed on the Runtime-minted notice
   identifier.* A Runtime MUST NOT name one after the provider
   message.id.  The provider identifier is external and is not
   character-set-safe, so using one as a path component would let a
   sender influence where the Runtime writes.  The same rule extends to
   the body artifact: inbound/messages/notice-<id>.json MUST be keyed on
   the notice identifier, and MUST NOT be keyed on message.id.  Inbound
   bytes are published only at inbound/notices/<notice-
   id>.attachments/<index>, the single Sidecar directory named in the
   layout tree.

   *Runtime-private Staging lives outside the mounted Volume* — at a
   path the Agent's mount cannot reach at all.

   *Ingest by copy.* Before validating, evaluating policy on, or
   composing from any Agent-authored artifact — req-<id>.json and every
   Sidecar ordinal — the Runtime MUST copy the bytes into Runtime-
   private Staging and MUST act only on that private copy.  All checks —
   schema validation, recipient policy, size and sha256 verification —
   and all message composition MUST run over that snapshot, and the
   Runtime MUST NOT re-read any Agent-writable path after a decision
   that depends on its content.  Verify-then-use against a live Agent-
   writable path is a race, not a check.

   This obligation is what makes the Connector's "MUST NOT delete or
   rewrite a published request" (Section 4.2) a pure well-formedness
   rule: a Runtime MAY treat an observed post-publish change as
   malformed, but no safety property depends on the Connector honoring
   immutability — which is what the invariant of Section 3.2 requires.
   (This governs the properties that protect the Runtime, other Agents,
   and the outside world.  Obligations that protect an Agent from its
   own inbox are necessarily Connector-implemented and are stated as
   such, not as properties the Runtime relies on a Connector for.)

   *Byte-absence is the gate.* The Runtime MUST NOT place non-clean
   bytes in an Agent's Namespace. content_ref is the Runtime's access
   grant, present only for bytes it has published as clean
   (Section 7.2).

Rapp & Adams              Expires 24 April 2027                [Page 22]
Internet-Draft                    AMMP                      October 2026

   *Tree rules.* Every Runtime-owned inbound-class tree is an *origin
   class*: a consumer MUST derive the class of a document from the tree
   it was read from, and MUST NOT derive it from any member of the
   document. notice_id uniqueness is per tree, and where a tree has more
   than one writer each MUST mint so that cross-writer collision is
   negligible — at least 128 bits of entropy under the identifier
   character set, or a writer-specific prefix — and a consumer keying
   state across trees MUST include the tree in that key. content_ref
   resolves only within the notices/ directory of the tree the enclosing
   document was read from.  Write authority per tree is distinct and
   MUST be structurally enforced; it MUST NOT depend on configuration
   the writer itself reads. inbound/ is the one tree the core protocol
   defines; a profile, such as the peer-origin profile of Section 12,
   MAY define others.

   *Inbound is Runtime-owned, and read-only is a valid posture.* The
   inbound/ tree is Runtime-owned end to end.  A Runtime MAY expose it
   to the sandbox read-only — for instance as a read-only mount, which
   is the mechanism that actually makes a delivered Notice unforgeable,
   since file permission bits do not bind a same-uid Agent, which can
   change a mode it owns.  A Connector MUST NOT require write access
   anywhere under inbound/: it MUST start, relay Notices, and resolve
   bodies when every write, rename, and directory creation under
   inbound/ fails. inbound/notices/processed/ is OPTIONAL — a
   conventional archive location a Connector MAY use only where a
   deployment leaves inbound writable; nothing on either side may depend
   on its existence or its contents.

   *Consumption tracking is Connector-private.* How a Connector records
   which Notices it has already relayed to its Agent is Connector-
   private state, outside this protocol.  Such state lives in the
   Connector's own storage outside the Volume, or in Connector-owned
   paths under outbound/ not enumerated in the layout tree — which MUST
   NOT use the req- prefix, reserved for Submit Requests and their
   Sidecars, so that a Runtime's scan of outbound/ never encounters
   Connector-private state.  Either way, the Runtime neither reads nor
   relies on it.  A seam-visible consumption signal is deliberately not
   specified by this document; Appendix K records the reasoning.

   *Outbound extension space.* outbound/ext/<name>/ is a Connector-owned
   side channel, one directory per <name>, that a Runtime MAY read by
   out-of-band agreement. *<name> is a stable Connector id: the
   Connector chooses it once, and it MUST NOT change when the
   Connector's repository, package, or deployment is renamed or moved.
   A Runtime that reads the directory MUST treat <name> as opaque — it
   is an identifier, never a name to be parsed, resolved, or matched
   against a repository or path.* Because it is also a path segment in
   an Agent-writable tree, <name> MUST be a single path segment of 1 to

Rapp & Adams              Expires 24 April 2027                [Page 23]
Internet-Draft                    AMMP                      October 2026

   64 characters drawn from lowercase ASCII letters, digits, hyphen and
   underscore, beginning with a letter or digit, so that it names the
   same directory on case-sensitive and case-insensitive filesystems
   alike.  This document specifies no shape for anything under it; it
   lies outside the req- prefix the Runtime scans, and nothing security-
   relevant may be built on it.  Its first use is a Connector's per-
   Notice *outcome* file, written by the receiving Agent's own uid and
   therefore forgeable by construction.  An outcome is a statement by
   the receiving side about what it did with a Notice; a party MUST NOT
   treat an outcome as proof of delivery or of non-delivery, and the
   Runtime's own delivery record is authoritative over it
   (Section 14.15).  There is deliberately no inbound extension space:
   an inbound tree is a Runtime assertion about origin, and a Connector
   cannot know what a vendor-named assertion means.

4.2.  Deletion rights

   This table names who may delete each artifact.  It is *permissive*: a
   party exercising a right it already effectively had — a Runtime
   deleting from its own Runtime-owned tree, for instance — changes
   nothing about what conformant behavior looks like.

   +=======================+===========+===========+===================+
   | Artifact              |Written by |May be     |Basis              |
   |                       |           |deleted by |                   |
   +=======================+===========+===========+===================+
   | outbound/             |Connector  |Runtime    |Consumption — the  |
   | req-<id>.json         |           |           |move to            |
   | (pending) and         |           |           |processed/, or     |
   | req-<id>.attachments/ |           |           |deletion once the  |
   |                       |           |           |Result is written. |
   |                       |           |           |The Connector MUST |
   |                       |           |           |NOT delete or      |
   |                       |           |           |rewrite a          |
   |                       |           |           |published request; |
   |                       |           |           |the JSON file's    |
   |                       |           |           |presence is the    |
   |                       |           |           |commit signal, and |
   |                       |           |           |un-publishing is   |
   |                       |           |           |unspecified.       |
   +-----------------------+-----------+-----------+-------------------+
   | outbound/processed/   |Runtime    |Runtime at |The Runtime's own  |
   | req-<id>.json         |           |will;      |archive. *Caveat:* |
   |                       |           |Connector  |Section 5.1        |
   |                       |           |after      |derives req_id     |
   |                       |           |reading the|from max(pending,  |
   |                       |           |matching   |processed,         |
   |                       |           |Result     |results) + 1, so a |

Rapp & Adams              Expires 24 April 2027                [Page 24]
Internet-Draft                    AMMP                      October 2026

   |                       |           |           |Connector that     |
   |                       |           |           |deletes here (or   |
   |                       |           |           |in results/) MUST  |
   |                       |           |           |preserve req_id    |
   |                       |           |           |monotonicity by    |
   |                       |           |           |other durable      |
   |                       |           |           |means, such as its |
   |                       |           |           |own counter.       |
   |                       |           |           |Correspondingly, a |
   |                       |           |           |Runtime MUST NOT   |
   |                       |           |           |rely on processed/ |
   |                       |           |           |contents as        |
   |                       |           |           |durable state (for |
   |                       |           |           |deduplication,     |
   |                       |           |           |say) — the         |
   |                       |           |           |Connector may      |
   |                       |           |           |delete there, and  |
   |                       |           |           |the Runtime's own  |
   |                       |           |           |record lives       |
   |                       |           |           |outside this       |
   |                       |           |           |protocol.          |
   +-----------------------+-----------+-----------+-------------------+
   | outbound/             |Runtime    |Connector, |No signal in this  |
   | results/<id>.json     |           |once read; |protocol tells the |
   |                       |           |Runtime by |Runtime that a     |
   |                       |           |retention  |Connector has read |
   |                       |           |           |a verdict, so      |
   |                       |           |           |Runtime-side       |
   |                       |           |           |deletion SHOULD be |
   |                       |           |           |age-based and      |
   |                       |           |           |generous: a Result |
   |                       |           |           |deleted before it  |
   |                       |           |           |is read is a lost  |
   |                       |           |           |verdict.           |
   +-----------------------+-----------+-----------+-------------------+
   | inbound/notices/      |Runtime    |Runtime    |Runtime retention  |
   | notice-<id>.json,     |           |only.      |policy.  Absent a  |
   | inbound/messages/     |           |Where a    |seam-visible       |
   | notice-<id>.json, and |           |deployment |consumption signal |
   | their .attachments/   |           |leaves     |(Appendix K),      |
   | Sidecar directories   |           |inbound    |retention MUST be  |
   |                       |           |writable,  |conservative and   |
   |                       |           |the        |age-based.  A      |
   |                       |           |Connector  |Connector already  |
   |                       |           |MAY move or|tolerates the      |
   |                       |           |delete per |disappearance of   |
   |                       |           |the        |these artifacts: a |
   |                       |           |optional   |read against a     |

Rapp & Adams              Expires 24 April 2027                [Page 25]
Internet-Draft                    AMMP                      October 2026

   |                       |           |processed/ |missing one        |
   |                       |           |convention,|returns "not       |
   |                       |           |but MUST   |resolved"          |
   |                       |           |NOT depend |(Section 7.3).     |
   |                       |           |on the     |                   |
   |                       |           |ability.   |                   |
   +-----------------------+-----------+-----------+-------------------+
   | peer/ notices,        |Runtime    |Runtime    |The same retention |
   | messages and their    |           |only, as   |rule as the        |
   | .attachments/ Sidecar |           |for the    |inbound row        |
   | directories (peer-    |           |inbound row|(Section 12.8).    |
   | origin profile)       |           |above.     |                   |
   +-----------------------+-----------+-----------+-------------------+
   | inbound/cursor.json   |Runtime    |Runtime    |Internal state.    |
   +-----------------------+-----------+-----------+-------------------+
   | audit/log.jsonl       |Runtime    |Runtime    |The Connector MUST |
   |                       |           |(rotation) |NOT delete it.     |
   +-----------------------+-----------+-----------+-------------------+
   | identity.json         |Runtime or |Runtime or |Consumers already  |
   |                       |provisioner|provisioner|tolerate its       |
   |                       |           |           |absence            |
   |                       |           |           |(Section 9).       |
   +-----------------------+-----------+-----------+-------------------+
   | outbound/ext/<name>/  |Connector  |Connector, |A Connector-owned  |
   |                       |           |at will;   |side channel       |
   |                       |           |Runtime,   |(Section 4.1).  A  |
   |                       |           |only what  |Runtime MUST NOT   |
   |                       |           |its out-of-|delete the         |
   |                       |           |band       |directory itself   |
   |                       |           |agreement  |or anything else   |
   |                       |           |says it    |under it, and an   |
   |                       |           |consumes   |operator reset is  |
   |                       |           |(an outcome|not consumption.   |
   |                       |           |it has     |                   |
   |                       |           |read, for  |                   |
   |                       |           |example)   |                   |
   +-----------------------+-----------+-----------+-------------------+
   | directory.json        |Runtime    |Runtime    |Advisory and       |
   |                       |           |           |Runtime-published  |
   |                       |           |           |(Section 10).  It  |
   |                       |           |           |is not read back   |
   |                       |           |           |as authorisation,  |
   |                       |           |           |so deleting or     |
   |                       |           |           |replacing it       |
   |                       |           |           |carries no state.  |
   +-----------------------+-----------+-----------+-------------------+

                                  Table 2

Rapp & Adams              Expires 24 April 2027                [Page 26]
Internet-Draft                    AMMP                      October 2026

5.  Outbound: Submit Request

   An Agent's only outbound action is writing an inert request file into
   its Namespace.  The file carries no send capability; the Runtime
   enforces policy before anything leaves.

   A Submit Request is written to <volume-root>/<agent-id>/outbound/
   req-<id>.json as a JSON text [RFC8259].  Its Sidecar bytes, if any,
   are written first, into req-<id>.attachments/, one blob per
   Descriptor, named by that Descriptor's 0-based array index
   (Section 4.1, commit sentinel).

   NOTE: '\' line wrapping per RFC 8792

   {
     "contract_version": "2",
     "req_id": "00000001",
     "agent_id": "agent.research",
     "ts": "2026-07-20T04:10:56Z",
     "in_reply_to": "1721457600000000000-4242",
     "draft": {
       "to": ["owner@example.com"],
       "cc": [],
       "subject": "Run report",
       "body_text": "The run finished; details below.",
       "reply_to_message_id": null,
       "attachments": [
         {
           "filename": "report.pdf",
           "media_type": "application/pdf",
           "size_bytes": 48213,
           "sha256": "970e441b6c4ecd37abf2541e1276c680bdec8481cef9368ff\
       1d010fd6b784b07"
         }
       ]
     }
   }

      Figure 2: draft/examples/submit-request.json (illustrative — not
                                 a fixture)

   The normative shape is Appendix A.  The Submit Request envelope is
   *closed*: additionalProperties is false at every level, and a Runtime
   MUST reject a request carrying any unrecognized member.  Section 13.1
   explains why this direction is closed while the Runtime-authored
   artifacts are open.

Rapp & Adams              Expires 24 April 2027                [Page 27]
Internet-Draft                    AMMP                      October 2026

5.1.  Field semantics and obligations

   *  *contract_version* (REQUIRED) is the wire major version, the
      string "2".  A Runtime MUST check it before schema validation and
      MUST fail closed on an absent, lower, or higher major
      (Section 13.1).

   *  *req_id* (REQUIRED) is *monotonic per Namespace* and MUST NOT be
      reused for the life of the Namespace's outbound/ tree; derive it
      from max(pending, processed, results) + 1.  Reuse collides message
      identifiers and triggers provider-side idempotency deduplication.
      req_id is pinned to a filename-safe character set by the schema,
      but it is not the path authority: the Runtime derives the path
      identifier from the validated filename (Section 4.1).

   *  *agent_id* (OPTIONAL), if present, MUST match the Namespace the
      request was found in, and a Runtime MUST reject a mismatch.  A
      Runtime MUST NOT use it as the attribution source (Section 4.1).

   *  *ts* (OPTIONAL) is the Connector's timestamp for the request.  It
      is Agent-authored and therefore advisory.

   *  *in_reply_to* (OPTIONAL) correlates this request with prior
      inbound work.  It carries either a Runtime-minted notice_id or a
      mail message identifier [RFC5322].  It is a *hint from an
      untrusted party* and MUST NOT by itself widen what the request is
      permitted to do — the same posture Section 8 fixes for task_id.

   *  The Runtime derives the message identity and the From/Reply-To of
      the composed message itself; identity is Runtime-owned, and the
      Agent does *not* choose its sending identity.

   *  *draft.to* is REQUIRED and carries at least one recipient;
      *draft.cc* is OPTIONAL and MAY be empty. *draft.subject* and
      *draft.body_text* are REQUIRED and MAY be empty strings. body_text
      is *plain text*; this document defines no alternative body
      representation.

   *  *draft.reply_to_message_id* (OPTIONAL, nullable) names the message
      this draft replies to, as an [RFC5322] message identifier.  It is
      composed directly into an In-Reply-To: header, which is why it
      carries a control-character pattern in the schema (below).

Rapp & Adams              Expires 24 April 2027                [Page 28]
Internet-Draft                    AMMP                      October 2026

   *  *Recipient policy is a Runtime decision.* The Agent's chosen
      draft.to and draft.cc are *not* inherently trusted.  Allowlisting,
      task-counterpart binding, and any other recipient rule are
      evaluated Runtime-side.  A populated allowlist plus a fresh
      recipient is an exfiltration surface that the Runtime, not the
      Connector, must close.

   *  *Recipient syntax and header-injection discipline.* Each draft.to
      and draft.cc element is exactly one bare addr-spec: no display
      name, address list, quoted local part, comment, IP literal, or
      folding whitespace.  The schema pins the pattern.  This profile is
      deliberately narrower than [RFC5322], because those excluded forms
      are exactly what makes allowlist evaluation non-mechanical;
      internationalized (EAI/SMTPUTF8) addresses are out of scope for
      now and may be added later.  A Runtime MUST reject — with a top-
      level rejected Result — a request whose recipient entries do not
      parse under this profile, MUST evaluate recipient policy over
      exactly the recipient set it will transmit to, and MUST NOT
      transmit to any address not present in draft.to or draft.cc.

      Separately, a Runtime MUST NOT allow any Agent-authored string —
      subject, a recipient entry, an attachment filename, an attachment
      media_type, draft.reply_to_message_id, in_reply_to, agent_id, ts,
      or body_text where it abuts headers — to introduce a header
      boundary in the composed message.  CR, LF, and NUL MUST be
      rejected or stripped before composition *even where schema
      validation was bypassed upstream*: the schema-level control-
      character patterns on subject, filename, media_type,
      reply_to_message_id, in_reply_to, agent_id, and ts are the first
      line of defense, not the only one.  Two of those fields are easily
      overlooked: reply_to_message_id and media_type are composed
      _directly into headers_ — In-Reply-To: and a MIME part's Content-
      Type: [RFC2045] respectively — rather than into a body or a
      display field.

   *  *draft.attachments[]* (OPTIONAL) is an array of Descriptors, each
      carrying filename, media_type, size_bytes, and sha256. *size_bytes
      and sha256 are REQUIRED on every Descriptor*, computed by the
      Connector over the *decoded* bytes it wrote, with sha256 in
      lowercase hexadecimal.

      There is *no outbound content_ref*. The Runtime re-derives each
      Sidecar's path from req-<id> plus the Descriptor's array index,
      which is the sole binding authority; an Agent-supplied path string
      would be pure disagreement and attack surface, so it is absent
      from the schema entirely and additionalProperties: false rejects
      it. attachments: [] is valid and is equivalent to the member being
      absent; the Sidecar directory MUST then be absent.

Rapp & Adams              Expires 24 April 2027                [Page 29]
Internet-Draft                    AMMP                      October 2026

      sha256 here is an integrity and deduplication aid, *not an
      outbound trust boundary*: the Agent controls both the bytes and
      the hash, so a mismatch catches corruption, never a hostile Agent.
      Its value to the Runtime is that it detects an inconsistent
      request, not that it authenticates one.

6.  Outbound: Result

   A Result is the Runtime's verdict for one consumed Submit Request,
   written to <volume-root>/<agent-id>/outbound/results/<id>.json.

   {
     "contract_version": "2",
     "req_id": "00000001",
     "outcome": "accepted",
     "detail": null,
     "reason_code": "allowlisted",
     "ts": "2026-08-09T19:14:00Z",
     "recipients": ["owner@example.com"],
     "message_id": "<20260809191400.1@example.org>",
     "job_id": null,
     "attestation": {
       "form": "self-stamp",
       "token": "selfstamp.v1.abcdef",
       "claims": {
         "agent": "agent.research",
         "acts_for": "owner@example.com",
         "issued_at": "2026-08-09T19:14:00Z"
       }
     },
     "attachments": [
       { "index": 0, "outcome": "accepted", "detail": null }
     ]
   }

    Figure 3: draft/examples/result.json (illustrative — not a fixture)

   The normative shape is Appendix B.  The Result envelope is *open*: a
   consumer MUST tolerate members it does not recognize, at every level
   (Section 13.1).

6.1.  Field semantics and obligations

   *  *The Runtime writes exactly one Result per Submit Request it
      consumes.* A request the Runtime never consumes gets no Result,
      and no signal in this protocol distinguishes "not yet" from
      "never."

Rapp & Adams              Expires 24 April 2027                [Page 30]
Internet-Draft                    AMMP                      October 2026

   *  *outcome* (REQUIRED) is one of accepted, queued_for_human, or
      rejected.  This is a *closed vocabulary*: it gates which
      processing path a document takes, so a consumer cannot safely
      treat an unrecognized value as opaque, and introducing a new value
      is a major change (Section 13.1).

   *  *Any policy miss — allowlist, rate limit, DLP hold, halt — MUST
      produce queued_for_human.* A Runtime MUST NOT send such a request,
      and MUST NOT drop it without a Result.

   *  *accepted* means the provider accepted the message for relay.
      Downstream delivery or quarantine is out of the Runtime's hands
      and is surfaced later via an inbound delivery-status notification,
      if any.

   *  *detail* (OPTIONAL, nullable) is human-readable text.  It carries
      no machine semantics.

   *  *reason_code* (OPTIONAL) records _which policy path_ authorized or
      refused the request; human text stays in detail.  A Runtime
      *SHOULD* set a reason on every Result, successes included.  The
      suggested vocabulary — recipient_bound, allowlisted,
      no_verified_counterparty, recipient_not_allowlisted, dlp_hold,
      no_screener, halted, rate_limited, unsupported_contract_version —
      is *extensible and not enum-locked in the schema*. Two consumer
      obligations follow: a consumer MUST tolerate a reason_code it does
      not recognize, treating it as opaque and never as an error, and a
      consumer MUST NOT infer the outcome from it. outcome is the sole
      authoritative verdict; reason_code only explains which path
      produced it, and a Runtime may add codes at any time without a
      protocol change. unsupported_contract_version is the code a
      Runtime sets on the rejected Result it writes for a version-
      refused Submit Request (Section 13.1).

   *  *ts* (OPTIONAL) is the Runtime-set, of-record time of the verdict,
      in ISO-8601 form.

   *  *recipients* (OPTIONAL) records the recipient set the Runtime
      actually transmitted to.

   *  *message_id* (OPTIONAL, nullable) is the [RFC5322] message
      identifier the Runtime assigned to the composed message.

   *  *job_id* (OPTIONAL, nullable) is a provider-side job identifier,
      opaque to this protocol.

Rapp & Adams              Expires 24 April 2027                [Page 31]
Internet-Draft                    AMMP                      October 2026

   *  *attestation* (OPTIONAL) is the identity stamp the Runtime applied
      to the outbound message.  Its form is self-stamp or verifiable,
      distinguishing a stamp a stranger cannot verify from one that can
      be verified independently.  A Connector _surfaces_ an attestation,
      and does not verify it; verification is a Runtime concern.

   *  *attachments[]* (OPTIONAL) carries one diagnostic entry per
      submitted attachment: index, an outcome of accepted, dlp_held,
      rejected_type, or too_large, and an optional detail. *index MUST
      equal the entry's own position in the array* — contiguous, unique,
      and covering 0 through n-1 exactly once.  Array position is the
      binding authority for attachments in both directions
      (Section 4.1).  This constraint is not expressible in JSON Schema
      and is enforced by the conformance gate as a named post-check
      rather than by the schema (Appendix J).

      *Two paths, kept apart.* A _malformed_ attachment — a size or
      digest mismatch, a missing or extra Sidecar, a duplicate index —
      is not a policy decision: it fails the whole request with a top-
      level rejected.  A _policy hold_ — DLP, a disallowed type, a size
      cap, including a fail-closed hold when no screener is available —
      is queued_for_human.  Per-attachment outcomes are diagnostic
      detail under *one* governing top-level outcome, and a Runtime MUST
      NOT send part of a request on their strength.

7.  Inbound: Deliver Notice and Inbound Message

   The Runtime writes one Deliver Notice per *delivered* message.
   Quarantined or unverified mail produces *no Notice*: a Runtime MUST
   NOT write a Notice for it, and logs it on its own side instead.  The
   Notice is a _pointer and summary_; the body is fetched on demand from
   a separate spooled artifact (Section 7.3) and is *untrusted content*.

Rapp & Adams              Expires 24 April 2027                [Page 32]
Internet-Draft                    AMMP                      October 2026

   {
     "contract_version": "2",
     "notice_id": "1721457600000000000-4242",
     "ts": "2026-07-20T04:00:00Z",
     "kind": "deliver",
     "message": {
       "id": "42",
       "from": "Alex Doe <owner@example.com>",
       "subject": "status?",
       "preview": "hey, how is the run going",
       "mailbox": "inbox",
       "task_id": "0f1e2d3c4b5a6978",
       "thread_id": "T-9182",
       "in_reply_to": "<20260719120000.7@example.com>",
       "references": ["<20260718090000.3@example.com>"],
       "sender_standing": "known",
       "provenance": "external",
       "verdict": {
         "auth": "dmarc-pass",
         "screen": "clean",
         "entitlement": "task-counterparty"
       },
       "attachments": [
         {
           "filename": "invoice.pdf",
           "media_type": "application/pdf",
           "size_bytes": 12345,
           "disposition": "unscanned"
         }
       ]
     }
   }

      Figure 4: draft/examples/deliver-notice.json (illustrative — not
                                 a fixture)

   The normative shape is Appendix C.  The Deliver Notice envelope is
   *open*: a consumer MUST tolerate members it does not recognize, at
   every level (Section 13.1).

7.1.  Field semantics and obligations

   *The drop rule.* A Connector relays only deliver Notices.  It MUST
   drop — not surface — a Notice that fails *either* of two independent
   checks, treating a failure of either one alone as sufficient grounds
   to drop:

Rapp & Adams              Expires 24 April 2027                [Page 33]
Internet-Draft                    AMMP                      October 2026

   1.  *Schema failure.* The Notice fails validation against the
       deliver-notice schema (Appendix C) for *any reason other than an
       unrecognized member*. That single formulation covers a kind other
       than deliver, a missing required member, an enum violation on a
       closed vocabulary, a pattern violation (including a traversal or
       character-set violation in notice_id, or a shape violation in
       content_ref's own pattern), a conditional (allOf / if-then)
       violation, and a type violation alike, with no need to enumerate
       schema-failure categories separately. *A Connector MUST NOT drop
       a Notice because it carries an unrecognized member*
       (Section 13.1); that is the one carve-out from "fails schema
       validation."

   2.  *The content_ref index and prefix binding.* A Notice can pass
       check 1 — every member individually well-shaped — and still carry
       an attachment Descriptor whose content_ref does not equal
       <notice_id>.attachments/<i> for its own 0-based array position i
       (Section 7.2): a mismatched trailing index, or a leading
       notice_id prefix pointing into a _different_ Notice's Sidecar
       directory.  A Connector MUST run this check independently of
       schema validation and MUST drop the Notice on failure exactly as
       it would a schema failure.

   *This is not a closure over schema validation.* Check 2 binds two
   members of the same document — a Descriptor's content_ref against the
   enclosing document's own notice_id and against the Descriptor's own
   array position — against each other, which is not expressible in the
   JSON Schema vocabulary these schemas use [JSON-SCHEMA-2020-12].  It
   therefore cannot be folded into "fails schema validation" no matter
   how the schema is phrased: it is a second, independent condition, not
   a special case of the first.  The conformance suite enforces it as a
   named post-check and pins it with a fixture that is schema-valid on
   its own and fails only this check (Appendix J).

   A *version refusal* — an absent or non-matching contract_version
   major — is explicitly *not* part of this drop path.  It is handled
   first and separately (Section 13.1) as a distinct, logged outcome: a
   Connector checks the version before it ever reaches schema
   validation, so a version-refused Notice never falls into the drop
   rule at all.

   A document with kind: "peer" found under inbound/notices/ fails check
   1 against this schema, and is dropped the same way (Appendix F, the
   peer-origin profile's cross-lock).

   *Field obligations.*

Rapp & Adams              Expires 24 April 2027                [Page 34]
Internet-Draft                    AMMP                      October 2026

   *  *contract_version*, *notice_id*, *ts*, *kind*, and *message* are
      REQUIRED. notice_id is Runtime-minted and is pinned by the schema
      to a character set that is safe as a path component and as a
      content_ref prefix.

   *  Required members of message are id, from, subject, preview, and
      mailbox.  The rest is enrichment (Section 13.2).

   *  *message.id* is the provider's message identifier.  It is
      *external and not character-set-safe*, and MUST NOT be used as a
      path component anywhere (Section 4.1).  It is the mail store's
      identifier for the message (an IMAP UID, for example), not the
      [RFC5322] Message-ID; an Agent replies to a delivered message by
      naming its notice identifier in in_reply_to (Section 5).

   *  *message.from is attacker-controlled display data.* Its display-
      name half is chosen freely by the sender —
      "security@yourbank.example <attacker@evil.example>" is a legal
      value — and it MUST NOT be used for any trust decision, nor
      rendered to a human or to an Agent as verified identity.  Verified
      sender identity is only ever what the Runtime summarizes into
      sender_standing and verdict; it is not the from string itself.
      The one exception in this protocol is the message.from of a Peer
      Notice, which the Runtime asserts (Section 12.4).

   *  *Presentation obligations.* A Connector that presents a message
      body to its Agent MUST convey two things, whatever words it uses:
      quoted or forwarded material inside the body carries no authority
      of its own — a message's authority extends to what its author
      wrote, never to what they relayed — and that an attachment's
      disposition is the Runtime's assertion about handling and not a
      scanner verdict, so a Connector MUST NOT imply that bytes have
      been screened and MUST NOT contradict the per-attachment status
      the Runtime published.  A Connector MUST also convey that an
      attachment's filename and media_type are sender-chosen display
      strings on the mail tree; on a tree whose sender the Runtime
      authenticated it SHOULD.  On the mail tree a Connector MUST
      additionally convey that the sender is not authenticated beyond
      whatever standing the Runtime has already summarized — carried in
      sender_standing and verdict, named here as the locus and not as
      vocabulary to repeat to a reader — and that message.from is
      sender-chosen text.  These are obligations about substance, not
      vocabulary: a Connector satisfies them with any wording its reader
      can act on without consulting this document, and they MUST be
      satisfiable by a literal string the Connector carries, requiring
      no lookup or computation when a body is presented.

Rapp & Adams              Expires 24 April 2027                [Page 35]
Internet-Draft                    AMMP                      October 2026

   *  *The untrusted-content posture covers every sender-originated
      string* in the Notice and in the spooled Inbound Message:
      message.from, subject, preview, body_text, references, attachment
      filename and media_type, and the Inbound Message's from, to, cc,
      and subject.  This is not limited to the members called out
      individually here.

   *  *message.sender_standing* (OPTIONAL) is an *open, advisory
      vocabulary* — known, agent-tier, first-contact, read-only, and
      others.  How the values are computed is Runtime policy and out of
      scope; the wire fixes only today's known vocabulary, so that a
      Connector can tell its Agent "stranger," "agent," or "needs sign-
      off."  A Connector MUST tolerate a value outside this list,
      treating it as opaque — never as grounds to drop the Notice — and
      a Runtime MAY add a value as a minor change (Section 13.1).

   *  *message.provenance* (OPTIONAL) classifies the message source —
      external, internal, and others — so that a Runtime can route
      provenance-labelled screening.  It is advisory metadata, like
      verdict, and a Connector MUST NOT present it as license for an
      Agent to obey body content.  It is likewise an *open, advisory
      vocabulary*: a Connector MUST tolerate a value outside this list,
      treating it as opaque, and MUST NOT drop the Notice on its
      account; a Runtime MAY add a value as a minor change.

   *  *message.verdict* (OPTIONAL) carries the upstream authentication,
      screening, and entitlement results the Runtime consumed.  A
      Connector treats it as advisory metadata, and MUST NOT present it
      as license for an Agent to obey body content.  The object is open:
      unrecognized members inside it are tolerated, as they are
      everywhere else in this artifact.

   *  *message.thread_id*, *message.in_reply_to*, and
      *message.references* (all OPTIONAL) carry provider and [RFC5322]
      threading identity.  They grant correlation, and a consumer MUST
      NOT treat them as grounds for trust (Section 8).

   *  *message.task_id* (OPTIONAL) is Runtime-minted; Section 8 states
      the obligations on it.

   *Direction and openness.* This artifact is Runtime-authored, and its
   envelope is open at every level: a Connector MUST tolerate a member
   it does not recognize, in message or at the top level, and MUST NOT
   drop the Notice on that account.  Emitting an unrecognized member
   remains Runtime non-conformance (Section 13.1).

Rapp & Adams              Expires 24 April 2027                [Page 36]
Internet-Draft                    AMMP                      October 2026

7.2.  Inbound attachment Descriptors

   *message.attachments[]* (OPTIONAL) is an array of Descriptors
   carrying filename, media_type, and size_bytes — all REQUIRED — plus a
   REQUIRED disposition, and OPTIONAL sha256 and content_ref members
   gated on it.

   *disposition is the Runtime's assertion, not a scanner verdict.* Its
   values are clean, stripped, quarantined, and unscanned, and the
   vocabulary is *closed*: it gates byte publication, so a new value
   would be a behavioral fork a consumer cannot treat as opaque, and
   introducing one is a major change (Section 13.1).  On what basis a
   Runtime asserts clean is deliberately unspecified — a screening
   pipeline's pass in one deployment, a closed operator-supervised
   topology in another.  That is Runtime policy and out of scope for the
   wire, the same posture sender_standing takes: the wire fixes the
   vocabulary, never the computation.  The Runtime is trusted by
   construction (Section 3.1); the load-bearing invariant is that the
   _Agent_ is not.

   The counterpart obligation is equally plain: *a Runtime that cannot
   make the determination MUST NOT assert clean.* It emits unscanned —
   or stripped or quarantined where it made that decision — and
   publishes no bytes.

   *Byte publication and the access grant.* A Runtime MAY publish an
   attachment's bytes at inbound/notices/<notice-id>.attachments/<index>
   *only* for a Descriptor it asserts disposition == "clean", and only
   then MAY it emit content_ref (a path of the form <notice-
   id>.attachments/<index>) and sha256.  Byte-absence remains the gate
   (Section 4.1) for anything not asserted clean.  Exposing sha256 on a
   withheld item would be a confirmation oracle for no integrity value,
   which is why it is clean-gated too.  Both conditionals are expressed
   in the schema.

   *The content_ref binding.* content_ref's leading notice_id prefix
   MUST equal the enclosing document's own notice_id, and its trailing
   <index> MUST equal the Descriptor's own 0-based array index.  The
   array index remains the sole binding authority (Section 4.1), and a
   consumer that resolves bytes MUST derive the path from the enclosing
   notice_id and the index, and MUST NOT use the content_ref string's
   own text as the path, since a Descriptor could otherwise point into
   another Notice's Sidecar directory.  This is a wire-shaped, document-
   level obligation, enforced by the conformance gate as a named post-
   check (Appendix J) in the same way the Result-side
   attachments[i].index == i binding is.

Rapp & Adams              Expires 24 April 2027                [Page 37]
Internet-Draft                    AMMP                      October 2026

   *A grant, not a directive.* A consumer MUST tolerate a Descriptor
   whose bytes it does not resolve — relaying the Notice as usual — and
   MUST NOT treat the presence of content_ref as a requirement to fetch.
   A Connector that never dereferences content_ref remains fully
   conformant.  A Connector MUST resolve published bytes only within its
   own Namespace's inbound/notices/ tree and MUST reject any content_ref
   that does not match the pinned pattern — the same confinement
   Section 4.1's read-side discipline requires generally.

   *filename and media_type here are attacker-controlled, untrusted
   display strings.* The untrusted-content posture extends to attachment
   Descriptors: a quarantined file named to look like an instruction is
   still an injection payload even with its bytes withheld.  A clean
   assertion covers the bytes' screening disposition, never the
   truthfulness of these strings.

   *Inbound size_bytes is provider-declared and therefore advisory and
   untrusted* — it comes from the provider's structure report, and is
   not Runtime-verified the way outbound size_bytes is.

   *MIME edges.* filename is the [RFC2047] / [RFC2231]-*decoded*,
   Runtime-sanitized display string.  An "attachment" is any part
   carrying Content-Disposition: attachment [RFC2183], or any named or
   non-inline text part — inline images and multipart/related parts
   count, which is the conservative reading of [RFC2045] and [RFC2046].
   The declared media_type is display-only: the Runtime sniffs the
   bytes, and the sniffed type governs policy on the outbound gate.

7.3.  The Inbound Message (body spool)

   The Runtime spools the delivered body at <volume-root>/<agent-
   id>/inbound/messages/notice-<id>.json, keyed on the Runtime-minted
   notice identifier — never on the provider message.id, for the reason
   given in Section 4.1.

Rapp & Adams              Expires 24 April 2027                [Page 38]
Internet-Draft                    AMMP                      October 2026

   {
     "contract_version": "2",
     "notice_id": "1721457600000000000-4242",
     "id": "42",
     "date": "Mon, 20 Jul 2026 04:00:00 +0000",
     "from": "Alex Doe <owner@example.com>",
     "to": "agent@example.org",
     "subject": "status?",
     "body_text": "hey, how is the run going",
     "attachments": [
       {
         "filename": "report.pdf",
         "media_type": "application/pdf",
         "size_bytes": 48213,
         "disposition": "unscanned"
       }
     ]
   }

     Figure 5: draft/examples/inbound-message.json (illustrative — not
                                 a fixture)

   The normative shape is Appendix D.  Required members are
   contract_version, notice_id, and body_text; the rest — id, date,
   from, to, cc, subject, and attachments[] — is enrichment
   (Section 13.2).  Descriptors here follow exactly the rules of
   Section 7.2, including the content_ref binding and the clean gating.
   This artifact's envelope is *open*, like the other Runtime-authored
   artifacts.

   *Resolution.* A Connector MUST resolve a notice identifier to a body
   from this artifact only, and MUST NOT fetch the body from the
   provider.  The body is *untrusted content*, the same posture as the
   rest of message.

   *Enforcement by absence.* A body the Runtime withholds — because of
   screening or quarantine, or because the message was never delivered —
   is simply not spooled, and a read against a missing artifact returns
   "not resolved."  There is no other signal and no fallback fetch.  The
   same "not resolved" response also covers an artifact the Runtime
   later removes under its own retention policy (Section 4.2): from the
   Connector's side, retired and never-spooled are indistinguishable,
   and both are already-specified behavior.

Rapp & Adams              Expires 24 April 2027                [Page 39]
Internet-Draft                    AMMP                      October 2026

8.  Task Correlation

   The Runtime mints and owns task_id; a Connector only reads it, to
   correlate a Notice with prior work.  The minting and thread-
   resolution _algorithm_ is Runtime policy and out of scope.  This
   protocol fixes three security obligations on it.

   *  *task_id MUST be unguessable and Runtime-minted* — an HMAC over
      the thread root, for instance.  It MUST NOT be a sequential or
      otherwise guessable value, and it MUST NOT be a capability an
      inbound sender can assert: a guessable task_id lets a sender who
      has seen one predict others.

   *  *Matching a task_id, by itself, MUST NOT grant a message entry
      into that task.* Authoritative correlation is header threading
      *plus* per-message verified sender identity.  A subaddress or
      header token supplied by an inbound sender is a _hint_ only, and a
      Runtime MUST NOT treat it as authoritative: it is attacker-
      controllable, and it is stripped by many gateways.

   *  *Threading grants correlation, not trust.* A message that threads
      onto a known task is still identity-verified per message; it does
      not inherit the task's standing.

9.  Binding Record

   The Binding Record is written by the Runtime or provisioner at the
   *Volume root* — <volume-root>/identity.json, a sibling of <agent-
   id>/, not inside it — and names which mailbox this Volume is bound
   to.  Its normative shape is Appendix E.

   The Binding Record names which mailbox a Volume is bound to; it is
   informational and not consulted for enforcement.

   {
     "contract_version": "2",
     "agent_id": "agent-research",
     "address": "agent@example.org",
     "provisioned_ts": "2026-07-20T04:10:56Z"
   }

                Figure 6: fixtures/valid/identity-basic.json

Rapp & Adams              Expires 24 April 2027                [Page 40]
Internet-Draft                    AMMP                      October 2026

9.1.  Field semantics and obligations

   *  *Informational, not authoritative.* The Volume is an Agent-
      writable filesystem, so a compromised Agent can edit or delete
      this file at will.  Attribution and policy stay keyed on Runtime-
      side configuration (Section 4.1).  This record grants nothing, and
      a Runtime MUST NOT consult it for enforcement.

   *  *Consumers MUST tolerate its absence*, and *MUST treat its
      contents as untrusted data*: a reader MUST NOT crash, or change
      its protocol behavior, because of a malformed, oversized, or
      hostile file.

   *  Its jobs are narrow by design: operator sanity — a human
      confirming which mailbox a Volume is bound to — a pre-start
      conflict check for whatever wrote it, catching for instance one
      Agent's credentials pointed at another Agent's Volume, and letting
      a Connector surface the Agent's own address back to its Agent.

   *  *The file is OPTIONAL* (Section 13.2): nothing in this protocol
      requires a Runtime to write it or a Connector to read it, and both
      sides MUST work correctly with it absent.

   *  Required members, if the file is present, are agent_id, address,
      and provisioned_ts. label is OPTIONAL. contract_version is
      OPTIONAL here, as it is on the Peer Directory (Section 10),
      because the file is not a protocol artifact a consumer must parse
      to function; if present it is pinned to "2".

   *  This artifact's envelope is *closed* (additionalProperties:
      false), for a different reason than the Submit Request's is:
      closure here is an anti-secret-leak lint, catching a provisioner
      that writes credential material into a file living on an Agent-
      readable Volume.  It is not a compatibility surface.

10.  The Peer Directory

   A Runtime MAY publish, at the Agent's Namespace root (<agent-
   id>/directory.json, beside the lanes rather than inside one), a per-
   instance projection of the authorisation graph naming which peers
   that Agent may address.

Rapp & Adams              Expires 24 April 2027                [Page 41]
Internet-Draft                    AMMP                      October 2026

   {
     "contract_version": "2",
     "router": "amap.router@fleet.example",
     "written_at": "2026-09-18T12:00:00Z",
     "delegate_to": [
       "bravo@fleet.example",
       "charlie@fleet.example"
     ],
     "mail_to": []
   }

       Figure 7: draft/examples/directory.json (illustrative — not a
                                  fixture)

   *  *Advisory, not authoritative.* The directory is the Runtime's most
      recent word about the graph, and grants nothing.  A Connector MUST
      NOT treat an address's presence as a prediction that a submission
      to it will be accepted, nor its absence as a prediction of
      refusal: authorisation is evaluated by the Runtime at submission
      time, and the Result (Section 6) is the only thing that reports
      what happened.  A consumer that reads the directory as a
      prediction is wrong even when the file is current.

   *  *A Runtime MUST NOT read directory.json back, for any purpose.* It
      is write-only from the Runtime's side: the graph is derived from
      policy, the file is a projection of the graph, and nothing derives
      the graph from the file.  Enforcement remains the Runtime's own
      check against its graph, on every message.  The prohibition is on
      the direction rather than the purpose, because the ways this
      decays — as an optimisation, a cache, a recovery source, or a
      cross-check — all have innocent intent, and the last two turn a
      projection into a source.  A projection that can be read back
      becomes a cache; a cache becomes a record; a record that disagrees
      with policy wins by accident.

   *  *Per-instance projection.* Each Agent's directory carries only the
      edges that Agent is party to.  It MUST NOT carry the set of peers
      who may address that Agent, which is an allowlist in all but name,
      and MUST NOT carry the Agent's own address — attribution is the
      Namespace a file arrived in (Section 4), so an address an Agent
      reads is a claim rather than an identity.

   *  router is the publishing Runtime's own address and MUST NOT appear
      in either list.  Every address is one bare addr-spec (Section 5).

   *  *Both lists are always present*, and empty when that lane is
      closed for this Agent; mail_to is empty in a delegation-only
      deployment.  Absent and empty MUST NOT be conflated.

Rapp & Adams              Expires 24 April 2027                [Page 42]
Internet-Draft                    AMMP                      October 2026

   *  *Written whole and atomically.* A Runtime MUST NOT publish a newly
      adopted instance's address to others before it has adopted that
      instance itself.

   *  *A Runtime that publishes the directory MUST ensure the Agent can
      read it*, and SHOULD expose it read-only.  Publishing to a path
      the Agent's Namespace does not expose is not conformant: the write
      succeeds, the file is correct, and no Agent can read it — a
      failure that reports nothing on either side.  Publishing is
      optional, so a Runtime that cannot make the file readable MUST NOT
      publish it; an unreadable directory is indistinguishable from an
      absent one to a consumer, and worse than an absent one to an
      operator, who has a file on disk that looks like it is working.
      The trap follows from the location: the file deliberately belongs
      to no lane, which is what keeps a delegation-only deployment from
      materialising a mail tree, and is exactly what places it outside
      an exposure built lane by lane.

   *  *written_at* is the only staleness signal a consumer gets: an
      honest marker, not a freshness guarantee.

   *  *Where the consumer is an Agent rather than a parser, these become
      obligations to convey.* A Connector that surfaces the directory to
      its Agent instead of interpreting it satisfies the obligations
      above by stating them, under the presentation rule in Section 7:
      the Agent is the party forming a belief, so the Connector MUST
      convey that an address's presence is not a prediction of
      acceptance, that the file may be absent, that an empty list and an
      absent list differ, and that written_at is the only staleness
      signal there is.  A Connector with no directory parser does not
      thereby satisfy them vacuously.

   *  *The file MAY be absent* — before first adoption, on a Runtime
      that does not implement this section, or after a failed write —
      and a consumer MUST tolerate absence exactly as it tolerates a
      missing Binding Record (Section 9).

   *  *A Runtime that cannot write it keeps routing.* The directory is
      advisory, so a failed write loses a convenience, not the ability
      to enforce; failing closed would convert an advisory artifact into
      a dependency.  The Runtime MUST log the failure.  The Agent's only
      signal remains written_at going stale.

Rapp & Adams              Expires 24 April 2027                [Page 43]
Internet-Draft                    AMMP                      October 2026

11.  The Fleet Roster

   A Runtime MAY publish a fleet roster: a single file, written for the
   whole fleet rather than per instance, naming the instances the
   Runtime has admitted.  The Peer Directory (Section 10) answers which
   peers an Agent may address; the roster answers only which instances
   exist.  It is a separate artifact, and no obligation of Section 10
   applies to it except where this section states one.

   *  *Existence is not authorisation.* A member's presence says the
      instance exists.  It does not say this Agent may address it, and
      it is not a prediction that a submission to it will be accepted:
      authorisation is evaluated by the Runtime at submission time, and
      the Result (Section 6) is the only thing that reports what
      happened.  A Connector MUST NOT treat membership as authorisation
      or as a prediction of acceptance.

   *  *Not a source of identity.* The roster is fleet-wide, so the
      reader's own address MAY appear in it.  A consumer MUST NOT derive
      its own identity from the roster and MUST NOT treat its own entry
      as a peer: attribution is the Namespace a file arrived in
      (Section 4), and an address read from a file is a claim rather
      than an identity.

   *  *Write-only from the Runtime's side.* A Runtime MUST NOT read the
      roster back, for any purpose, for the reasons given for the
      directory in Section 10.

   *  *Written whole and atomically.* A Runtime MUST NOT list a newly
      admitted instance before it has adopted that instance itself.
      router is the publishing Runtime's own address and MUST NOT appear
      in members.  Every address is one bare addr-spec.

   *  *Unknown is not empty.* members is always present.  When the
      Runtime cannot determine membership, it MUST NOT write an empty
      members list; it leaves the previous file in place to age.
      written_at is the only staleness signal a consumer gets, and
      interval_s, when present, is the Runtime's nominal rewrite
      interval to judge it against.  A consumer MUST treat an absent
      roster, or one it judges stale, as unknown, and MUST NOT treat it
      as an empty fleet.  Unknown is not unusable: an Agent may still
      address a member of a stale roster, and the Runtime decides; what
      it must not do is conclude that an instance is absent from the
      fleet.  This section fixes no freshness bound; how stale is stale
      is the consumer's judgement, made against interval_s where it is
      present.

Rapp & Adams              Expires 24 April 2027                [Page 44]
Internet-Draft                    AMMP                      October 2026

   *  *Readability is split between the parties that can observe it.* A
      Runtime cannot observe a mount.  It MUST publish the roster only
      into a location the deployment designates for it, and MUST NOT
      create that location.  The deployment MUST expose that location
      read-only to each Agent it points at the roster, and SHOULD stop
      pointing Agents at the roster before, or together with, ceasing to
      expose it.  A roster written where nothing is exposed is inert,
      not a violation: an Agent is only ever pointed at a roster its
      deployment exposes.

   *  *Conveyed, where the consumer is an Agent.* Whatever points an
      Agent at the roster, whether a Connector that surfaces it or a
      deployment that tells the Agent where to read it, MUST convey that
      membership is neither authorisation nor a prediction of
      acceptance, that the roster is not the Agent's identity and its
      own entry is not a peer, and that an absent or stale roster means
      unknown.  Stating these in text the Agent receives whenever it is
      pointed at the roster satisfies this; no tool is required.

   The roster's shape is pinned by its schema (Appendix H) and by the
   roster-* fixtures (Appendix J).  The envelope is Runtime-authored and
   therefore open (Section 13.1): a consumer ignores members it does not
   recognize, and emitting one remains producer non-conformance.

12.  The Peer-Origin Profile (Optional)

   This section is an optional profile.  It gives agent-to-agent traffic
   between Agents of the same principal its own inbound tree, peer/, and
   states what that tree asserts, who may write it, how peers are
   addressed, how a reply is correlated with the message it answers, and
   how peer traffic is carried and verified between hosts.  A deployment
   that implements the profile is bound by every requirement in this
   section; a deployment that does not is unaffected.  No schema or
   fixture outside the profile depends on it, and the wire
   contract_version is the same for both.

12.1.  Scope

   The profile is optional because peer origin is needed by deployments
   in which Agents task one another, and by no deployment that only
   handles mail.

Rapp & Adams              Expires 24 April 2027                [Page 45]
Internet-Draft                    AMMP                      October 2026

   The tree is named for what it asserts, not for what it is used for:
   peer/ asserts authenticated, same-principal peer origin, and the
   document kind is peer.  What a Connector does with a peer message
   (delegation, review, notification) is the Connector's business.
   Because the kind and the tree share a name, the rule that a
   document's kind equals the name of the tree it was read from is
   mechanical.

   The profile adds an origin class and reuses mail threading for
   correlation.  It defines no call or return semantics, no method or
   operation member, and no status vocabulary for the work itself: a
   reply is prose authored by an Agent, with the posture of any other
   peer content.  This keeps the profile on the mail side of the line
   drawn in Section 1.5.

   The profile relies on two generic rules in Section 4.1: the _tree
   rules_ (every Runtime-owned inbound-class tree is an origin class,
   derived from the tree and never from a member, with notice_id
   uniqueness, content_ref confinement and write authority all per
   tree), and the _outbound extension space_ outbound/ext/<name>/, where
   delivery outcomes are kept (Section 12.8).  There is deliberately no
   inbound extension space.

12.2.  The Peer Tree

   <agent-id>/
     outbound/                   # as in the Volume Layout
     inbound/                    # mail
       notices/  messages/
     peer/                       # PROFILE: same layout, conventions
       notices/notice-<id>.json  #   and Sidecar rules as inbound/
       notices/<notice-id>.attachments/<i>
       messages/notice-<id>.json
     audit/

     Figure 8: Namespace layout with the peer tree (illustrative — not
                                 a fixture)

   *A sibling of inbound/, never a child.* Nesting the peer tree inside
   the mail tree would put the tree the mail deliverer must not write
   inside the tree it must write, so the write-authority partition
   (Section 12.3) would have to be carved out of a mount instead of
   being one.  As a sibling, the partition coincides with mount
   granularity.  Every obligation on inbound/ in Section 4 applies to
   peer/ unchanged: it is Runtime-owned; it MAY be mounted read-only; a
   Connector MUST NOT require write access to it; only the Runtime
   deletes from it; and its retention is conservative and age-based.

Rapp & Adams              Expires 24 April 2027                [Page 46]
Internet-Draft                    AMMP                      October 2026

   *Its own schema.* A Peer Notice is pinned by the peer-notice schema
   (Appendix F): the Deliver Notice shape with kind fixed to "peer" and
   message.mailbox fixed to "peer", message.from pinned to a bare addr-
   spec (Section 12.4), message.id, message.in_reply_to and
   message.references[] pinned to the notice_id character set
   (Section 12.5), and the optional Runtime-asserted
   message.sender_exposure (Section 12.7).  Attachment Descriptors are
   exactly those of Section 7.2.  The body spool uses the Inbound
   Message schema (Appendix D) unchanged.  Because a Peer Notice appears
   only in a tree that a mail-only consumer never reads, the closed kind
   vocabulary of the Deliver Notice is untouched.

   *The cross-lock.* A deliver document under peer/notices/ fails the
   peer-notice schema, and a peer document under inbound/notices/ fails
   the Deliver Notice schema.  Both are dropped under the rule of
   Section 7.1.

   *The placer mints notice_id.* The component that places a notice into
   a tree mints its notice_id.  Where a tree has more than one writer,
   each MUST mint so that a collision between writers is negligible: at
   least 128 bits of entropy under the notice_id character set, or a
   writer-specific prefix.  A consumer that keys state across trees MUST
   include the tree in the key.  On a single host, a Runtime MAY make
   the notice_id equal to the peer message id (Section 12.5).  Between
   hosts they differ, because the receiving Runtime mints the notice_id
   and the message id is the sender's.  A consumer MUST NOT assume that
   they coincide.

   *content_ref is confined to its tree.* Bytes resolve only within the
   notices/ directory of the tree the enclosing document was read from,
   recomputed from that document's notice_id and the Descriptor's index.
   A Peer Notice cannot point into the mail Sidecar directory, or the
   reverse.

   *Advisory members.* provenance on a Peer Notice is redundant with the
   tree and MAY be omitted; if present, it is advisory, and a consumer
   MUST NOT derive or override the origin class from it.
   sender_standing, if present, SHOULD be the existing agent-tier value.

12.3.  Origin Class Follows the Tree

   Write authority per tree is a behavioral obligation of the same kind
   as read-only inbound tolerance, single-writer and single-drainer
   operation, and ingest by copy (Section 4.1): invisible to a document
   validator, and checked operationally.  If it were left to
   deployments, the separate tree would be decorative, because one
   writer that chose the tree by inspecting a member would be routing on
   a member with one level of indirection.  The mechanism that enforces

Rapp & Adams              Expires 24 April 2027                [Page 47]
Internet-Draft                    AMMP                      October 2026

   it (user identities, mount sets, a host boundary) is out of scope;
   the property is not, and this section prescribes a check for it.

   *Connector obligations.* The tree a notice is read from is the
   Runtime's sole assertion of its origin class: inbound/ is mail, and
   peer/ is same-principal peer origin.  A Connector MUST derive the
   class from the tree, and MUST NOT derive or override it from any
   member of the document.  A document whose kind does not equal the
   name of the tree it was read from is dropped under the rule of
   Section 7.1.  A Connector that cannot establish which tree a document
   came from MUST treat it as mail.

   The peer tree asserts authenticated origin: a same-principal Agent,
   reaching this Namespace through a path restricted to authorized peer
   senders.  It asserts nothing about content.  Peer content is authored
   by a peer and unscreened, and may paraphrase external mail
   (Section 12.7).  How a Connector treats each class is out of scope;
   that it distinguishes the classes only by tree is not.  Like the
   untrusted-content posture of Section 7, this obligation protects an
   Agent from its own inbox, and is stated as a Connector obligation
   rather than as a property the Runtime relies on.  The invariant of
   Section 3.2 is unaffected.

   *Write-authority partition (Runtime).* Write authority over peer/
   MUST be distinct from write authority over inbound/. A component that
   admits messages from a transport not restricted to authorized peer
   senders (the _admitting component_, of which a mail deliverer is the
   usual example) MUST NOT hold write authority over peer/. The
   restriction MUST be structural, and MUST NOT be configuration that
   the component reads.  The prescribed operational check is a write
   attempt against peer/ from inside the admitting component, which MUST
   fail.  The rule is one-directional: a component holding peer write
   authority MAY write inbound/, as the delivery failure notice of
   Section 12.5 requires.

   A Runtime MUST NOT write a notice into peer/ on the basis of any
   claim the message makes about itself.  It MAY write there only when
   the message arrived through a path whose write authority is already
   restricted to authorized peer senders, or when the message carries a
   signature that verifies against a configured peer key (Section 12.6).

Rapp & Adams              Expires 24 April 2027                [Page 48]
Internet-Draft                    AMMP                      October 2026

   *Verification placement (Runtime).* Verification MUST be performed by
   a component holding peer write authority, which receives candidates
   through a location that is neither tree (Section 12.6.10).  The
   admitting component MUST NOT perform verification.  A verdict
   computed by one component and acted on by another would cross a trust
   boundary as data, which is what this rule keeps out of the decision;
   and the verifier processes hostile input, so it needs to be the
   component whose write authority the outcome concerns, not one that
   parses raw mail.

12.4.  Addressing

   *A peer address is a bare addr-spec*, under the same pattern and
   exclusions that Section 5.1 fixes for draft.to, with no display name.
   Its local part is the Agent's name within its Runtime, and its domain
   is the Runtime's authority.  A deployment confined to one host, with
   no mail, MAY use a non-routable domain.  Traffic between hosts
   requires a routable domain, because it travels as mail
   (Section 12.6).  "Routable" means deliverable by the mail system that
   carries the deployment's peer traffic; the domain need not resolve in
   the public DNS, because peer keys are configured rather than looked
   up (Section 12.6.7).  A peer domain MUST belong to exactly one
   Runtime among those configured as peers, so a deployment that may
   peer across organizations should use domains it controls.  Where an
   Agent has a bound mailbox (the Binding Record's address, Section 9),
   the deployment SHOULD use it, so that the Agent has one identity on
   both lanes.

   An addr-spec is used rather than an opaque identifier with a
   directory, or a URI scheme, because between hosts the transport
   address is an email address.  Any other scheme would need a mapping
   onto one, and someone to run the mapping.  With an addr-spec, the
   domain names the authority, the peer-key table is keyed by domain,
   and a signature has something to bind.  The grammar, the schema
   pattern and the header-injection discipline already exist.

   *Sending needs no new wire shape.* A sending Agent writes an ordinary
   Submit Request (Section 5) whose draft.to names the peer.  The
   Runtime's recipient policy recognizes a declared edge and routes the
   request to the peer tree rather than to mail.  The Result vocabulary,
   outbound Sidecars and reply key are the existing ones.  Whether a
   recipient is a peer is Runtime policy, and out of scope, like an
   allowlist.

   *message.from on a Peer Notice is authenticated.* It is the one from
   in this protocol that a consumer may treat as authenticated origin: a
   bare addr-spec, pinned by the schema, and asserted by the Runtime
   from the restricted write path or from a verified statement

Rapp & Adams              Expires 24 April 2027                [Page 49]
Internet-Draft                    AMMP                      October 2026

   (Section 12.6).  A Runtime MUST NOT copy it from text the sender
   supplied.  This is the opposite of the rule for mail from
   (Section 7.1).  A Connector's sender check may key on it, because the
   Runtime asserted it.

   *A configured peer key vouches only for its own Runtime's domains*
   (Section 12.6.7).  Without this, one Runtime's key could sign a
   message claiming an address in another Runtime's domain.

   *One address, one Namespace.* A peer address resolves to exactly one
   Namespace, as every address does in this protocol: one Namespace, one
   Connector instance, one Agent.  Sessions are below the Connector, and
   this document does not define them.

   A peer message is a single unit addressed to one Agent.  A Connector
   MUST NOT cause it to be acted on more than once.  A Connector that
   cannot identify a single target MUST hold the notice rather than fan
   it out, and SHOULD report the condition through its own side channel
   (Section 12.8).

   "One Agent, one live session; refuse when there are several" is one
   conformant implementation, not the only one.  A finer target (a named
   session) is out of scope.  A coarser one (a pool) is a Runtime alias
   that expands to several addresses, each resolving to one Namespace,
   and is not a change to addressing.

12.5.  Correlation and Replies

   *The peer message id.* The sending Runtime mints the peer message id
   when it accepts the submit.  The id MUST be globally unique and
   unguessable, with at least 128 bits of entropy, and MUST be spelled
   under the notice_id character set (^[A-Za-z0-9_][A-Za-z0-9_.-]*$).
   It has exactly one spelling inside every AMMP document: message.id,
   message.in_reply_to and message.references[] on a Peer Notice,
   message_id on a peer-routed Result, and the reply key a sender
   submits all carry the bare id.  When the id is carried in a mail
   header, it is encoded as <id@domain>, where domain is the domain of
   the sender's address (Section 12.6.3).  The encoding is applied on
   the way into the header and removed on the way out, and MUST NOT
   appear in an AMMP document.

   The reverse edge and every ledger depend on two properties of the id:
   that it is unguessable, and that it has one spelling end to end.
   Bare 32-character hexadecimal satisfies both, and so does unpadded
   base64url (subject, between hosts, to Section 12.6.1).  A mail
   notice's in_reply_to remains an [RFC5322] identifier; the Deliver
   Notice schema is unchanged.

Rapp & Adams              Expires 24 April 2027                [Page 50]
Internet-Draft                    AMMP                      October 2026

   *A reply is a Peer Notice in the reverse direction*, on the same
   tree, correlated with the message it answers.  It is not a third kind
   or a third tree: the tree grants the class, and a reply deserves the
   class of the message it answers.  There is no correlation member;
   mail threading already provides one, and between hosts it is
   [RFC5322] threading.

    +===========================+====================================+
    | Member                    | On the peer lane                   |
    +===========================+====================================+
    | message.id (Peer Notice)  | The peer message id.               |
    +---------------------------+------------------------------------+
    | message_id (the sender's  | MUST equal the peer message id, so |
    | Result)                   | that the sender learns it from its |
    |                           | own Result. reason_code SHOULD be  |
    |                           | peer_routed, so that the sender    |
    |                           | can tell the lanes apart (an open  |
    |                           | vocabulary, Section 6.1).          |
    +---------------------------+------------------------------------+
    | message.in_reply_to (Peer | REQUIRED on a reply, and equal to  |
    | Notice)                   | the id being answered.  A new task |
    |                           | omits it.                          |
    +---------------------------+------------------------------------+
    | message.references[]      | Multi-step threads, as in mail.    |
    | (Peer Notice)             |                                    |
    +---------------------------+------------------------------------+
    | in_reply_to or            | How a responder names the message  |
    | draft.reply_to_message_id | it answers.                        |
    | (Submit Request)          |                                    |
    +---------------------------+------------------------------------+

              Table 3: Correlation members on the peer lane

   task_id remains an optional, Runtime-minted thread handle
   (Section 8).

Rapp & Adams              Expires 24 April 2027                [Page 51]
Internet-Draft                    AMMP                      October 2026

   *Reverse edges.* The authorization graph is directed, yet a reply
   must be able to flow from B to A when only A to B is declared.  A
   Runtime permits a reverse message when its reply key names a peer
   message in the opposite direction that the Runtime itself issued or
   placed (at submission, one it placed for the submitting Agent; at
   reception, one it issued to the replying Agent), within a configured
   window.  This consults a member, but checks it against state the
   Runtime minted, which is the posture Section 8 takes for threading:
   correlation, not trust.  Because ids are unguessable, the key is not
   a forgeable capability.  Several replies to one id within the window,
   and a reply to a reply under the id the Runtime issued for it, are
   conformant.  This document does not fix the window's length; it
   requires only that the window exists and is configured.

   *An unresolved reply key is refused, not stripped.* A reply key
   (in_reply_to, or else draft.reply_to_message_id) that the Runtime
   cannot resolve from its own ledger MUST produce a rejected Result
   with reason_code unresolved_reply.  A Runtime MUST NOT strip the key
   and route the message on draft.to as a new peer message.  A key that
   resolves but whose reverse edge is not permitted (no declared edge,
   or an expired window) MUST likewise produce a rejected Result
   (reason_code peer_reply_window_expired, or the Runtime's own code),
   and MUST NOT be re-routed.  A Peer Notice MUST carry in_reply_to if
   and only if the placing Runtime resolved it from its own ledger to a
   peer message id that it issued or placed.

   The reason is the reason this profile exists.  On the peer lane, a
   message the sender meant as a reply, which the Runtime quietly turned
   into a new task, arrives as a new instruction.  That is worse than a
   rejection the sender can act on by resubmitting without the key.  The
   rule is a requirement rather than a permission because a Connector
   may build policy on it (for example, admitting a replier it would not
   otherwise accept), and that is sound across a deployment only if
   every conformant Runtime upholds it.  Such a policy MUST NOT widen
   anything else about the notice's treatment.

   Between hosts, the sending Runtime has already rejected an
   unresolvable key at submission.  A verified statement whose
   in_reply_to the _receiving_ Runtime cannot resolve therefore
   indicates a stale or forged capability from a configured peer.  The
   receiver MUST NOT place it in peer/ with the key, MUST NOT place it
   as a new peer task, and MUST log the cause, and it SHOULD tell the
   sending Runtime with a signed Control Notice (Section 12.6.8).  The
   ledger binds the participants as well as the id (Section 12.6.6).
   The statement is not demoted to mail either: its origin was verified,
   only its correlation claim failed, and routing it as mail would be a
   third silent transformation.

Rapp & Adams              Expires 24 April 2027                [Page 52]
Internet-Draft                    AMMP                      October 2026

   *Two things called a result.* A _work result_ is the responder's
   content for the requester, described above.  A _delivery outcome_ is
   mechanical feedback from a Connector to its Runtime (delivered, held,
   and so on).  A delivery outcome is not a peer message; it is kept in
   outbound/ext/<name>/ (Section 12.8).

   *Delivery failure notices.* A Runtime that reports a terminal
   delivery failure to the sending Agent SHOULD do so as a deliver
   Notice in the sender's inbound/ tree, with message.from set to the
   Runtime's own address (amap.router@<domain> or equivalent),
   in_reply_to set to the peer message id, and a body authored by the
   Runtime alone.  Such a notice MUST NOT be placed in peer/, and MUST
   NOT quote or forward any text from the receiving Connector's outcome
   file.

   The Runtime is never an allowed peer sender, so this keeps it out of
   every Connector's peer policy.  The mail tree's untrusted-content
   posture is the right one for "your message did not arrive".  And the
   outcome file is text written by the receiver, so forwarding it would
   let one Agent put prose in front of another under the Runtime's name.
   A component holding peer write authority that writes inbound/ is the
   one direction Section 12.3 permits.  The peer message id appears in a
   mail-tree notice, but only to the sender, who already holds it from
   its own Result, so no capability is disclosed.

12.6.  Peer Traffic Between Hosts

   Peer traffic between hosts is mail.  The sending Runtime signs it
   with its own key; there are no per-Agent keys.  Traffic on one host
   needs no signature, because local write permission is a stronger
   guarantee, although a Runtime MAY sign it too, for a uniform code
   path, without changing its treatment.  A receiving Runtime places a
   message from another host in peer/ only when every rule in this
   section holds.  Otherwise it hands the message to mail admission,
   drops it, or refuses it, as Section 12.6.4 specifies.

12.6.1.  The Statement

   The sending Runtime builds a _statement_, a small JSON object, signs
   its exact bytes with Ed25519 (Section 5.1 of [RFC8032]), and carries
   the bytes and the signature in one header field (Section 12.6.2).
   The receiver verifies the signature over the bytes it received
   _before_ parsing them.  Nothing is re-serialized: no canonicalization
   is defined, and whatever serialization the sender chose is the one
   that is signed.

Rapp & Adams              Expires 24 April 2027                [Page 53]
Internet-Draft                    AMMP                      October 2026

   The peer-statement schema (Appendix F) pins the statement: version v
   1, closed at every level, with the members v, key_id, kind ("peer"),
   id, in_reply_to (optional), from, to, ts, subject, body_sha256 (the
   digest of the body the receiver will publish), attachments (each with
   sha256, size_bytes, filename and media_type, in leaf order),
   sender_exposure (optional, Section 12.7) and sender_since (optional,
   Section 12.6.9).

   Unlike the Runtime-authored notices (Section 13.1), the statement is
   closed, because it is signed bytes that another Runtime acts on.  A
   member the receiver does not know is a member it cannot bind to what
   it publishes, and v is the path for evolution.  The schema states the
   shapes.  The following rules, which JSON Schema cannot express, are
   also part of the statement:

   *  *Formats.* ts, sender_since and the timestamps in sender_exposure
      are [RFC3339] date-times in UTC, in whole seconds, with the Z
      designator, and each MUST denote a real instant.  Digests are
      SHA-256 [FIPS180-4], in lowercase hexadecimal.  Every number is a
      lexical integer, with no fraction and no exponent, so 1.0 is not
      1.  No string contains an unpaired surrogate (Section 8.2 of
      [RFC8259]).

   *  *Ids.* A sending Runtime MUST mint the ids it sends between hosts
      under ^[A-Za-z0-9_][A-Za-z0-9_-]*$, which is the notice_id
      character set (Section 12.5) without ".", with at least 128 bits
      of entropy, so that <id@domain> is a valid msg-id (Section 3.6.4
      of [RFC5322]).  Hexadecimal satisfies this; a base64url id whose
      first character is "-" MUST be minted again.  The statement schema
      enforces the pattern for id and in_reply_to.  An id used only on
      one host MAY also contain ".", as the notice_id character set
      allows; an id sent between hosts MUST NOT.

   *  *to* is one address: a peer message is addressed to one Agent
      (Section 12.4).

   *  *sender_exposure* obeys Section 12.7: window_start is not later
      than window_end; last_external_delivery_ts is present if and only
      if external_mail_delivered is true, and then lies within the
      window; and asserted_by is the signing Runtime's configured
      Runtime address (Section 12.6.7).

   *  *sender_since*, when present, is not later than ts.

12.6.2.  Carriage, and the Candidate the Verifier Sees

   *The header field.*

Rapp & Adams              Expires 24 April 2027                [Page 54]
Internet-Draft                    AMMP                      October 2026

   AMAP-Peer-Statement: k=<key_id>; s=<statement>; b=<signature>

   *  s is the statement's bytes, and b is the 64-byte signature over
      exactly those bytes.  Each is base64url without padding (Section 5
      of [RFC4648]).

   *  k is the statement's key_id, in the clear: the receiver verifies
      before it parses, so it must choose the key before it can read the
      statement.  The signed key_id MUST equal k.

   *  The field name is matched case-insensitively.  The sending Runtime
      MUST emit exactly one such field.

   *  The sender MUST fold the field so that no line exceeds 998 octets
      (Section 2.1.1 of [RFC5322]), and SHOULD fold at 78.  It may
      insert folding whitespace anywhere, because the receiver removes
      all of it.

   *  A statement MUST NOT exceed 8192 bytes.  A sending Runtime that
      would otherwise emit a larger one SHOULD refuse the submit
      instead, with a rejected Result whose reason_code is
      peer_statement_too_large.

   *  The sending Runtime MUST also set Message-ID to <id@domain>, where
      domain is the domain of from, and SHOULD set In-Reply-To and
      References to the encoded forms (Section 12.5), so that mail
      threads coherently and a demoted message still threads as mail.

   *The message shape.* A sending Runtime SHOULD send either a single
   text/plain part, or multipart/mixed holding one text/plain part
   followed by the attachments.  The text part SHOULD be charset=utf-8
   with base64 transfer encoding, so that transport cannot rewrite it.
   The sender hashes the UTF-8 encoding of the body after replacing
   every CRLF pair with LF.  Nothing else is normalized: a bare CR, a
   bare LF, trailing whitespace and a leading dot are all content.

   *The selection rule.* The admitting component reduces a message to a
   candidate, and the rule is the same at every receiver:

   *  *Leaves.* Walk the MIME tree [RFC2046] depth-first, descending
      only into multipart/*. Every other part is a leaf, including
      message/rfc822, whose octets are its part body as transmitted.

   *  *Body.* The first leaf of type text/plain whose Content-
      Disposition [RFC2183] is not attachment is the body.

Rapp & Adams              Expires 24 April 2027                [Page 55]
Internet-Draft                    AMMP                      October 2026

   *  *Attachments.* Every other leaf, in walk order, is an attachment:
      a text/html alternative, an inline image, or a second text/plain
      part.

   *  *No body leaf* means the empty body.

   A different structure is not forbidden; it is simply likely to fail
   the attachment count or the body digest, and so to be demoted.

   *The candidate.* The intake location (Section 12.6.10) MUST hold the
   following, in a layout this document does not prescribe:

   *  *Field bodies.* The body of each AMAP-Peer-Statement, AMAP-Peer-
      Control and Message-ID field, in message order.  The admitting
      component MAY unfold a field body or trim its surrounding
      whitespace, and MUST NOT otherwise alter it.  A field body over
      12288 octets MUST be carried as an _oversize_ marker, and MUST NOT
      be truncated.  The 12288-octet limit applies to the field body as
      transmitted, before unfolding or trimming.  A field body the
      admitting component cannot carry as ASCII text (a non-ASCII octet,
      or an undecodable value) MUST be carried as an _invalid_ marker in
      the field's position.  A marker keeps the field's identity and
      multiplicity: the admitting component MUST NOT omit a field,
      because omitting a second, malformed statement field would leave
      one well-formed field, and the verifier would select it.

   *  *The body leaf*, if there is one: its octets after transfer
      decoding, and the value of its charset parameter, if any.

   *  *Each attachment leaf:* its octets after transfer decoding, in
      order.

   The verifier, not the admitting component, decodes the body octets
   strictly.  The charset label, compared case-insensitively, MUST be
   utf-8 or us-ascii; an absent label means us-ascii.  Any other label,
   or octets that are invalid in the labelled charset, fail check 9
   (Section 12.6.3).  The verifier then replaces CRLF pairs with LF and
   hashes the result, and that same text is what it publishes as
   body_text.

   *What is published.* On placement, the receiver takes all of the
   following from the verified statement, and MUST NOT take them from
   headers: message.id, in_reply_to and from; subject; sender_exposure;
   and the attachment Descriptors' filename, media_type, size_bytes and
   sha256. to selects the Namespace.  The receiver MUST NOT publish
   references from headers; it MAY derive them from its own ledger
   (Section 12.6.6).  The byte sniffing, caps and disposition rules of
   Section 7.2 still apply to the attachment bytes.

Rapp & Adams              Expires 24 April 2027                [Page 56]
Internet-Draft                    AMMP                      October 2026

   *Comparisons.* Addresses (from, to, the edge graph, the ledger and
   Runtime addresses) compare the local part exactly and the domain
   case-insensitively in ASCII.  Ids compare exactly, because they are
   opaque.  After unfolding and trimming, Message-ID MUST be exactly
   "<", the id, "@", the domain and ">", with the id equal to the
   statement's id and the domain equal to the domain of from.

   *Logging.* A receiver MUST NOT log a raw header field value
   unescaped.  Fields are supplied by the attacker, and k is validated
   against the key_id pattern before it is used for anything.

12.6.3.  Verification

   The receiver applies these checks in this order.  The first one that
   fails decides the outcome (Section 12.6.4).  Only a candidate that
   passes every check is eligible for peer/.

   1.   *The field.* There is exactly one AMAP-Peer-Statement field;
        with none, the message is not a peer candidate, and is ordinary
        mail.  A message that also carries an AMAP-Peer-Control field is
        dropped (Section 12.6.8).  The field body is ASCII and is not
        oversize.  With every SP, HTAB, CR and LF removed, it consists
        of exactly the tags k, s and b, each once, in any order,
        separated by ";". k matches the key_id pattern. s and b are
        unpadded base64url; b decodes to 64 bytes, and s to at most
        8192.

   2.   *Known statement.* If the SHA-256 digest of the statement bytes
        equals that of a statement the receiver has recorded and still
        retains (Section 12.6.5), the candidate is a duplicate.  This
        check needs no key, and holds whatever else has changed since: a
        timestamp now outside the window, a key since expired or
        removed, or a body modified in transit.

   3.   *Key.* k names exactly one configured key, valid at the
        receiver's clock (not-before ≤ now ≤ not-after, inclusive).

   4.   *Signature.* The signature is valid for that key over the
        received bytes, under the verification policy of Section 12.6.7.

   5.   *Statement.* (a) The bytes are UTF-8 without a byte-order mark,
        and parse as JSON with no repeated member name and no non-JSON
        constant.  They validate against the schema, and key_id equals
        k. (b) Every rule of Section 12.6.1 that the schema cannot
        express holds.

Rapp & Adams              Expires 24 April 2027                [Page 57]
Internet-Draft                    AMMP                      October 2026

   6.   *Key domain.* The domain of from is one of the domains
        configured for the Runtime that owns the key.  A key vouches
        only for its own Runtime's domains (Section 12.4).

   7.   *Freshness.* ts lies within the window of Section 12.6.5.  While
        peer admission is closed (no trustworthy bound, Section 12.6.5),
        the window is undefined: the candidate is _held_ in intake,
        undisposed, until admission reopens.

   8.   *Exposure asserter.* If sender_exposure is present, asserted_by
        is the configured Runtime address of the Runtime that owns the
        key.

   9.   *Body.* The body decodes under the charset rule of
        Section 12.6.2, and its digest equals body_sha256.  The verifier
        MUST hash bytes it holds itself, and MUST NOT use a digest that
        the admitting component computed.

   10.  *Attachments.* The count matches; then, for each attachment in
        order, sha256 and size_bytes match the octets.

   11.  *Message-ID.* There is exactly one such field, matching
        Section 12.6.2.

   12.  *Reservation.* No record exists for the pair (the configured
        Runtime that owns the key, id) (Section 12.6.5).  The candidate
        is reserved, atomically.

   A reserved candidate is then _decided_, and the following are not
   verification failures:

   *  if to is not hosted here, the candidate is misrouted;

   *  if in_reply_to is present, it must resolve and be permitted
      (Section 12.6.6);

   *  otherwise, the receiver's own graph must have the edge from from
      to to (Section 12.6.6);

   *  any deployment admission policy is applied (Section 12.6.9);

   *  otherwise, the decision is placement.

   The decision MUST be persisted on the record before it has any effect
   (Section 12.6.5).  Only a persisted placement decision is ever
   published; a persisted refusal or drop MUST NOT be published,
   whatever happens afterwards.

Rapp & Adams              Expires 24 April 2027                [Page 58]
Internet-Draft                    AMMP                      October 2026

12.6.4.  Outcomes

   +============+========================================+=============+
   | Outcome    | When                                   | Control     |
   |            |                                        | Notice      |
   +============+========================================+=============+
   | peer       | Every check passes, and the            | none        |
   |            | message is routed and permitted.       |             |
   +------------+----------------------------------------+-------------+
   | held       | Check 7 while peer admission is        | none        |
   |            | closed (Section 12.6.5); not yet       |             |
   |            | disposed of.                           |             |
   +------------+----------------------------------------+-------------+
   | mail, no   | No AMAP-Peer-Statement field.          | none        |
   | statement  |                                        |             |
   +------------+----------------------------------------+-------------+
   | mail,      | A failure of check 1 (other than       | SHOULD, for |
   | demoted    | the both-fields drop below), 3,        | 5(b) and 6  |
   |            | 4, 5, 6, 7, 8, 9, 10 or 11.            | to 11       |
   +------------+----------------------------------------+-------------+
   | drop: both | An AMAP-Peer-Statement and an          | none        |
   | fields     | AMAP-Peer-Control field in one         |             |
   |            | message (Section 12.6.8).              |             |
   +------------+----------------------------------------+-------------+
   | drop:      | Check 2 (a known statement) or         | SHOULD, for |
   | duplicate  | check 12 (a known id).                 | check 12    |
   |            |                                        | only        |
   +------------+----------------------------------------+-------------+
   | drop:      | to is not hosted here.                 | SHOULD      |
   | misrouted  |                                        |             |
   +------------+----------------------------------------+-------------+
   | refuse:    | Section 12.6.6.                        | SHOULD      |
   | unresolved |                                        |             |
   | reply      |                                        |             |
   +------------+----------------------------------------+-------------+
   | refuse: no | Section 12.6.6.                        | SHOULD      |
   | edge       |                                        |             |
   +------------+----------------------------------------+-------------+
   | refuse:    | A deployment admission policy          | SHOULD,     |
   | policy     | (Section 12.6.9).                      | with        |
   |            |                                        | policy_code |
   +------------+----------------------------------------+-------------+

            Table 4: Outcomes for a candidate from another host

   *Mail, demoted.* Demotion is a classification, not a delivery.  The
   candidate goes to ordinary mail admission and the deployment's lane
   policy; where that policy gives the pair no mail lane, it is not

Rapp & Adams              Expires 24 April 2027                [Page 59]
Internet-Draft                    AMMP                      October 2026

   delivered.  The demoted notice MUST NOT carry sender_exposure, the
   statement or the signature, and carries the mail-admission verdict
   the admitting component attached.  Mail admission MUST NOT treat an
   invalid statement as evidence of who sent the message.

   *Drop and refuse.* A dropped or refused candidate is neither placed
   nor handed to mail admission.  Every outcome other than peer and
   "mail, no statement" MUST be logged with its cause.  A refusal is not
   demoted, because the origin was verified and only an authorization or
   correlation claim failed; routing it as mail would be a silent
   transformation (Section 12.5).

   *Who is told.* A Control Notice (Section 12.6.8) MUST be sent only
   when every one of these holds; a configured k alone is never enough:

   *  checks 1 to 5(a) passed, so the statement is authenticated by a
      currently valid configured key and is well-formed enough to name.
      Before check 4 the sender is unauthenticated, and notifying the
      claimed sender would be backscatter; between checks 4 and 5(a),
      the statement cannot be named;

   *  the candidate was not a duplicate caught at check 2 (an exact
      retained statement: the first copy was already answered, and a
      replay on the path must not draw a second notice);

   *  the candidate is a statement, not a Control Notice: nothing is
      ever sent about a Control Notice (Section 12.6.8);

   *  the destination is the Runtime address configured for the key's
      owner, and never an address taken from the message.

   A "dropped, duplicate" notice (check 12) tells the sender that a
   record with that id already exists.  That record's disposition may
   have been placement or refusal: the notice proves a prior
   reservation, not that the task failed.  Whether and how the sending
   Agent learns of an outcome is governed by Section 12.6.8.

12.6.5.  The Replay Store, Windows and Recovery

   *Windows.* The receiver accepts ts when lower ≤ ts ≤ now + F, both
   bounds inclusive, where lower = max(H − P, floor):

   *  *F*, the allowance for clock skew, defaults to 300 seconds and
      MUST NOT exceed 900.

Rapp & Adams              Expires 24 April 2027                [Page 60]
Internet-Draft                    AMMP                      October 2026

   *  *P*, the past window, defaults to 3600 seconds, and MUST be at
      least 300 and at most 86400.  A message that arrives later than P
      is demoted, which is visible to the sender.  A longer P costs
      replay storage and a longer key retirement (Section 12.6.7).

   *  *H* is the latest receiver time ever observed, kept durably.  The
      lower bound uses H rather than the current clock, so that a clock
      that steps backwards cannot re-admit a statement whose record has
      been pruned.

   *  *floor* is set only by recovery and by window changes, both below.

   *Records.* Replay identity is scoped to the pair (configured Runtime,
   id), where the Runtime is the configured peer that owns the verifying
   key, so that rotating a key never resets it.  Each record holds the
   scope, the id and ts; the SHA-256 digest of the statement bytes; the
   notice_id minted at reservation; a state, which is reserved, decided
   or done; and, once decided, the _disposition_: placement, or a
   refusal or drop with its cause (and policy_code), and whether a
   Control Notice is still pending.

   *Recording.* The receiver records a candidate at check 12, after
   checks 1 to 11 have passed.  A candidate that fails any of them
   records nothing, so a mangled copy cannot suppress the intact one
   that follows.  Misrouted and refused candidates are recorded too, so
   that their replays are dropped quietly instead of being evaluated
   again each time.  Control Notices (Section 12.6.8) use the same
   store.

   *Reservation, decision and effect.* These are three durable steps, in
   order, each atomic:

   1.  *Reserve.* Check 12 creates the reserved record and mints a fresh
       notice_id.  The candidate stays in the intake location until the
       record is done.  A reservation is undecided, and authorizes
       nothing.

   2.  *Decide.* The receiver routes, correlates, permits and applies
       policy (Section 12.6.3), and persists the disposition on the
       record, which becomes decided.  A refusal or drop also marks its
       control notice as pending.

   3.  *Effect.* For a placement, the notice is written under the minted
       notice_id; for a refusal or drop, the Control Notice is durably
       queued.  Only then is the record done.  Placement is idempotent:
       a notice that is already present is not written again.

Rapp & Adams              Expires 24 April 2027                [Page 61]
Internet-Draft                    AMMP                      October 2026

   A copy that arrives while another copy is reserved or decided matches
   it at check 2, and is dropped.  On restart, the receiver MUST finish
   every record that is not done, exactly once, by resuming its
   _recorded_ state, and MUST NOT infer a disposition from the absence
   of a notice:

   *  a reserved record is decided then, under the configuration in
      force at recovery (the receiver has no older one it can trust),
      and then takes effect;

   *  a decided placement is placed if the notice is absent, and is
      marked done without placing it again if the notice is present;

   *  a decided refusal or drop is not published: its pending control
      notice is queued, once, and the record is marked done.

   An implementation MUST survive a crash at each of these points: after
   reservation, after the decision, after a notice is placed, after a
   control notice is queued, and after a terminal refusal is recorded,
   including when the graph or policy changed while it was down.

   *Retention.* The receiver MUST retain each done record while H ≤ ts +
   P, and MAY retain it longer.  It MUST NOT prune a record that is not
   done.  A known statement replayed after its retention has lapsed
   fails freshness, so it is demoted and never placed.  Duplicate
   suppression is therefore effective only within retention: this
   profile does not promise exactly-once delivery, and an implementation
   MUST NOT claim it.

   *Resending.* A sending Runtime whose hand-off was ambiguous MAY
   resend the same id, signed again with a new ts.  If the first copy
   arrived and is retained, the resend is dropped at check 12, and the
   control notice tells the sender so; if the first copy did not arrive,
   the resend is placed.  A sending Runtime that resends MUST keep the
   same id, including across a resend that a person approved.

   This permission is idempotent only within retention.  Once the first
   copy's record has been pruned, a resend of the same id under a fresh
   ts finds no record, and is placed a second time.  A deployment that
   cannot accept that MUST forbid signing again in its own deployment
   profile, and resend only the original bytes, which check 2 drops or
   freshness demotes.  The permission is kept for portability, not as a
   recommendation.

Rapp & Adams              Expires 24 April 2027                [Page 62]
Internet-Draft                    AMMP                      October 2026

   *Recovery.* The store and H MUST survive a restart.  If the receiver
   loses the store, or cannot show that a restored store is complete, it
   MUST restore this barrier: no statement that could already have been
   accepted under the lost coverage may be admitted again, including at
   the boundary, under a clock that stepped back, and under the largest
   F that was ever in effect.

   *  *With H intact*, the receiver sets floor to one second after
      max(C, H) + Fmax, persisted before admission resumes.  C is the
      moment from which it again holds complete evidence, H is the high-
      water clock at the loss, and Fmax is the largest F in effect at
      any point that could have admitted a statement still within the
      lost coverage.  Fmax is taken from the retained history of F if
      that history is provable, and is 900 otherwise.  Fmax MUST NOT be
      chosen from the wall-clock interval ending at C: after a clock
      rollback, that interval can miss a larger F that was in effect
      near H.  Retaining the largest F ever configured is safe.  Any
      statement accepted before the loss had ts ≤ H + F ≤ max(C, H) +
      Fmax, so none reaches the floor.  The floor is strictly above that
      bound: an inclusive floor would re-admit a statement accepted at
      exactly now + F, and C alone would re-admit one accepted before
      the clock stepped back.

   *  *With H lost as well*, no trustworthy bound exists, and the
      receiver MUST close peer admission: candidates reaching check 7
      are held in intake, and nothing is placed or demoted on a window
      the receiver cannot define.  Admission reopens only when the
      operator records a bound B that is at or after the largest
      receiver time before the loss.  The floor is then one second after
      B + Fmax, with Fmax determined as above: losing H does not by
      itself lose the history of F.  The held candidates are then
      evaluated in arrival order.  The current wall-clock time is not a
      bound.

   *Window changes.* Narrowing P or F takes effect at once.  Widening P
   MUST NOT re-admit anything: the receiver sets the floor to the lower
   bound in effect at the moment of the change, and the window then
   widens only as records accumulate.

12.6.6.  Correlation and Edges Between Hosts

   *The ledger.* A Runtime keeps a record for every peer message it
   issued to another host or placed.  Each record holds the id; the
   local Agent and the remote Agent; the direction (issued or placed);
   and an expiry, the configured reply window (Section 12.5).  The
   ledger is scoped to peer messages: a mail notice id never resolves.

Rapp & Adams              Expires 24 April 2027                [Page 63]
Internet-Draft                    AMMP                      October 2026

   *Resolution.* A verified statement carrying in_reply_to resolves only
   if a record exists whose id equals in_reply_to, whose local Agent
   equals to, whose remote Agent equals from, and which has not expired.
   Possession of an id is not authority: an id presented by another
   Agent on the same host, or by any Agent on another host, does not
   resolve.

   *Permission.* A resolved reply to a record this Runtime issued is a
   reverse message, and is permitted without an edge (Section 12.5).  A
   resolved message against a record this Runtime placed is a follow-up
   in the same direction, and needs the edge from from to to.  A
   statement without in_reply_to needs the edge.

   *The receiver's own graph.* The edge MUST be checked against the
   receiver's own graph.  The sending Runtime checked its edge at
   submission (Section 12.4), but which peers may task an Agent is the
   receiving Runtime's decision.  If correlation fails, the outcome is
   "refuse: unresolved reply"; if permission fails, it is "refuse: no
   edge".  Neither is demoted.

   *At submission.* The sending Runtime MUST apply the same binding: a
   reply key resolves only if the submitting Agent is the record's local
   Agent.  It SHOULD also refuse at submission a message it already
   knows the receiver will refuse, such as one for a pair it knows has
   no edge or is held by policy, so that the Agent learns of it at once.
   The receiver remains the authority.

12.6.7.  Keys

   *Configuration.* The operator configures, at every receiver, each
   peer Runtime's domain set, its Runtime address (amap.router@<domain>
   or equivalent), and its key set.  Trust anchors are configured, not
   discovered: there is no DNS publication, directory or certificate
   chain.  A receiver MUST refuse to load a configuration in which:

   *  a key_id occurs twice, across all peers (the receiver selects a
      key by k alone);

   *  a not-before time is later than its not-after time;

   *  a public key is not a canonical encoding of a curve point; or

   *  a public key is of small order.

   A sending Runtime SHOULD make its key ids globally unique, for
   example by deriving them from a fingerprint of the public key.

Rapp & Adams              Expires 24 April 2027                [Page 64]
Internet-Draft                    AMMP                      October 2026

   These are configuration-time checks, a gate separate from
   verification.  A bare Ed25519 implementation will construct and
   verify under a small-order or non-canonical key; the key loader, not
   the verifier, is what refuses one.  An implementation MUST
   demonstrate the refusal through its loader, and MUST NOT count
   passing the vectors marked for rejection at verification as covering
   it.

   *Rotation.* A receiver MUST support at least two concurrent keys per
   peer.  A rotation proceeds as follows:

   1.  Add the new key at every receiver.

   2.  Switch the sender to it.

   3.  If the sender stops using the old key at time T, receivers keep
       the old key valid until at least T + F + P, because a statement
       signed at T can carry a ts up to T + F, and arrive up to P after
       that.

   Validity is inclusive at both ends.  Revocation is removing a key or
   ending its validity.  Replay records are scoped to the Runtime rather
   than the key, so neither retiring nor revoking a key erases replay
   evidence (Section 12.6.5).

   *The signing key* is a Runtime credential (Section 3.1).  It MUST NOT
   be readable from any Agent Namespace or by the admitting component.
   It MUST NOT be used to sign anything except this profile's statements
   and Control Notices, which their kind members keep apart.

   *Verification policy.* Verification is Ed25519 as specified in
   [RFC8032], with the cofactorless equation [S]B = R + [k]A.  A
   verifier MUST reject a signature whose S is not less than L; a non-
   canonical encoding of R or A; and a key that failed the configuration
   checks above.  A point encoding MUST be decoded canonically before
   any other check is applied to it: the "negative zero" encoding of x =
   0 (y = p − 1 with the sign bit set) is non-canonical, and is rejected
   as such before a small-order check is reached.

   This document requires nothing about how Ed25519 is computed.  A
   vetted library, a system binary or any other implementation conforms
   if it produces these results.  The conformance suite holds the test
   vectors of Section 7.1 of [RFC8032] (TEST 1 to 3), and negative
   vectors marked for rejection either at verification or at
   configuration (Appendix J).  An implementation MUST pass both kinds
   through the implementation it ships.

Rapp & Adams              Expires 24 April 2027                [Page 65]
Internet-Draft                    AMMP                      October 2026

12.6.8.  Control Notices

   A receiving Runtime tells a configured sending Runtime what it did
   with a statement it did not place, using a signed _Control Notice_.
   The peer-control schema (Appendix F) pins it: version v 1, closed,
   with the members v, key_id, kind ("control"), id, from, to, ts,
   about, outcome, cause, and policy_code (with cause policy).

   *Form.* A Control Notice is carried in an AMAP-Peer-Control header
   field with the k, s and b grammar of Section 12.6.2, signed with the
   same per-Runtime key.  Its kind makes it impossible to read as a peer
   statement, and the reverse.  It carries no free text: the cause
   vocabulary is closed, and the receiving Runtime composes any words
   itself.  It travels as mail between Runtime addresses.

   *When.* A receiver SHOULD send one for every outcome Section 12.6.4
   marks, and MUST send one only when the eligibility conditions of
   Section 12.6.4 hold in full: never for a failure before check 5(b),
   never for a duplicate caught at check 2, never about a Control
   Notice, and never merely because a key was found.  There is one
   notice per record: the pending notice is part of the record's
   disposition (Section 12.6.5), MUST be queued durably before the
   record is done, and MUST be sent at most once, including across a
   crash.  A receiver MAY rate-limit Control Notices; any suppression
   MUST be logged.

   *policy_code* is defined by the deployment.  A receiving Runtime
   renders a description it defines locally.  An unknown token gets a
   generic description, and MUST NOT be interpreted.

   *Addressing.* from is the receiver's own Runtime address. to MUST be
   the Runtime address configured for the peer that owns the verifying
   key, and MUST NOT be an address taken from the message.  A control
   notice MUST NOT be sent to a Runtime that is not a configured peer.

   *Receiving one.* A Runtime applies checks 1 to 5 of Section 12.6.3,
   with AMAP-Peer-Control and the control schema in place of the
   statement's.  It then requires all of these: from is the Runtime
   address of the peer that owns the key; to is its own Runtime address;
   ts is fresh (Section 12.6.5); the notice's pair (Runtime, id) is new;
   and about names a ledger record that this Runtime issued to an Agent
   in the signer's domains.

Rapp & Adams              Expires 24 April 2027                [Page 66]
Internet-Draft                    AMMP                      October 2026

   *What it does.* It SHOULD give the issuing Agent a failure notice
   authored by the Runtime in inbound/ (the delivery failure notice of
   Section 12.5), with no text from the Control Notice except the
   vocabulary values.  Where it does, the failure notice is owed from
   the moment the record is reserved, and the record is done only once
   the failure notice is durable; a crash in between is finished by
   recovery, once.

   *One per message.* An issued record receives at most one failure
   notice.  A later Control Notice about the same about, under a
   different id, is dropped.  A "dropped, duplicate" report means that a
   record for the message exists at the receiver, whose disposition may
   have been placement; it is not proof that the task failed.

   *Failures.* A Control Notice that fails any rule is dropped and
   logged: it MUST NOT be placed, handed to mail admission, or answered.
   A message carrying both fields is dropped.

12.6.9.  Optional: Sender Age, and Deployment Admission Policy

   A deployment MAY refuse authenticated peer messages for reasons of
   its own, such as a first-sight quarantine for a new sender, or a
   requirement that every delegation between hosts carry
   sender_exposure.  The refusal is "refuse: policy", with a policy_code
   defined by the deployment.  The policies themselves are the
   deployment's business, not this profile's.

   Measuring a sender's age at the receiver, from first sight, would
   make every Agent's first delegation to each host fail.  The optional
   signed member sender_since exists to avoid that:

   *  It is the time at which the sending Runtime first enrolled the
      sending Agent's identity, as that Runtime defines identity, taken
      from its durable registry.

   *  It is a Runtime assertion, covered by the signature, and MUST NOT
      be taken from text the sender supplied.

   *  It has the format of ts, and is not later than ts.  Skew is
      absorbed by the age itself: a receiver measures age as its own
      clock minus sender_since, so a few minutes of skew change nothing
      under a policy measured in hours.

Rapp & Adams              Expires 24 April 2027                [Page 67]
Internet-Draft                    AMMP                      October 2026

   *  *Continuity.* The sending Runtime MUST NOT reset it for a
      continuation of the same identity: a restart, a rebinding, a re-
      creation or a key rotation keeps the value.  A new enrollment of
      the identity starts a new value.  The sending Runtime decides
      which of the two a given event is, and MUST decide it the same way
      every time for that identity.

   *  *Consistency.* A receiver that enforces a sender-age policy MUST
      keep the last value it accepted for each pair (Runtime, from).  A
      later statement that asserts an _earlier_ value claims an older
      enrollment than the Runtime previously asserted; it is
      inconsistent, and the receiver refuses it with its own
      policy_code.  A later value is a new enrollment, and the age
      starts again.

   A receiver MAY enforce a minimum sender age from it.  Its absence
   means "not asserted", and a receiver MUST NOT read it as "new" or as
   "old enough".  A deployment that requires it refuses its absence with
   its own policy_code.

12.6.10.  The Intake Location

   The intake location is REQUIRED, as a structural property with no
   prescribed layout.  A message admitted from a shared transport that
   presents as a peer or control candidate MUST be held, until it is
   disposed of, in a location that is neither inbound/ nor peer/, that
   is writable by the admitting component and readable by the verifying
   component, and that is unreachable from any Agent Namespace.
   Candidates MUST be held as the candidate of Section 12.6.2: the
   verifier MUST NOT parse mail.  The verifier MUST apply the read-side
   discipline of Section 4.1 to candidates, since the admitting
   component is less trusted than the verifier.  The verifier disposes
   of each candidate exactly once: it places it in peer/, minting the
   notice_id at reservation (Section 12.6.5); or it hands it to mail
   admission, which, if it delivers, places it in inbound/ under a
   notice_id minted there; or it drops or refuses it, logging the cause.
   A candidate held while peer admission is closed (Section 12.6.5) is
   not yet disposed of, and stays in the intake location until admission
   reopens.

   How a Runtime divides its intake (one location or several, for
   example by candidate kind), and where it keeps the traffic it authors
   itself, are implementation choices; this section constrains only the
   properties above.

Rapp & Adams              Expires 24 April 2027                [Page 68]
Internet-Draft                    AMMP                      October 2026

12.7.  Sender Exposure

   The split between Runtime assertions and sender claims (Section 7.1)
   applies: disposition, verdict and sender_standing are Runtime
   assertions, while subject and body_text (and, on the mail tree, from)
   are sender claims.  An exposure record belongs on the assertion side.
   It records _delivery_, not _reading_: a Runtime can know that it
   delivered external mail to the sender's Namespace, but cannot know
   what the sender did with it.

   A Peer Notice asserting exposure, with the stamp in
   message.sender_exposure:

   {
     "contract_version": "2",
     "notice_id": "7b2a1c0d9e8f47a6b5c4d3e2f1a0b9c8",
     "ts": "2026-09-03T12:05:00Z",
     "kind": "peer",
     "message": {
       "id": "7b2a1c0d9e8f47a6b5c4d3e2f1a0b9c8",
       "from": "triage@fleet.example",
       "subject": "Customer asks for an export",
       "preview": "A customer mail this morning asks whether",
       "mailbox": "peer",
       "sender_exposure": {
         "asserted_by": "amap.router@fleet.example",
         "external_mail_delivered": true,
         "window_start": "2026-09-02T12:05:00Z",
         "window_end": "2026-09-03T12:05:00Z",
         "last_external_delivery_ts": "2026-09-03T08:41:10Z",
         "derived_from": ["1721457600000000001-4242"]
       }
     }
   }

              Figure 9: fixtures/valid/peer-exposure-true.json

   *  *Placement.* It is a member of message, beside sender_standing,
      provenance and verdict, so that the Runtime's assessments of the
      message are kept together.  The schema requires asserted_by (a
      bare addr-spec naming the asserting Runtime; between hosts, the
      signer), external_mail_delivered, window_start and window_end, and
      requires last_external_delivery_ts when the boolean is true.  Its
      absence when the boolean is false is stated here, not in the
      schema.

Rapp & Adams              Expires 24 April 2027                [Page 69]
Internet-Draft                    AMMP                      October 2026

   *  *Shape.* A boolean over an explicit window, plus the most recent
      timestamp.  There is no count, because a count would assert a
      precision that a measure of delivery lacks. derived_from is
      optional, and present only where the Runtime observed the
      derivation itself (the sender's reply key named a notice in its
      mail tree).

   *  *Absence means "not assessed".* A consumer MUST NOT read the
      member's absence as "no exposure". false, asserted, is a stronger
      statement than the member omitted: it says that the Runtime
      assessed the window and delivered no external mail.  A Runtime
      that is the only writer of a sender's mail tree, and has written
      there only same-principal Agent mail and its own delivery failure
      notices, SHOULD assert false.  Notices the Runtime authors in the
      mail tree (the delivery failure notices of Section 12.5) are not
      external mail, and MUST NOT make the boolean true.  Moving
      window_start forward to the moment the Runtime's assessment began
      (for example, an instance's approval time after a reset) keeps the
      window on the wire equal to the span assessed.

   *  *Not settable by the sender.* On one host, the Submit Request
      envelope is closed (Section 13.1), so a request carrying the
      member is rejected, which is stronger than stripping it.  Between
      hosts, it is taken only from the verified statement, and is
      removed on demotion (Section 12.6.4).  A Runtime MUST NOT accept
      it from any sender.

   *  *Use.* It MUST NOT drive routing or treatment (Section 12.3).  It
      is advisory, for audit, and covered by the signature.

   *Lineage declared by the sender is not included.* Such a member would
   ride on the Submit Request, whose envelope is closed, so adding it
   would be a major change; it would work only when the sender
   cooperates, which is the case where it is least needed; and a
   forgeable member invites readers to trust it.  Most of its value is
   recovered without one: when a sender's reply key names a notice in
   its _mail_ tree, the Runtime knows which external message the peer
   message derives from, and records that as its own observation
   (derived_from, and the audit log).

12.8.  Delivery Outcomes, Retention, Deletion and Presentation

   *  *Delivery outcomes are not a profile artifact.* A Connector that
      reports what it did with a notice (delivered, held, refused, and
      so on) writes the report under outbound/ext/<name>/, in a shape
      agreed out of band with its Runtime, under the stable Connector id
      that Section 4.1 requires.  Section 4.1 carries the one normative
      rule: an outcome is a claim by the receiving side, never proof of

Rapp & Adams              Expires 24 April 2027                [Page 70]
Internet-Draft                    AMMP                      October 2026

      delivery or of non-delivery, and the Runtime's own delivery record
      is authoritative over it (Section 14.15).  If a second Connector
      needs outcomes, that is the point at which to define a shape for
      them in this profile.

   *  *Retention.* The record is the audit log, not the spool.  The
      Runtime logs every candidate it disposes of (id, from, to, tree,
      exposure, signature verdict, and any demotion or refusal cause) to
      the per-Agent audit/log.jsonl it owns, and to its own store of
      record.  Spool retention for peer/ follows the mail row of the
      deletion table (Section 4.2) unchanged.

   *  *Deletion rights.* The deletion table (Section 4.2) applies to
      peer/ artifacts exactly as to inbound ones: written by the
      Runtime, and deleted by the Runtime only.

   *  *Presentation.* A Connector presenting a peer body MUST meet the
      presentation obligations of Section 7.1, with one prohibition and
      two additions.  It MUST NOT convey that the sender is
      unauthenticated: on this tree, message.from is asserted by the
      Runtime from the restricted write path (Section 12.4), and saying
      otherwise contradicts the mechanism the tree exists to provide.
      It SHOULD convey that an authenticated request is still bounded by
      what the receiving Agent is authorized to do: a verified sender
      can ask for something the receiver may not perform, and a framing
      that reads as a grant invites the reader to treat verified
      identity as having settled that question.  It SHOULD convey that
      authenticated origin is not a warranty about content: peer content
      is authored by a peer and unscreened, and may paraphrase external
      mail (Section 12.7).  Authenticated _who_; unverified _what_.

      None of this is an enforcement mechanism.  Enforcement on this
      tree is the write-authority partition and the origin-follows-the-
      tree rule (Section 12.3); presentation is what a Connector states
      to a reader who can ignore it.  The reason to get it right is that
      the alternative asserts something false.

13.  Versioning and Conformance

13.1.  Versioning

   *The fail-closed rule.* contract_version is a major-version string.
   A consumer that reads a *higher major* than it speaks MUST *fail
   closed* — refuse to process the artifact — and MUST NOT silently mis-
   parse it.

Rapp & Adams              Expires 24 April 2027                [Page 71]
Internet-Draft                    AMMP                      October 2026

   *Version-check ordering.* A consumer MUST implement the version check
   by reading contract_version *before* any schema validation, comparing
   majors as non-negative decimal integers.  An absent major, a lower
   major, or a higher major is a *version refusal*: fail closed, as a
   distinct, logged outcome, never folded into the malformed-document
   drop path of Section 7.1.

   Checking the version first is what makes fail-closed implementable at
   all.  A validator that runs schema checks first cannot distinguish
   "wrong version" from "malformed," and a strict schema would reject a
   future-major document for the wrong reason entirely.

   This ordering rule is about _inbound_ artifacts a Connector consumes,
   which have no mechanism in this protocol to carry a distinct outcome
   back to the Runtime.  It does not reach the _outbound_ direction: a
   Runtime version-refusing a Submit Request MAY carry that refusal on
   the ordinary request-rejection path — a top-level rejected Result
   with reason_code: "unsupported_contract_version" (Section 6.1) — and,
   per the conformance classes below, MUST do so for the version-absent
   request fixture.  That is still a distinct, legible outcome rather
   than a fold into the malformed-request case: reason_code is exactly
   this protocol's mechanism for saying _why_ a rejected fired, and a
   consumer MUST already tolerate a reason_code it does not otherwise
   interpret.  A Runtime has no equivalent document to write for a
   version-refused _Notice_ it never emits — hence the asymmetry:
   logged-only inbound, rejected outbound.

   *A version signal, not member rejection, is the boundary.* Rejecting
   a document because it carries an unrecognized member is not a version
   mechanism.  Under closed envelopes, a consumer that meets a new
   member rejects the _whole document_: inbound, that is silent loss of
   the message; outbound, it is an accidental schema error rather than a
   designed refusal.  And while the version string is unchanged, nothing
   can react to the new member.  A change that a consumer of the current
   wire major would reject or misread as a whole document therefore
   requires a new wire major, so that the consumer refuses it _by
   version_, legibly, and both sides upgrade deliberately.  Schema
   strictness and protocol version are separate axes, and this document
   does not use the first as the second.

   *Unknown-member tolerance is direction-aware.* The rule is stated per
   direction because the two directions have opposite threat models.

   *  *Runtime-authored artifacts — Deliver Notice, Inbound Message,
      Result, Peer Notice — are open.* A consumer MUST ignore members it
      does not recognize, at every level of the document, and MUST NOT
      reject, drop, or fail the artifact on their account.  Strictness
      in this direction defends nothing: the producer is trusted by

Rapp & Adams              Expires 24 April 2027                [Page 72]
Internet-Draft                    AMMP                      October 2026

      construction, and in the one posture where an attacker can author
      a Notice — a writable inbound tree — that attacker can author a
      fully schema-valid one, so strictness is not the defense there.
      Its failure mode is silent loss of mail.  Emitting an unknown
      member is still a *producer* non-conformance: a Runtime MUST NOT
      emit members this document does not define.  In the schemas,
      additionalProperties: false is absent from the deliver-notice,
      inbound-message, result and peer-notice schemas at every level —
      top level, message, Descriptor items, and attestation — while the
      clean-gating conditionals are orthogonal and remain.

   *  *The Submit Request envelope is closed*, additionalProperties:
      false at every level.  Closure does real security work there: it
      is what bars an outbound content_ref and any other smuggled
      member; the validator is the trusted Runtime; and a refusal is a
      _legible, per-request_ rejected Result, never a silent loss.  A
      Runtime MUST reject a request carrying unrecognized members.

   *  *Consequently:* adding an optional member to a Runtime-authored
      artifact is additive and needs no new wire major; adding any
      member to the Submit Request requires one.

   *  The Binding Record is closed, for the separate reason given in
      Section 9.1.  The signed Peer Statement and Control Notice are
      closed because they are signed bytes another Runtime acts on
      (Section 12.6.1); they carry their own version member, v.

   *Tolerance extends to values, not only members.* An optional,
   explicitly advisory enum on a Runtime-authored artifact —
   message.sender_standing and message.provenance (Section 7.1) — is
   scoped the same way.  A consumer MUST tolerate a value it does not
   recognize, treating it as opaque or as the member being absent, and
   MUST NOT reject, drop, or fail the artifact on that account; a
   Runtime MAY add a value to either vocabulary without a new wire
   major.  Without this, a Runtime adding a new sender_standing or
   provenance value would silently lose every Notice carrying it,
   against every Connector still validating the old enum — the member
   case again, at the value level.  In the schemas, the enum constraint
   is absent from these two members.

   This is *not* a universal loosening of every enum. kind, outcome —
   both the top-level Result outcome and the per-attachment outcome —
   and the inbound Descriptor's disposition remain *closed, enum-locked
   vocabularies*. kind and outcome gate which processing path a document
   takes at all, and disposition gates byte publication (Section 7.2),
   so a new value in any of them is a behavioral fork a consumer cannot
   safely treat as opaque.  Introducing one requires a new wire major.

Rapp & Adams              Expires 24 April 2027                [Page 73]
Internet-Draft                    AMMP                      October 2026

   *When the wire major changes, and when it does not.* The wire major
   exists to force a legible, fail-closed refusal where a _whole
   document_ that a counterpart already accepts would otherwise be
   silently misread or silently dropped.  A change whose only
   incompatibility already travels a designed, legible, per-artifact
   refusal path (a rejected Result for one request, or the drop of one
   Notice under Section 7.1) does not require one.  Changing the major
   for such a change would make every counterpart on the old major
   refuse _all_ traffic, including traffic the change does not affect,
   which is the failure the major exists to prevent.  Revisions of this
   document are not wire versions: a revision that requires no new wire
   major leaves contract_version unchanged.

   *Why the wire version is "2".* The wire version starts at "2", not
   "1", because the value "1" is already taken.  Before this document,
   implementations exchanged an earlier, incompatible shape under "1":
   it had no attachments, and its envelopes were closed.  Reusing "1"
   for this document's shapes would give one major two incompatible
   meanings, and would defeat the fail-closed rule above exactly where
   it is needed: a consumer of the old shape would accept a document it
   cannot process. "2" is also what current implementations emit and
   check.  Appendix L records the history.  The authors would be glad of
   the community's view on whether a reset is nonetheless worth its
   cost.

   *Change control.* A capability change starts with a change to this
   document, its schemas, and its fixtures, _before_ either side
   implements it.  Neither a Runtime nor a Connector may invent a wire
   member locally.  A change that breaks an existing fixture is taken
   deliberately by both sides, and requires a new wire major only under
   the rule above.

13.2.  Required versus optional

   contract_version is REQUIRED on the Submit Request, the Deliver
   Notice, the Inbound Message, the Result, the Peer Notice and the
   Fleet Roster.  It is OPTIONAL on the Binding Record (Section 9.1) and
   the Peer Directory, and the signed Peer Statement and Control Notice
   carry their own version member, v, instead.  The rest of this table
   reflects what implementations emit today; specified-but-unbuilt
   enrichment stays optional, and tightening any of it to required would
   be a future major bump.

Rapp & Adams              Expires 24 April 2027                [Page 74]
Internet-Draft                    AMMP                      October 2026

   +===================+=====================+=========================+
   | Artifact          | Required            | Optional (specified;    |
   |                   |                     | may be absent)          |
   +===================+=====================+=========================+
   | *deliver-notice*  | contract_version,   | message.{task_id,       |
   |                   | notice_id, ts,      | thread_id,              |
   |                   | kind, message{id,   | in_reply_to,            |
   |                   | from, subject,      | references,             |
   |                   | preview, mailbox}   | sender_standing,        |
   |                   |                     | verdict, provenance},   |
   |                   |                     | message.attachments[]   |
   |                   |                     | (items require          |
   |                   |                     | filename, media_type,   |
   |                   |                     | size_bytes,             |
   |                   |                     | disposition; sha256     |
   |                   |                     | and content_ref         |
   |                   |                     | optional, gated to      |
   |                   |                     | disposition ==          |
   |                   |                     | "clean")                |
   +-------------------+---------------------+-------------------------+
   | *submit-request*  | contract_version,   | agent_id, ts,           |
   |                   | req_id, draft{to,   | in_reply_to,            |
   |                   | subject, body_text} | draft.{cc,              |
   |                   |                     | reply_to_message_id},   |
   |                   |                     | draft.attachments[]     |
   |                   |                     | (items require          |
   |                   |                     | filename, media_type,   |
   |                   |                     | size_bytes, sha256)     |
   +-------------------+---------------------+-------------------------+
   | *result*          | contract_version,   | detail, reason_code,    |
   |                   | req_id, outcome     | ts, recipients,         |
   |                   |                     | message_id, job_id,     |
   |                   |                     | attestation (requires   |
   |                   |                     | form; token and         |
   |                   |                     | claims optional),       |
   |                   |                     | attachments[] (items    |
   |                   |                     | require index,          |
   |                   |                     | outcome)                |
   +-------------------+---------------------+-------------------------+
   | *inbound-message* | _(the artifact      | id, date, from, to,     |
   |                   | itself is OPTIONAL  | cc, subject,            |
   |                   | — Section 7.3)_; if | attachments[] (same     |
   |                   | present:            | Descriptor rules as     |
   |                   | contract_version,   | deliver-notice)         |
   |                   | notice_id,          |                         |
   |                   | body_text           |                         |
   +-------------------+---------------------+-------------------------+
   | *binding-record*  | _(the file itself   | label;                  |

Rapp & Adams              Expires 24 April 2027                [Page 75]
Internet-Draft                    AMMP                      October 2026

   |                   | is OPTIONAL —       | contract_version        |
   |                   | Section 9)_; if     | (optional — if          |
   |                   | present: agent_id,  | present, pinned to      |
   |                   | address,            | "2")                    |
   |                   | provisioned_ts      |                         |
   +-------------------+---------------------+-------------------------+
   | *directory*       | _(the artifact      | contract_version (if    |
   |                   | itself is OPTIONAL  | present, pinned to      |
   |                   | — Section 10)_; if  | "2")                    |
   |                   | present: router,    |                         |
   |                   | written_at,         |                         |
   |                   | delegate_to,        |                         |
   |                   | mail_to             |                         |
   +-------------------+---------------------+-------------------------+
   | *peer-notice*     | _(peer-origin       | message.{in_reply_to,   |
   |                   | profile —           | references, task_id,    |
   |                   | Section 12.2)_      | thread_id,              |
   |                   | contract_version,   | sender_standing,        |
   |                   | notice_id, ts,      | provenance, verdict,    |
   |                   | kind: "peer",       | sender_exposure,        |
   |                   | message{id, from,   | attachments};           |
   |                   | subject, preview,   | in_reply_to is          |
   |                   | mailbox: "peer"}    | REQUIRED on a reply     |
   |                   |                     | (Section 12.5).  The    |
   |                   |                     | body spool and          |
   |                   |                     | Sidecars are as for     |
   |                   |                     | deliver-notice.         |
   +-------------------+---------------------+-------------------------+
   | *peer-statement*  | _(between hosts —   | in_reply_to,            |
   |                   | Section 12.6.1)_ v, | sender_exposure,        |
   |                   | key_id, kind, id,   | sender_since            |
   |                   | from, to, ts,       |                         |
   |                   | subject,            |                         |
   |                   | body_sha256,        |                         |
   |                   | attachments;        |                         |
   |                   | carried in exactly  |                         |
   |                   | one AMAP-Peer-      |                         |
   |                   | Statement field     |                         |
   |                   | with the tags k, s  |                         |
   |                   | and b               |                         |
   +-------------------+---------------------+-------------------------+
   | *peer-control*    | _(between hosts —   | policy_code (REQUIRED   |
   |                   | Section 12.6.8)_ v, | with cause policy)      |
   |                   | key_id, kind, id,   |                         |
   |                   | from, to, ts,       |                         |
   |                   | about, outcome,     |                         |
   |                   | cause; carried in   |                         |
   |                   | exactly one AMAP-   |                         |

Rapp & Adams              Expires 24 April 2027                [Page 76]
Internet-Draft                    AMMP                      October 2026

   |                   | Peer-Control field  |                         |
   |                   | with the tags k, s  |                         |
   |                   | and b               |                         |
   +-------------------+---------------------+-------------------------+
   | *roster*          | _(the artifact      | interval_s              |
   |                   | itself is OPTIONAL  |                         |
   |                   | — Section 11)_; if  |                         |
   |                   | present:            |                         |
   |                   | contract_version,   |                         |
   |                   | router, written_at, |                         |
   |                   | members[] (items    |                         |
   |                   | require address,    |                         |
   |                   | state)              |                         |
   +-------------------+---------------------+-------------------------+

                                  Table 5

   inbound/notices/processed/ is an OPTIONAL *directory convention*
   (Section 4.1), not a protocol artifact: it carries no schema, and no
   fixture exists or is needed for it.

   Emission of contract_version on the artifacts that require it is
   mandatory.  An implementation not yet emitting it has an
   implementation conformance gap, not a license from this document.

13.3.  Conformance

   *The fixtures are the other side.* fixtures/ holds golden artifacts,
   and a producer or consumer proves conformance by passing them with
   *no counterpart present* (Appendix J).  That is what makes
   independent implementation practical: neither side needs the other to
   exist in order to demonstrate conformance.

   Not every obligation in this document is wire-shaped.  A _behavioral_
   obligation — one about how an implementation acts against a
   filesystem, rather than about the shape of a document — lies outside
   the fixture gate by construction and is checked operationally
   instead.  Appendix J lists every obligation of this kind that a green
   run does not prove.

   This document defines two conformance classes.  An implementation MAY
   claim both.  Claiming either without the corresponding fixture run,
   and without the prescribed operational check for that class's
   behavioral obligations, is not conformance.

   *Runtime conformance class.* An implementation claiming Runtime
   conformance:

Rapp & Adams              Expires 24 April 2027                [Page 77]
Internet-Draft                    AMMP                      October 2026

   *  MUST produce Deliver Notice documents valid against the deliver-
      notice schema (Appendix C) and Result documents valid against the
      result schema (Appendix B); MAY produce Inbound Message documents,
      which when produced MUST be valid against the inbound-message
      schema (Appendix D); and MAY produce a Binding Record valid
      against the binding-record schema (Appendix E).

   *  MUST consume Submit Request documents: it MUST accept every valid
      request-* fixture and MUST reject every invalid request-* fixture.

   *  MUST demonstrate its producing side by confirming that its own
      emitted Notices, Results, and Inbound Messages satisfy the same
      schemas the suite's valid notice-*, result-*, and message-*
      fixtures satisfy — *including the three fixtures carrying
      unrecognized members*. A Runtime MUST NOT itself emit unrecognized
      members, but its _parser_, exercised by those fixtures, is what a
      Connector conformance run depends on it never having to special-
      case.  It MUST also confirm that any Binding Record it writes
      satisfies the valid identity-* shape.

   *  MUST uphold the filesystem and policy obligations of Sections 4
      through 8 that no document validator can observe.  Appendix J
      lists them explicitly as what a green fixture run does *not*
      prove, and they are obligations of the class regardless — write-
      side path discipline (the conjunctive parent-chain-pinning, no-
      follow-while-building, and non-following-final-component rule),
      ingest by copy, read-side open discipline, and single-writer/
      single-drainer chief among them.

   *Connector conformance class.* An implementation claiming Connector
   conformance:

   *  MUST produce Submit Request documents valid against the submit-
      request schema (Appendix A), and MUST NOT emit an outbound
      content_ref (Section 5.1).

   *  MUST consume Deliver Notice, Inbound Message, and Result
      documents: it MUST accept every valid notice-*, message-*, and
      result-* fixture — *including those carrying unrecognized members
      or unrecognized advisory-enum values* — and MUST reject, meaning
      drop rather than surface, every invalid notice-* fixture *except
      the version-refusal one*, which it MUST instead refuse via the
      distinct version-refusal path (Section 13.1): never folded into
      the drop path, and never surfaced to the Agent either.

      Section 7.1's drop rule is *two independent conditions, not a
      closure over schema validation*. "Fails schema validation for any
      reason other than an unrecognized member" (check 1) covers eleven

Rapp & Adams              Expires 24 April 2027                [Page 78]
Internet-Draft                    AMMP                      October 2026

      of the twelve other invalid notice-* fixtures — missing-required-
      member, wrong-kind, enum, pattern, and conditional violations
      alike.  The twelfth is schema-valid on its own, producing zero
      errors from the gate's schema validator, and is caught only by the
      content_ref index and prefix binding (check 2, Section 7.2), a
      named post-check the schema cannot express.  A Connector
      conformance claim requires running *both* checks; schema
      validation alone does not close this set.

      Accepting the clean-Descriptor fixture — a Descriptor asserted
      clean and carrying content_ref and sha256 — is part of this
      obligation, and acceptance means relaying the Notice, not fetching
      the bytes: a Connector that never dereferences content_ref
      satisfies it (Section 7.2).

   *  MUST return "not resolved" for a body whose Inbound Message is
      absent, and MUST NOT attempt any fallback fetch (Section 7.3).

   *  MUST tolerate a Binding Record that is absent, and MUST NOT crash
      or otherwise misbehave on one that is hostile or malformed
      (Section 9.1).

   *  MUST NOT require write access anywhere under inbound/,
      demonstrated by the operational check below rather than by a
      fixture (Section 4.1).

   *  MUST demonstrate its producing side by confirming that its own
      emitted requests satisfy the same schema the suite's valid
      request-* fixtures satisfy, and MUST uphold the write-ordering,
      atomicity, and read-discipline obligations of Section 4.1, which
      the fixtures cannot check.

   *Peer-origin sub-classes.* The peer-origin profile (Section 12) adds
   two sub-classes, each requiring the corresponding class above plus
   the profile's fixtures and operational checks.  Claiming either
   without both is not conformance.

   An implementation claiming *Runtime + peer-origin* conformance, in
   addition to the Runtime class:

   *  MUST produce Peer Notices valid against the peer-notice schema
      (Appendix F), and MUST confirm that its own emitted Peer Notices
      satisfy the same schema the valid peer-* fixtures satisfy;

   *  MUST refuse to emit into peer/ any document that would fail that
      schema, and MUST NOT emit a peer document into inbound/
      (Section 12.2);

Rapp & Adams              Expires 24 April 2027                [Page 79]
Internet-Draft                    AMMP                      October 2026

   *  MUST uphold the operational obligations of the profile: the write-
      authority partition, proven by the write probe, and verification
      placement (Section 12.3); a Runtime-asserted peer from
      (Section 12.4); in_reply_to only when resolved, and refusal rather
      than stripping (Section 12.5); the intake location, and hashing of
      bytes by the verifier itself (Section 12.6); and sender_exposure
      never settable by a sender (Section 12.7);

   *  if it verifies peer traffic from other hosts, MUST reach every
      step's expected outcome in the signed cases, and MUST pass the
      Ed25519 vectors through the implementation it ships (Appendix J).

   An implementation claiming *Connector + peer-origin* conformance, in
   addition to the Connector class:

   *  MUST accept every valid peer-* fixture and MUST reject every
      invalid peer-* fixture when read from peer/notices/, and MUST
      reject invalid/notice-kind-peer.json when read from inbound/
      notices/;

   *  MUST derive the origin class from the tree alone (Section 12.3),
      and MUST act on a peer message at most once, holding rather than
      fanning out when it cannot identify a single target
      (Section 12.4);

   *  MUST tolerate unknown members on a Peer Notice, whose envelope is
      open (Section 13.1).

   *Read-only inbound tolerance — the prescribed operational check.* Run
   the Connector against an inbound tree it cannot write — a genuinely
   read-only mount, not merely cleared permission bits, since a same-uid
   Agent can change a mode it owns — and confirm that it starts, relays
   every pending Notice, resolves bodies, and submits outbound requests,
   without requiring any write under inbound/ to succeed.

14.  Security Considerations

   This section gathers the threats this protocol's obligations exist to
   address.  It introduces no new mechanism: apart from the rule for
   special-use domain names (Section 14.16), which is stated only here,
   every requirement referenced here is stated normatively in Sections 4
   through 13.  The organizing principle throughout is the invariant of
   Section 3.2 — enforcement is Runtime-side, and no property depends on
   the Agent or its Connector behaving.

Rapp & Adams              Expires 24 April 2027                [Page 80]
Internet-Draft                    AMMP                      October 2026

   The threat actor assumed throughout is a *fully compromised Agent*
   with write access to its own Namespace, running as a uid that owns
   the files it creates, able to act concurrently with the Runtime, and
   able to observe or predict the filenames the Runtime is about to use.

14.1.  Path authority and traversal

   An identifier drawn from a file's _contents_ MUST NOT name a write
   path (Section 4.1).  A Runtime that trusted a req_id member inside a
   request body to name the path its Result is written to would let the
   Agent, which controls that member, direct a Runtime-privileged write
   anywhere the Runtime can reach.  The obligation is that the Runtime
   derives identifiers from the _validated filename_, over a restricted
   character set, and re-derives every path from data it controls.

   The same reasoning is why inbound Sidecar directories and the body
   spool are keyed on the Runtime-minted notice identifier rather than
   the provider message.id: the provider identifier is external,
   arbitrary, and not character-set-safe, and using one as a path
   component would hand the choice of path to the sender.  The schemas
   pin notice_id and req_id to character sets that are safe as path
   components; message.id is deliberately unconstrained, because it is
   never used as one.

14.2.  Symlinks, descriptors, and open discipline

   A shared filesystem between a trusted and an untrusted party makes
   every path-based check racy.  The Agent can replace any component of
   a path it owns with a symlink, at any moment, including between a
   Runtime's check and the Runtime's subsequent act.

   Section 4.1 states the write-side discipline as three conjunctive
   requirements, and the conjunction is the point: each leg closes a gap
   the other two leave open.

   *  Pinning only the final component leaves the _parent_ swappable,
      and rename and directory-creation calls have no no-follow flag at
      all — so a bare "do not follow symlinks on writes" rule is not
      sufficient.

   *  Pinning the parent chain without refusing symlinks _while
      acquiring_ it means the pinned descriptor may point at whatever
      the Agent planted before the Runtime opened it — including across
      a Runtime restart, when the chain is re-opened.

   *  Pinning the whole chain correctly still says nothing about the
      final filename.  Every Runtime-written filename in the layout tree
      is predictable or observable to the Agent, so a pre-planted

Rapp & Adams              Expires 24 April 2027                [Page 81]
Internet-Draft                    AMMP                      October 2026

      symlink at that name receives the Runtime's write with the
      Runtime's privileges unless the final open refuses to follow it
      and, where the file is newly created, refuses to reuse an existing
      name at all (O_EXCL).

   The append-only audit log is the one write with no .tmp sibling to
   lean on, and it is called out separately for two reasons.  O_EXCL
   cannot apply to a file that legitimately already exists, so the
   final-component protection there is a no-follow, append-mode, *non-
   blocking* open plus an fstat through the resulting descriptor.  The
   non-blocking part is load-bearing rather than a performance nicety: a
   no-follow flag does not apply to a FIFO, because a FIFO is not a
   symlink, and opening a FIFO for writing blocks until a reader
   appears.  An Agent that plants a FIFO at that filename therefore
   stalls the Runtime's drain indefinitely — a denial of service that
   the exclusive-create branch happens to refuse for free, and that this
   branch must refuse explicitly.

   The read side mirrors all of it (Section 4.1): per-component no-
   follow opens or an equivalently pinned walk, a non-blocking open, an
   fstat through the open descriptor rather than a pre-open stat (which
   is itself a race), refusal of a multiply-linked file, and enforced
   size and count caps.  The caps are not optional hardening: the
   too_large Result value presumes one, and without them an Agent can
   present an unbounded Sidecar or an unbounded request and exhaust the
   Runtime.

14.3.  Time-of-check to time-of-use, and ingest by copy

   Even with every open performed correctly, validating a file _in
   place_ and then using it is a race: the Agent can rewrite the bytes
   between the check and the use, so the document that passed policy is
   not necessarily the document that gets composed and sent.  The
   obligation that closes this is ingest by copy (Section 4.1) — the
   Runtime copies the request and every Sidecar into Runtime-private
   Staging outside the Volume, and validates, evaluates policy on, and
   composes exclusively from that private copy, never re-reading an
   Agent-writable path after a decision that depends on its content.

   This is also what keeps the trust model honest.  Without it, the
   property "the request the Runtime approved is the request the Runtime
   sent" would rest on the Connector not rewriting a published file — an
   Agent-side obligation carrying a Runtime-side safety property, which
   the invariant of Section 3.2 forbids.  With it, that Connector
   obligation is pure well-formedness: a Runtime MAY treat an observed
   post-publish change as malformed, but nothing depends on the
   Connector honoring it.

Rapp & Adams              Expires 24 April 2027                [Page 82]
Internet-Draft                    AMMP                      October 2026

14.4.  Concurrency

   Every atomicity guarantee in this protocol — the .tmp-then-rename
   discipline, the commit sentinel, the derivation of req_id from the
   maximum of the pending, processed, and result sets — assumes a single
   writer and a single drainer per Namespace, and none of them provides
   mutual exclusion (Section 4.1).  Two concurrent drains can deliver
   the same request twice; two concurrent writers can collide on a
   req_id and clobber a published request, producing a verdict that
   names a request that no longer exists as written.  The exclusion
   mechanism lives outside this protocol, and an implementation that
   does not provide one is not conformant, because behavior under
   violation is unspecified rather than merely degraded.

14.5.  Attribution spoofing

   Attribution is the Namespace a file arrived in, never an identifier
   inside the file (Section 4.1).  A compromised Agent can write any
   agent_id it likes into a request; the member exists only as a cross-
   check, and a Runtime MUST reject a mismatch rather than believe it.
   Structural isolation is what actually enforces this: a Connector
   scoped to its own Namespace cannot name another Agent's.

   The Binding Record deserves the same skepticism from the other
   direction (Section 9.1).  It sits on an Agent-writable Volume, so its
   contents are untrusted data, it grants nothing, and it is never
   consulted for enforcement.

14.6.  Untrusted content

   Inbound mail is an injection vector aimed at the Agent's reasoning,
   and this protocol's posture is that _every sender-originated string_
   is untrusted content: bodies, subjects, previews, threading
   references, sender display names, and attachment Descriptors alike
   (Section 7.1).

   Two cases are worth restating because they are the ones most often
   assumed safe.

   *message.from is attacker-controlled display data.* Its display-name
   half is chosen freely by the sender, so a value such as
   "security@yourbank.example <attacker@evil.example>" is entirely legal
   and renders convincingly.  It MUST NOT be used for a trust decision
   or shown to a human or an Agent as verified identity.  Verified
   sender identity is only ever what the Runtime summarizes into
   sender_standing and verdict, or, on a Peer Notice, the message.from
   the Runtime asserts (Section 12.4).

Rapp & Adams              Expires 24 April 2027                [Page 83]
Internet-Draft                    AMMP                      October 2026

   *An attachment filename is an injection payload.* A Descriptor is
   metadata about a file the Agent may never receive the bytes of, and a
   clean Disposition asserts something about _bytes_, never about the
   truthfulness of the strings describing them.  A quarantined file
   named to look like an instruction reaches the Agent's context intact
   even though its bytes were withheld. media_type is likewise a
   declaration, which is why policy is governed by the Runtime's own
   sniffing of the bytes rather than by the declared type.

   verdict, sender_standing, and provenance are advisory metadata for
   routing and presentation.  None of them is a license for an Agent to
   obey body content, and this document deliberately does not define
   what an Agent should do with a message — that is Connector and
   deployment policy.

14.7.  Recipient policy and header injection

   Recipient policy is the exfiltration boundary, and it is a Runtime
   decision (Section 5.1).  The Agent's draft.to and draft.cc are
   requests, not instructions.  Two obligations make the boundary
   mechanical rather than best-effort: the Runtime MUST evaluate policy
   over exactly the recipient set it will transmit to, and it MUST NOT
   transmit to any address not present in those members.  Without the
   first, policy can be evaluated over a parse of the recipients that
   differs from the one used for transmission; without the second, a
   Runtime-side addition (a bcc, an archive address) escapes the
   evaluated set.

   The narrow addr-spec profile exists to make the evaluation
   mechanical.  Display names, address lists inside a single element,
   quoted local parts, comments, IP literals, and folding whitespace are
   precisely the forms that make "does this address match the allowlist"
   a parsing problem with divergent answers — the classic confusion in
   which one parser sees the allowlisted address and the transmitting
   parser sees the attacker's.  The profile is deliberately narrower
   than [RFC5322] for that reason, and the cost is disclosed: some
   legitimate [RFC5322] addresses cannot be expressed under this
   document.

Rapp & Adams              Expires 24 April 2027                [Page 84]
Internet-Draft                    AMMP                      October 2026

   Header injection is the second half.  Any Agent-authored string that
   reaches the composed message can attempt to introduce a header
   boundary with CR, LF, or NUL.  The schema patterns are the first line
   of defense, but a Runtime MUST also reject or strip those characters
   at composition time, because a document can reach composition by a
   path where schema validation was bypassed upstream.  The two fields
   most easily overlooked are the ones composed _directly into headers_
   rather than into a body or display context: reply_to_message_id,
   which becomes an In-Reply-To: header, and an attachment's media_type,
   which becomes a MIME part's Content-Type: [RFC2045].

14.8.  The outbound digest is not a trust boundary

   sha256 on an outbound attachment Descriptor is computed by the
   Connector over bytes the Connector wrote (Section 5.1).  The Agent
   controls both, so a matching digest proves only that the request is
   internally consistent.  It catches corruption and interrupted writes;
   it authenticates nothing.  A Runtime MUST NOT treat a digest match as
   evidence about the content's safety, and MUST apply its own policy —
   sniffing the bytes, applying type and size rules — to the staged copy
   regardless.

   The inbound direction is weaker still and is labelled as such:
   inbound size_bytes is provider-declared and advisory, not Runtime-
   verified (Section 7.2).

14.9.  Confirmation-oracle avoidance

   Inbound metadata is gated so that withholding bytes does not become
   an information channel. sha256 on an inbound Descriptor is emitted
   only where the Runtime asserts clean (Section 7.2).  Publishing a
   digest for a withheld attachment would let an Agent confirm whether a
   _guessed_ file was the one quarantined — a confirmation oracle —
   while providing no integrity value at all, since the Agent has no
   bytes to check it against.

   The same principle governs quarantined mail generally: it produces no
   Notice at all, rather than a Notice announcing that something was
   withheld.

14.10.  Byte-absence and artifact-absence as the access gate

   The access gate in this protocol is the absence of bytes, not a flag
   inside a document (Section 4.1).  A Runtime MUST NOT place non-clean
   bytes in an Agent's Namespace; content_ref is the grant, and it
   exists only where bytes were published.  An Agent that ignores every
   Disposition it is shown still cannot read what was never written.

Rapp & Adams              Expires 24 April 2027                [Page 85]
Internet-Draft                    AMMP                      October 2026

   The same shape appears for bodies: a withheld or never-delivered
   message is simply not spooled, and a read against a missing artifact
   returns "not resolved," with no fallback fetch available to the
   Connector (Section 7.3).  Because retention may also remove an
   artifact (Section 4.2), "retired" and "never spooled" are
   indistinguishable from the Connector's side — which is a deliberate
   property, not an ambiguity to be resolved by a new signal.

   The content_ref binding rules (Section 7.2) close the corresponding
   path-confusion risk: a consumer derives the byte path from the
   enclosing notice_id and the array index rather than from the
   content_ref string, and refuses any reference that does not match the
   pinned pattern or points outside its own inbound tree.  Otherwise a
   Descriptor could name another Notice's Sidecar directory — including
   one belonging to a different delivery whose bytes the Agent was never
   granted.

14.11.  Fail-closed version handling, and the cost of tolerance

   A consumer reading a higher major than it speaks fails closed, and
   the version check runs _before_ schema validation so that "wrong
   version" and "malformed" remain distinguishable (Section 13.1).
   Folding the two together is how a protocol acquires silent,
   undiagnosable mail loss.

   The unknown-member tolerance rule is direction-aware precisely
   because tolerance has a security cost in one direction and a safety
   benefit in the other.  On Runtime-authored artifacts, strictness
   defends nothing — the producer is trusted, and an attacker able to
   author a Notice at all (a writable inbound tree) can author a schema-
   valid one — while its failure mode is silent mail loss.  On the
   Agent-authored Submit Request, closure is doing real work: it is what
   makes an outbound content_ref, or any other smuggled member, a hard
   rejection rather than a member the Runtime might one day interpret.
   A Runtime therefore MUST keep rejecting unrecognized members inbound
   from an Agent, and its rejection is legible — a rejected Result —
   rather than silent.

   The value-level tolerance is scoped to the two explicitly advisory
   vocabularies for the same reason: kind, outcome, and disposition gate
   processing paths and byte publication, and treating an unrecognized
   value in any of them as opaque would let a producer's new value
   silently select a consumer's default path.

Rapp & Adams              Expires 24 April 2027                [Page 86]
Internet-Draft                    AMMP                      October 2026

14.12.  Inbound integrity and the writability of the inbound tree

   A read-only inbound mount is the mechanism that makes a delivered
   Notice unforgeable (Section 4.1).  Permission bits alone do not
   achieve it: a same-uid Agent can change a mode it owns, so an inbound
   tree protected only by bits is writable by the attacker in practice.
   Deployments that leave inbound writable should understand what they
   are accepting — an Agent can then author a schema-valid Notice for
   mail that never arrived, or alter one that did.

   This is also why the tolerance rule does not weaken the inbound
   direction: in the writable-inbound posture, the attacker's forgery is
   schema-valid anyway, so schema strictness was never the control.  The
   control is the mount.

   Correspondingly, a Connector MUST NOT require write access under
   inbound/, and consumption tracking is Connector-private state kept
   elsewhere (Section 4.1).  A Connector that crashes on a read-only
   inbound tree is broken against any Runtime enforcing inbound
   integrity.

14.13.  Retention, deletion, and lost verdicts

   Deletion rights are named per artifact class (Section 4.2) because an
   unnamed right becomes either unbounded growth or an accidental data-
   loss path.  Two hazards are worth restating.

   *A Result deleted before it is read is a lost verdict.* No signal in
   this protocol tells a Runtime that a Connector has read one, so
   Runtime-side retention SHOULD be age-based and generous.

   *Deletion interacts with req_id monotonicity.* A Connector exercising
   its deletion rights in processed/ or results/ removes exactly the
   state from which the next req_id is derived, and MUST preserve
   monotonicity by other durable means.  Symmetrically, a Runtime MUST
   NOT treat processed/ as durable state — for deduplication, for
   instance — because the Connector may delete there.  The Runtime's own
   record of what it sent belongs outside this protocol entirely.

14.14.  What is wire-verifiable, and what is a Runtime obligation

   A green conformance run proves that documents have the right shape.
   It proves nothing about a filesystem.  The following are Runtime or
   Connector obligations that no document validator can observe, and a
   reviewer assessing an implementation must check them another way
   (Appendix J lists them too):

Rapp & Adams              Expires 24 April 2027                [Page 87]
Internet-Draft                    AMMP                      October 2026

   *  Sidecar file existence, and the binding between an ordinal and a
      directory entry on disk.

   *  Whether size_bytes and sha256 match the bytes actually on disk.

   *  Display sanitization of filename, and its never-a-path invariant.

   *  The keying of inbound Sidecar directories on the Runtime-minted
      notice identifier.

   *  The location of Runtime-private Staging, outside the mounted
      Volume.

   *  Write ordering and commit-sentinel atomicity.

   *  Caps, allowlists, and quotas.

   *  The absence of a spooled body for a quarantined or blocked
      message.

   *  A Connector's tolerance of a read-only inbound tree.

   *  Ingest by copy.

   *  Write-side path discipline, in all three of its conjunctive legs.

   *  Read-side open discipline.

   *  Single writer and single drainer per Namespace.

   *  Compose-time rejection or stripping of CR, LF, and NUL.

   Two of these deserve emphasis as trust-model statements rather than
   as testing notes.  First, disposition == "clean" is the _Runtime's
   assertion_, and this document deliberately does not specify the basis
   for it; a consumer that treats clean as a proof rather than as an
   assertion has misread the trust model.  Second, this protocol cannot
   make a Runtime trustworthy — it can only ensure that everything the
   Agent contributes is inert until a Runtime acts on it, and that
   whatever the Runtime asserts is legible and attributable to the
   Runtime.

Rapp & Adams              Expires 24 April 2027                [Page 88]
Internet-Draft                    AMMP                      October 2026

14.15.  The extension space, and outcomes as claims

   outbound/ext/<name>/ (Section 4.1) is Connector-owned and Agent-
   writable, and this document specifies no shape for anything under it.
   Its <name> is a stable, opaque Connector id (Section 4.1), so a
   Runtime's out-of-band agreement with a Connector survives that
   Connector's repository being renamed.  Nothing security-relevant may
   be built on it.  Its first use, a per-Notice outcome file, is written
   by the receiving Agent's own uid and is therefore forgeable by
   construction: a receiving Agent can claim a delivery that did not
   happen, deny one that did, or suppress a refusal.  An outcome is a
   statement by the receiving side about what it did with a Notice; a
   party MUST NOT treat an outcome as proof of delivery or of non-
   delivery, and the Runtime's own delivery record is authoritative over
   it.  A Runtime that reads outcomes may let them change what a sender
   is told or what an operator sees; it MUST NOT let them change what
   its own record says happened.  There is deliberately no inbound
   extension space, because an inbound tree is a Runtime assertion about
   origin (Section 7.1) and a Connector cannot know what a vendor-named
   assertion means.

14.16.  Special-use domain names

   Deployments commonly give Agents addresses under names that are not
   part of the public DNS: special-use domain names listed in the
   registry established by [RFC6761] (such as .test, .local, .home.arpa
   and .onion), and names reserved for private use, such as .internal.
   A Runtime MUST NOT hand mail addressed to such a name to a mail
   system outside the deployment, whatever its recipient policy allows:
   doing so leaks fleet-internal addresses, and the mail they carry,
   onto the public Internet.  A deployment's own mail system that is
   authoritative for such a name may carry it, and then MUST NOT relay
   it onward.

14.17.  The Peer-Origin Profile

   The profile adds a tree that a consumer may treat as authenticated
   origin, so its security rests on who can write that tree and on what
   a verifying Runtime accepts from another host.

   *Forgery of the peer tree.* A component that parses mail from an open
   transport is the most exposed component in a Runtime.  If it could
   write peer/, any sender able to reach it could forge authenticated
   peer origin.  The write-authority partition and the placement of
   verification (Section 12.3) exist to prevent that, and are checked by
   a write probe rather than by configuration review.

Rapp & Adams              Expires 24 April 2027                [Page 89]
Internet-Draft                    AMMP                      October 2026

   *Key compromise.* A Runtime's signing key speaks for every Agent in
   that Runtime's domains.  An attacker holding it can place peer
   messages, as any of those Agents, at every Runtime that configures
   the key, until the key is revoked; the key-domain check
   (Section 12.6.3) confines the damage to the compromised Runtime's own
   domains.  Keeping the key out of every Agent Namespace and out of the
   admitting component, using it for nothing else, and supporting
   rotation (Section 12.6.7) are the mitigations.  Because replay
   records are scoped to the Runtime rather than the key, rotation does
   not reopen a replay window.

   *Replay and clocks.* A statement is a bearer of authenticated origin
   for as long as it is fresh.  The replay store, the high-water clock H
   and the recovery floor (Section 12.6.5) bound how long that is,
   including across a clock that steps backwards and a store that is
   lost.  The profile does not promise exactly-once delivery, and says
   so; a deployment that needs it beyond the retention period has to
   forbid re-signing.

   *Parsing hostile input.* The verifier reads data an attacker chose.
   It verifies the signature over the exact bytes received before
   parsing anything, defines no canonicalization (so there is no second
   parse to disagree with the first), refuses duplicate JSON member
   names, and receives the message already reduced to a candidate
   (Section 12.6.2), so that it never parses MIME itself.  Oversize and
   invalid fields are carried as markers so that a malformed second
   field cannot be silently removed.

   *Backscatter and loops.* A Control Notice is sent only after the
   statement has been authenticated, only to the configured Runtime
   address of the key's owner, never about another Control Notice, and
   at most once per record (Section 12.6.8).  An unauthenticated message
   therefore cannot direct a Runtime to send mail to a third party, and
   two Runtimes cannot be made to answer each other indefinitely.

   *Authenticated is not trustworthy.* A verified peer is a same-
   principal Agent, which may itself have read hostile external mail and
   be relaying it.  The tree authenticates _who_, never _what_
   (Section 12.8). sender_exposure (Section 12.7) lets a Runtime say
   whether the sender was exposed to external mail, and absence of the
   stamp means that nothing was assessed.

   *Exposure in transit.* Between hosts, the statement travels in a
   header field.  Its subject, addresses and attachment names are
   encoded rather than encrypted, and are visible to every mail system
   on the path, as the rest of the message is.  The profile provides
   integrity and origin, not confidentiality; a deployment that needs
   confidentiality between hosts has to obtain it from the transport.

Rapp & Adams              Expires 24 April 2027                [Page 90]
Internet-Draft                    AMMP                      October 2026

15.  IANA Considerations

15.1.  Message Header Fields

   The peer-origin profile (Section 12) defines two header fields
   carried in mail between Runtimes.  IANA is requested to register them
   in the "Provisional Message Header Field Names" registry, following
   the procedure of [RFC3864]:

    +=====================+==========+=============+==================+
    | Header Field Name   | Protocol | Status      | Reference        |
    +=====================+==========+=============+==================+
    | AMAP-Peer-Statement | mail     | provisional | Section 12.6.2   |
    |                     |          |             | of this document |
    +---------------------+----------+-------------+------------------+
    | AMAP-Peer-Control   | mail     | provisional | Section 12.6.8   |
    |                     |          |             | of this document |
    +---------------------+----------+-------------+------------------+

                    Table 6: Header field registrations

   Change controller: IETF.

15.2.  Possible Future Registries

   This document creates no registry.  Several of its values are
   candidates for one, and the authors would welcome the community's
   view on whether IANA should govern them: the values of
   contract_version (Section 13.1), whose fail-closed semantics make an
   unambiguous assignment important; the open reason_code vocabulary of
   the Result (Section 6.1); and the closed outcome and cause
   vocabularies of the Control Notice (Section 12.6.8).

16.  References

16.1.  Normative References

   [FIPS180-4]
              National Institute of Standards and Technology, "Secure
              Hash Standard (SHS)", FIPS PUB 180-4,
              DOI 10.6028/NIST.FIPS.180-4, August 2015,
              <https://doi.org/10.6028/NIST.FIPS.180-4>.

Rapp & Adams              Expires 24 April 2027                [Page 91]
Internet-Draft                    AMMP                      October 2026

   [JSON-SCHEMA-2020-12]
              Wright, A., Ed., Andrews, H., Ed., Hutton, B., Ed., and G.
              Dennis, "JSON Schema: A Media Type for Describing JSON
              Documents", Work in Progress, Internet-Draft, draft-
              bhutton-json-schema-01, 10 June 2022,
              <https://json-schema.org/draft/2020-12/json-schema-core>.

   [RFC2045]  Freed, N. and N. Borenstein, "Multipurpose Internet Mail
              Extensions (MIME) Part One: Format of Internet Message
              Bodies", RFC 2045, DOI 10.17487/RFC2045, November 1996,
              <https://www.rfc-editor.org/info/rfc2045>.

   [RFC2046]  Freed, N. and N. Borenstein, "Multipurpose Internet Mail
              Extensions (MIME) Part Two: Media Types", RFC 2046,
              DOI 10.17487/RFC2046, November 1996,
              <https://www.rfc-editor.org/info/rfc2046>.

   [RFC2047]  Moore, K., "MIME (Multipurpose Internet Mail Extensions)
              Part Three: Message Header Extensions for Non-ASCII Text",
              RFC 2047, DOI 10.17487/RFC2047, November 1996,
              <https://www.rfc-editor.org/info/rfc2047>.

   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
              Requirement Levels", BCP 14, RFC 2119,
              DOI 10.17487/RFC2119, March 1997,
              <https://www.rfc-editor.org/info/rfc2119>.

   [RFC2183]  Troost, R., Dorner, S., and K. Moore, Ed., "Communicating
              Presentation Information in Internet Messages: The
              Content-Disposition Header Field", RFC 2183,
              DOI 10.17487/RFC2183, August 1997,
              <https://www.rfc-editor.org/info/rfc2183>.

   [RFC2231]  Freed, N. and K. Moore, "MIME Parameter Value and Encoded
              Word Extensions: Character Sets, Languages, and
              Continuations", RFC 2231, DOI 10.17487/RFC2231, November
              1997, <https://www.rfc-editor.org/info/rfc2231>.

   [RFC3339]  Klyne, G. and C. Newman, "Date and Time on the Internet:
              Timestamps", RFC 3339, DOI 10.17487/RFC3339, July 2002,
              <https://www.rfc-editor.org/info/rfc3339>.

   [RFC3629]  Yergeau, F., "UTF-8, a transformation format of ISO
              10646", STD 63, RFC 3629, DOI 10.17487/RFC3629, November
              2003, <https://www.rfc-editor.org/info/rfc3629>.

Rapp & Adams              Expires 24 April 2027                [Page 92]
Internet-Draft                    AMMP                      October 2026

   [RFC4648]  Josefsson, S., "The Base16, Base32, and Base64 Data
              Encodings", RFC 4648, DOI 10.17487/RFC4648, October 2006,
              <https://www.rfc-editor.org/info/rfc4648>.

   [RFC5322]  Resnick, P., Ed., "Internet Message Format", RFC 5322,
              DOI 10.17487/RFC5322, October 2008,
              <https://www.rfc-editor.org/info/rfc5322>.

   [RFC8032]  Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital
              Signature Algorithm (EdDSA)", RFC 8032,
              DOI 10.17487/RFC8032, January 2017,
              <https://www.rfc-editor.org/info/rfc8032>.

   [RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
              2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
              May 2017, <https://www.rfc-editor.org/info/rfc8174>.

   [RFC8259]  Bray, T., Ed., "The JavaScript Object Notation (JSON) Data
              Interchange Format", STD 90, RFC 8259,
              DOI 10.17487/RFC8259, December 2017,
              <https://www.rfc-editor.org/info/rfc8259>.

   [RFC8792]  Watsen, K., Auerswald, E., Farrel, A., and Q. Wu,
              "Handling Long Lines in Content of Internet-Drafts and
              RFCs", RFC 8792, DOI 10.17487/RFC8792, June 2020,
              <https://www.rfc-editor.org/info/rfc8792>.

16.2.  Informative References

   [A2A]      Linux Foundation, "Agent2Agent Protocol, v1.0.1", May
              2026, <https://a2a-protocol.org>.

   [ACP-CLIENT]
              Zed Industries, "Agent Client Protocol", n.d.,
              <https://agentclientprotocol.com>.

   [ACP-MERGER]
              LF AI & Data, "Agent Communication Protocol merges into
              the Agent2Agent project", August 2025.

   [MCP]      Anthropic, "Model Context Protocol specification, revision
              2025-11-25", November 2025,
              <https://modelcontextprotocol.io/
              specification/2025-11-25>.

   [RFC2033]  Myers, J., "Local Mail Transfer Protocol", RFC 2033,
              DOI 10.17487/RFC2033, October 1996,
              <https://www.rfc-editor.org/info/rfc2033>.

Rapp & Adams              Expires 24 April 2027                [Page 93]
Internet-Draft                    AMMP                      October 2026

   [RFC3864]  Klyne, G., Nottingham, M., and J. Mogul, "Registration
              Procedures for Message Header Fields", BCP 90, RFC 3864,
              DOI 10.17487/RFC3864, September 2004,
              <https://www.rfc-editor.org/info/rfc3864>.

   [RFC5321]  Klensin, J., "Simple Mail Transfer Protocol", RFC 5321,
              DOI 10.17487/RFC5321, October 2008,
              <https://www.rfc-editor.org/info/rfc5321>.

   [RFC6761]  Cheshire, S. and M. Krochmal, "Special-Use Domain Names",
              RFC 6761, DOI 10.17487/RFC6761, February 2013,
              <https://www.rfc-editor.org/info/rfc6761>.

Appendix A.  Submit-Request Schema

   _(Normative.)_ The schema below is written in the JSON Schema 2020-12
   dialect [JSON-SCHEMA-2020-12] and governs the artifact defined in
   Section 5.  This is the Agent-authored artifact, and its envelope is
   closed at every level: an unrecognized member is a hard rejection,
   surfaced as a rejected Result rather than as a silent drop
   (Section 13.1).

   NOTE: '\' line wrapping per RFC 8792

   {
     "$schema": "https://json-schema.org/draft/2020-12/schema",
     "$id": "https://amap-spec/schemas/submit-request.schema.json",
     "title": "submit-request",
     "description": "agent -> runtime: an inert outbound request. AMMP\
       , section \"Outbound: Submit Request\". Agent-authored: this doc\
       ument's envelope stays CLOSED (additionalProperties:false) at ev\
       ery level per the asymmetry of section \"Versioning\" — an unrec\
       ognized member here is a designed, legible `rejected` refusal, n\
       ever a silent drop.",
     "type": "object",
     "required": ["contract_version", "req_id", "draft"],
     "additionalProperties": false,
     "properties": {
       "contract_version": { "const": "2" },
       "req_id": {
         "type": "string",
         "minLength": 1,
         "description": "monotonic per namespace; filename-safe (path a\
       uthority is the filename, not this field — the filename-authorit\
       y-for-paths obligation of section \"Obligations\").",
         "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.-]*(?!\\n)$"
       },
       "agent_id": { "type": "string", "minLength": 1, "pattern": "^[^\\

Rapp & Adams              Expires 24 April 2027                [Page 94]
Internet-Draft                    AMMP                      October 2026

       u0000-\u001f\u007f]*(?!\\n)$" },
       "ts": { "type": "string", "minLength": 1, "pattern": "^[^\u0000-\
       \u001f\u007f]*(?!\\n)$" },
       "in_reply_to": { "type": "string", "pattern": "^[^\u0000-\u001f\\
       u007f]*(?!\\n)$" },
       "draft": {
         "type": "object",
         "required": ["to", "subject", "body_text"],
         "additionalProperties": false,
         "properties": {
           "to": {
             "type": "array",
             "items": {
               "type": "string",
               "description": "one bare addr-spec per element (no displ\
       ay name, address list, quoted local part, comment, IP literal, o\
       r folding whitespace — deliberately narrower than RFC 5322: thos\
       e forms are exactly what make allowlist evaluation non-mechanica\
       l). EAI/SMTPUTF8 is out of scope at this version.",
               "pattern": "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+@[A-Za-z0-9\
       .-]+(?!\\n)$"
             },
             "minItems": 1
           },
           "cc": {
             "type": "array",
             "items": {
               "type": "string",
               "pattern": "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+@[A-Za-z0-9\
       .-]+(?!\\n)$"
             }
           },
           "subject": { "type": "string", "pattern": "^[^\u0000-\u001f\\
       u007f]*(?!\\n)$" },
           "body_text": { "type": "string" },
           "reply_to_message_id": { "type": ["string", "null"], "patter\
       n": "^[^\u0000-\u001f\u007f]*(?!\\n)$" },
           "attachments": {
             "type": "array",
             "items": {
               "type": "object",
               "required": ["filename", "media_type", "size_bytes", "sh\
       a256"],
               "additionalProperties": false,
               "properties": {
                 "filename":   { "type": "string", "minLength": 1, "pat\
       tern": "^[^\u0000-\u001f\u007f]*(?!\\n)$" },
                 "media_type": { "type": "string", "minLength": 1, "pat\

Rapp & Adams              Expires 24 April 2027                [Page 95]
Internet-Draft                    AMMP                      October 2026

       tern": "^[^\u0000-\u001f\u007f]*(?!\\n)$" },
                 "size_bytes": { "type": "integer", "minimum": 0 },
                 "sha256":     { "type": "string", "pattern": "^[a-f0-9\
       ]{64}(?!\\n)$" }
               }
             }
           }
         }
       }
     }
   }

               Figure 10: schemas/submit-request.schema.json

Appendix B.  Result Schema

   _(Normative.)_ JSON Schema 2020-12 [JSON-SCHEMA-2020-12]; governs the
   artifact defined in Section 6.  Runtime-authored, and therefore open
   at every level: a consumer tolerates unrecognized members
   (Section 13.1).  The attachments[i].index == i binding stated in
   Section 6.1 is not expressible here and is enforced as a named post-
   check by the conformance gate (Appendix J).

   NOTE: '\' line wrapping per RFC 8792

   {
     "$schema": "https://json-schema.org/draft/2020-12/schema",
     "$id": "https://amap-spec/schemas/result.schema.json",
     "title": "result",
     "description": "runtime -> agent: verdict for a submit-request. AM\
       MP, section \"Outbound: Result\". Runtime-authored: this documen\
       t's envelope is OPEN (no additionalProperties:false) at every le\
       vel per the tolerance rule of section \"Versioning\" — a consume\
       r MUST ignore members it does not recognize; emitting an unrecog\
       nized member remains producer non-conformance.",
     "type": "object",
     "required": ["contract_version", "req_id", "outcome"],
     "properties": {
       "contract_version": { "const": "2" },
       "req_id": { "type": "string", "minLength": 1 },
       "outcome": {
         "type": "string",
         "enum": ["accepted", "queued_for_human", "rejected"]
       },
       "detail": { "type": ["string", "null"] },
       "reason_code": { "type": "string", "minLength": 1 },
       "ts": { "type": "string", "minLength": 1 },
       "recipients": { "type": "array", "items": { "type": "string" } },

Rapp & Adams              Expires 24 April 2027                [Page 96]
Internet-Draft                    AMMP                      October 2026

       "message_id": { "type": ["string", "null"] },
       "job_id": { "type": ["string", "null"] },
       "attestation": {
         "type": "object",
         "required": ["form"],
         "properties": {
           "form":   { "type": "string", "enum": ["self-stamp", "verifi\
       able"] },
           "token":  { "type": "string", "minLength": 1 },
           "claims": { "type": "object" }
         }
       },
       "attachments": {
         "type": "array",
         "items": {
           "type": "object",
           "required": ["index", "outcome"],
           "properties": {
             "index":   { "type": "integer", "minimum": 0 },
             "outcome": { "type": "string",
                          "enum": ["accepted", "dlp_held", "rejected_ty\
       pe", "too_large"] },
             "detail":  { "type": ["string", "null"] }
           }
         }
       }
     }
   }

                   Figure 11: schemas/result.schema.json

Appendix C.  Deliver-Notice Schema

   _(Normative.)_ JSON Schema 2020-12 [JSON-SCHEMA-2020-12]; governs the
   artifact defined in Section 7.  Runtime-authored and open at every
   level.  The two allOf conditionals implement the clean-gating of
   content_ref and sha256 (Section 7.2); the content_ref index and
   prefix binding, by contrast, is not expressible here and is enforced
   as a named post-check (Appendix J).

   NOTE: '\' line wrapping per RFC 8792

   {
     "$schema": "https://json-schema.org/draft/2020-12/schema",
     "$id": "https://amap-spec/schemas/deliver-notice.schema.json",
     "title": "deliver-notice",
     "description": "runtime -> connector: one notice per delivered mes\
       sage. AMMP, section \"Inbound: Deliver Notice and Inbound Messag\

Rapp & Adams              Expires 24 April 2027                [Page 97]
Internet-Draft                    AMMP                      October 2026

       e\". Runtime-authored: this document's envelope is OPEN (no addi\
       tionalProperties:false) at every level per the tolerance rule o\
       f section \"Versioning\" — a consumer MUST ignore members it doe\
       s not recognize; emitting an unrecognized member remains produce\
       r non-conformance.",
     "type": "object",
     "required": ["contract_version", "notice_id", "ts", "kind", "messa\
       ge"],
     "properties": {
       "contract_version": { "const": "2" },
       "notice_id": { "type": "string", "minLength": 1, "pattern": "^[A\
       -Za-z0-9_][A-Za-z0-9_.-]*(?!\\n)$" },
       "ts": { "type": "string", "minLength": 1 },
       "kind": { "const": "deliver" },
       "message": {
         "type": "object",
         "required": ["id", "from", "subject", "preview", "mailbox"],
         "properties": {
           "id": { "type": "string", "minLength": 1 },
           "from": { "type": "string", "minLength": 1 },
           "subject": { "type": "string" },
           "preview": { "type": "string" },
           "mailbox": { "type": "string", "minLength": 1 },
           "task_id": { "type": "string", "minLength": 1 },
           "thread_id": { "type": "string" },
           "in_reply_to": { "type": "string" },
           "references": { "type": "array", "items": { "type": "string\
       " } },
           "sender_standing": {
             "type": "string",
             "description": "advisory, open vocabulary (known, agent-ti\
       er, first-contact, read-only, ...). A consumer MUST tolerate a v\
       alue outside this list, treating it as opaque — never grounds t\
       o drop the notice; a runtime may add a value without a new wire \
       major (section \"Versioning\")."
           },
           "provenance": {
             "type": "string",
             "description": "advisory, open vocabulary (external, inter\
       nal, ...). A consumer MUST tolerate a value outside this list, t\
       reating it as opaque — never grounds to drop the notice; a runti\
       me may add a value without a new wire major (section \"Versionin\
       g\")."
           },
           "verdict": {
             "type": "object",
             "properties": {
               "auth": { "type": "string" },

Rapp & Adams              Expires 24 April 2027                [Page 98]
Internet-Draft                    AMMP                      October 2026

               "screen": { "type": "string" },
               "entitlement": { "type": "string" }
             }
           },
           "attachments": {
             "type": "array",
             "items": {
               "type": "object",
               "required": ["filename", "media_type", "size_bytes", "di\
       sposition"],
               "properties": {
                 "filename":    { "type": "string", "minLength": 1 },
                 "media_type":  { "type": "string", "minLength": 1 },
                 "size_bytes":  { "type": "integer", "minimum": 0 },
                 "sha256":      { "type": "string", "pattern": "^[a-f0-\
       9]{64}(?!\\n)$" },
                 "disposition": { "type": "string",
                                  "enum": ["clean", "stripped", "quaran\
       tined", "unscanned"] },
                 "content_ref": { "type": "string",
                                  "pattern": "^[A-Za-z0-9_.-]+\\.attach\
       ments/(0|[1-9][0-9]*)(?!\\n)$" }
               },
               "allOf": [
                 { "if": { "required": ["content_ref"] },
                   "then": { "properties": { "disposition": { "const": \
       "clean" } } } },
                 { "if": { "required": ["sha256"] },
                   "then": { "properties": { "disposition": { "const": \
       "clean" } } } }
               ]
             }
           }
         }
       }
     }
   }

               Figure 12: schemas/deliver-notice.schema.json

Appendix D.  Inbound-Message Schema

   _(Normative.)_ JSON Schema 2020-12 [JSON-SCHEMA-2020-12]; governs the
   artifact defined in Section 7.3.  Runtime-authored and open at every
   level.  Its Descriptor rules, including the clean-gating
   conditionals, match the deliver-notice schema's.

Rapp & Adams              Expires 24 April 2027                [Page 99]
Internet-Draft                    AMMP                      October 2026

   NOTE: '\' line wrapping per RFC 8792

   {
     "$schema": "https://json-schema.org/draft/2020-12/schema",
     "$id": "https://amap-spec/schemas/inbound-message.schema.json",
     "title": "inbound-message",
     "description": "runtime -> connector: the full delivered message (\
       incl. body_text), spooled at inbound/messages/notice-<id>.json, \
       keyed on the runtime-minted notice-id (never the provider messag\
       e.id). AMMP, section \"The Inbound Message (body spool)\". Runti\
       me-authored: this document's envelope is OPEN (no additionalProp\
       erties:false) at every level per the tolerance rule of section \\
       "Versioning\" — a consumer MUST ignore members it does not recog\
       nize; emitting an unrecognized member remains producer non-confo\
       rmance.",
     "type": "object",
     "required": ["contract_version", "notice_id", "body_text"],
     "properties": {
       "contract_version": { "const": "2" },
       "notice_id": { "type": "string", "minLength": 1, "pattern": "^[A\
       -Za-z0-9_][A-Za-z0-9_.-]*(?!\\n)$" },
       "body_text": { "type": "string" },
       "id": { "type": "string" },
       "date": { "type": "string" },
       "from": { "type": "string" },
       "to": { "type": "string" },
       "cc": { "type": "string" },
       "subject": { "type": "string" },
       "attachments": {
         "type": "array",
         "items": {
           "type": "object",
           "required": ["filename", "media_type", "size_bytes", "dispos\
       ition"],
           "properties": {
             "filename":    { "type": "string", "minLength": 1 },
             "media_type":  { "type": "string", "minLength": 1 },
             "size_bytes":  { "type": "integer", "minimum": 0 },
             "sha256":      { "type": "string", "pattern": "^[a-f0-9]{6\
       4}(?!\\n)$" },
             "disposition": { "type": "string",
                              "enum": ["clean", "stripped", "quarantine\
       d", "unscanned"] },
             "content_ref": { "type": "string",
                              "pattern": "^[A-Za-z0-9_.-]+\\.attachment\
       s/(0|[1-9][0-9]*)(?!\\n)$" }
           },
           "allOf": [

Rapp & Adams              Expires 24 April 2027               [Page 100]
Internet-Draft                    AMMP                      October 2026

             { "if": { "required": ["content_ref"] },
               "then": { "properties": { "disposition": { "const": "cle\
       an" } } } },
             { "if": { "required": ["sha256"] },
               "then": { "properties": { "disposition": { "const": "cle\
       an" } } } }
           ]
         }
       }
     }
   }

               Figure 13: schemas/inbound-message.schema.json

Appendix E.  Binding-Record Schema

   _(Normative.)_ JSON Schema 2020-12 [JSON-SCHEMA-2020-12]; governs the
   artifact defined in Section 9.  This envelope is closed, for the
   reason given in Section 9.1 — an anti-secret-leak lint rather than a
   compatibility surface.

   NOTE: '\' line wrapping per RFC 8792

   {
     "$schema": "https://json-schema.org/draft/2020-12/schema",
     "$id": "https://amap-spec/schemas/binding-record.schema.json",
     "title": "binding-record",
     "description": "runtime/provisioner -> volume root: identity.json\
       , the mailbox<->volume binding record. AMMP, section \"Binding R\
       ecord\". INFORMATIONAL, NOT AUTHORITATIVE: the volume is agent-w\
       ritable, so a consumer MUST treat contents as untrusted data an\
       d MUST tolerate the file's absence. Unlike the other runtime-aut\
       hored documents, this schema is closed (section \"Versioning\") \
       — its closure is an anti-secret-leak lint, not a compatibility s\
       urface.",
     "type": "object",
     "required": ["agent_id", "address", "provisioned_ts"],
     "additionalProperties": false,
     "properties": {
       "contract_version": { "const": "2" },
       "agent_id": { "type": "string", "minLength": 1 },
       "address": { "type": "string", "minLength": 1 },
       "provisioned_ts": { "type": "string", "minLength": 1 },
       "label": { "type": "string" }
     }
   }

               Figure 14: schemas/binding-record.schema.json

Rapp & Adams              Expires 24 April 2027               [Page 101]
Internet-Draft                    AMMP                      October 2026

Appendix F.  Peer-Origin Profile Schemas

   _(Normative.)_ JSON Schema 2020-12 [JSON-SCHEMA-2020-12].  The first
   schema governs a Peer Notice, the artifact the peer-origin profile
   (Section 12) places under <agent-id>/peer/notices/ (Section 12.2).
   Its envelope is Runtime-authored and therefore open (Section 13.1).
   A document with kind: "peer" found under inbound/notices/ fails the
   deliver-notice schema (Appendix C), and a document with kind:
   "deliver" found under peer/notices/ fails this one; both are dropped
   under the rule of Section 7.1.

   NOTE: '\' line wrapping per RFC 8792

   {
     "$schema": "https://json-schema.org/draft/2020-12/schema",
     "$id": "https://amap-spec/schemas/peer-notice.schema.json",
     "title": "peer-notice",
     "description": "runtime -> connector: one notice per peer message \
       placed in <agent-id>/peer/notices/. AMMP, section \"The Peer-Ori\
       gin Profile (Optional)\". Same layout as deliver-notice with kin\
       d and mailbox pinned to \"peer\", message.from pinned to a bare \
       addr-spec (the one runtime-asserted `from` in the protocol), mes\
       sage.id / in_reply_to / references pinned to the notice_id chars\
       et (the peer message id is bare on the wire; its Message-ID enco\
       ding exists only inside a mail header, never in an AMMP document\
       ), plus the optional runtime-asserted message.sender_exposure. R\
       untime-authored: envelope OPEN at every level per section \"Vers\
       ioning\" — a consumer MUST ignore members it does not recognize\
       . A document with kind \"peer\" found under inbound/notices/ fai\
       ls deliver-notice.schema.json; a document with kind \"deliver\" \
       found under peer/notices/ fails this schema: both are dropped un\
       der the drop rule of section \"Field semantics and obligations\\
       " (the profile's cross-lock, section \"The Peer Tree\").",
     "type": "object",
     "required": ["contract_version", "notice_id", "ts", "kind", "messa\
       ge"],
     "properties": {
       "contract_version": { "const": "2" },
       "notice_id": { "type": "string", "minLength": 1, "pattern": "^[A\
       -Za-z0-9_][A-Za-z0-9_.-]*(?!\\n)$" },
       "ts": { "type": "string", "minLength": 1 },
       "kind": { "const": "peer" },
       "message": {
         "type": "object",
         "required": ["id", "from", "subject", "preview", "mailbox"],
         "properties": {
           "id": {
             "type": "string", "minLength": 1,

Rapp & Adams              Expires 24 April 2027               [Page 102]
Internet-Draft                    AMMP                      October 2026

             "description": "the peer message id: minted by the sendin\
       g runtime on accepting the submit, returned to the sender as res\
       ult.message_id, >=128 bits of entropy, globally unique and ungue\
       ssable. Bare on the wire under the notice_id charset; when carri\
       ed in a mail header it is encoded as <id@domain> and decoded aga\
       in before it enters any AMMP document.",
             "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.-]*(?!\\n)$"
           },
           "from": {
             "type": "string", "minLength": 1,
             "description": "bare addr-spec, runtime-asserted from the \
       restricted write path or the verified cross-host statement, neve\
       r copied from sender text. The one `from` in the protocol a cons\
       umer may treat as authenticated origin.",
             "pattern": "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+@[A-Za-z0-9.-\
       ]+(?!\\n)$"
           },
           "subject": { "type": "string" },
           "preview": { "type": "string" },
           "mailbox": { "const": "peer" },
           "task_id": { "type": "string", "minLength": 1 },
           "thread_id": { "type": "string" },
           "in_reply_to": {
             "type": "string", "minLength": 1,
             "description": "present iff the placing runtime resolved i\
       t from its own ledger (a peer message id it issued or placed). R\
       EQUIRED on a result by prose; a fresh task omits it. Bare peer i\
       d, never the Message-ID-encoded form.",
             "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.-]*(?!\\n)$"
           },
           "references": {
             "type": "array",
             "items": { "type": "string", "minLength": 1, "pattern": "^\
       [A-Za-z0-9_][A-Za-z0-9_.-]*(?!\\n)$" }
           },
           "sender_standing": {
             "type": "string",
             "description": "advisory, open vocabulary; SHOULD be `agen\
       t-tier` when set on a peer notice."
           },
           "provenance": {
             "type": "string",
             "description": "advisory, open vocabulary; redundant with \
       the tree on a peer notice and MAY be omitted. Never grounds to d\
       erive or override origin class."
           },
           "verdict": {
             "type": "object",

Rapp & Adams              Expires 24 April 2027               [Page 103]
Internet-Draft                    AMMP                      October 2026

             "properties": {
               "auth": { "type": "string" },
               "screen": { "type": "string" },
               "entitlement": { "type": "string" }
             }
           },
           "sender_exposure": {
             "type": "object",
             "description": "runtime-asserted, never sender-settable (\
       a submit-request carrying it is `rejected` by the closed envelop\
       e; cross-host it is taken only from the verified statement). Abs\
       ence means NOT ASSESSED, never `no exposure`. MUST NOT drive rou\
       ting or treatment.",
             "required": ["asserted_by", "external_mail_delivered", "wi\
       ndow_start", "window_end"],
             "properties": {
               "asserted_by": { "type": "string", "minLength": 1, "patt\
       ern": "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+@[A-Za-z0-9.-]+(?!\\n)$\
       " },
               "external_mail_delivered": { "type": "boolean" },
               "window_start": { "type": "string", "minLength": 1 },
               "window_end": { "type": "string", "minLength": 1 },
               "last_external_delivery_ts": { "type": "string", "minLen\
       gth": 1 },
               "derived_from": {
                 "type": "array",
                 "items": { "type": "string", "minLength": 1, "pattern"\
       : "^[A-Za-z0-9_][A-Za-z0-9_.-]*(?!\\n)$" }
               }
             },
             "if": { "required": ["external_mail_delivered"], "properti\
       es": { "external_mail_delivered": { "const": true } } },
             "then": { "required": ["last_external_delivery_ts"] }
           },
           "attachments": {
             "type": "array",
             "items": {
               "type": "object",
               "required": ["filename", "media_type", "size_bytes", "di\
       sposition"],
               "properties": {
                 "filename":    { "type": "string", "minLength": 1 },
                 "media_type":  { "type": "string", "minLength": 1 },
                 "size_bytes":  { "type": "integer", "minimum": 0 },
                 "sha256":      { "type": "string", "pattern": "^[a-f0-\
       9]{64}(?!\\n)$" },
                 "disposition": { "type": "string",
                                  "enum": ["clean", "stripped", "quaran\

Rapp & Adams              Expires 24 April 2027               [Page 104]
Internet-Draft                    AMMP                      October 2026

       tined", "unscanned"] },
                 "content_ref": { "type": "string",
                                  "pattern": "^[A-Za-z0-9_.-]+\\.attach\
       ments/(0|[1-9][0-9]*)(?!\\n)$" }
               },
               "allOf": [
                 { "if": { "required": ["content_ref"] },
                   "then": { "properties": { "disposition": { "const": \
       "clean" } } } },
                 { "if": { "required": ["sha256"] },
                   "then": { "properties": { "disposition": { "const": \
       "clean" } } } }
               ]
             }
           }
         }
       }
     }
   }

                 Figure 15: schemas/peer-notice.schema.json

   Between hosts, a peer message travels as mail carrying a signed Peer
   Statement in one AMAP-Peer-Statement header field (Section 12.6).
   The receiving Runtime verifies the statement's bytes before parsing
   them, then validates them against the schema below, which is closed
   at every level: these are signed bytes another Runtime acts on, not a
   Runtime-authored document a Connector reads.  The statement-*
   fixtures (Appendix J) exercise the schema, and the signed cases
   exercise the verification rules.

   NOTE: '\' line wrapping per RFC 8792

   {
     "$schema": "https://json-schema.org/draft/2020-12/schema",
     "$id": "https://amap-spec/schemas/peer-statement.schema.json",
     "title": "peer-statement",
     "description": "runtime -> runtime: the signed statement that carr\
       ies a peer message between hosts. AMMP, section \"Peer Traffic B\
       etween Hosts\". The sending runtime signs the statement's exact \
       UTF-8 bytes with Ed25519 and ships both in one AMAP-Peer-Stateme\
       nt header field (section \"Carriage, and the Candidate the Verif\
       ier Sees\"); the receiving runtime verifies the signature over t\
       he received bytes before parsing, then validates against this sc\
       hema. CLOSED at every level, unlike the runtime-authored notices\
       : these are signed bytes another runtime acts on, a member the r\
       eceiver does not know is a member it cannot bind to what it publ\
       ishes, and the version member `v` is the only evolution path. A \

Rapp & Adams              Expires 24 April 2027               [Page 105]
Internet-Draft                    AMMP                      October 2026

       statement that fails this schema fails verification, and the mes\
       sage is demoted to mail (sections \"Verification\" and \"Outcome\
       s\"). Numbers are lexical integers (no fraction or exponent) an\
       d strings contain no unpaired surrogate (section \"The Statement\
       \", checked at check 5(b) of section \"Verification\"); the sche\
       ma cannot express either, nor calendar validity, interval order\
       , the entropy of `id`, or any binding to the message: those are \
       the verification steps of section \"Verification\".",
     "type": "object",
     "additionalProperties": false,
     "required": [
       "v",
       "key_id",
       "kind",
       "id",
       "from",
       "to",
       "ts",
       "subject",
       "body_sha256",
       "attachments"
     ],
     "properties": {
       "v": {
         "type": "integer",
         "const": 1
       },
       "key_id": {
         "type": "string",
         "description": "names one key in the signing peer's configure\
       d key set; opaque, unique within that set, 1-64 characters.",
         "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}(?!\\n)$"
       },
       "kind": {
         "const": "peer"
       },
       "id": {
         "type": "string",
         "minLength": 1,
         "description": "the peer message id (section \"Correlation an\
       d Replies\"), bare. A sending runtime mints it under ^[A-Za-z0-9\
       _][A-Za-z0-9_-]*$ (hex, or base64url re-minted if it would star\
       t with '-'): the notice_id charset without '.', so its Message-I\
       D encoding <id@domain> is a valid msg-id (section \"The Statemen\
       t\"); the receiver publishes it as message.id.",
         "pattern": "^[A-Za-z0-9_][A-Za-z0-9_-]*(?!\\n)$"
       },
       "in_reply_to": {

Rapp & Adams              Expires 24 April 2027               [Page 106]
Internet-Draft                    AMMP                      October 2026

         "type": "string",
         "minLength": 1,
         "description": "the peer message id this message answers (sect\
       ion \"Correlation and Replies\"), bare, minted by the other runt\
       ime under the same pattern. Absent on a fresh task. Resolved aga\
       inst the receiver's ledger with the participants bound (section \
       \"Correlation and Edges Between Hosts\").",
         "pattern": "^[A-Za-z0-9_][A-Za-z0-9_-]*(?!\\n)$"
       },
       "from": {
         "type": "string",
         "minLength": 1,
         "description": "bare addr-spec of the sending agent; its domai\
       n MUST be one of the signing peer's configured domains.",
         "pattern": "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+@[A-Za-z0-9.-]+(?\
       !\\n)$"
       },
       "to": {
         "type": "string",
         "minLength": 1,
         "description": "bare addr-spec of the one receiving agent (sec\
       tion \"Addressing\": a peer message is addressed to one agent).",
         "pattern": "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+@[A-Za-z0-9.-]+(?\
       !\\n)$"
       },
       "ts": {
         "type": "string",
         "description": "when the sending runtime signed: RFC 3339 UTC\
       , whole seconds, upper-case T and Z, no offset. MUST be a real c\
       alendar instant (section \"The Statement\").",
         "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9\
       ]{2}Z(?!\\n)$"
       },
       "subject": {
         "type": "string",
         "description": "the subject the receiver publishes as message.\
       subject; no control characters.",
         "pattern": "^[^\u0000-\u001f]*(?!\\n)$"
       },
       "body_sha256": {
         "type": "string",
         "description": "SHA-256 of the UTF-8 encoding of the body_tex\
       t the receiver will publish, lower-case hex.",
         "pattern": "^[a-f0-9]{64}(?!\\n)$"
       },
       "attachments": {
         "type": "array",
         "description": "one entry per attachment, in order; empty whe\

Rapp & Adams              Expires 24 April 2027               [Page 107]
Internet-Draft                    AMMP                      October 2026

       n there are none.",
         "items": {
           "type": "object",
           "additionalProperties": false,
           "required": [
             "sha256",
             "size_bytes",
             "filename",
             "media_type"
           ],
           "properties": {
             "sha256": {
               "type": "string",
               "pattern": "^[a-f0-9]{64}(?!\\n)$"
             },
             "size_bytes": {
               "type": "integer",
               "minimum": 0
             },
             "filename": {
               "type": "string",
               "minLength": 1
             },
             "media_type": {
               "type": "string",
               "minLength": 1
             }
           }
         }
       },
       "sender_exposure": {
         "type": "object",
         "additionalProperties": false,
         "description": "section \"Sender Exposure\", asserted by the s\
       ending runtime; omitted when it made no assessment. Timestamps i\
       n ts's format; window_start <= window_end; last_external_deliver\
       y_ts present iff external_mail_delivered is true, and then withi\
       n the window; asserted_by is the signing runtime's configured ru\
       ntime address (section \"The Statement\"; checks 5(b) and 8 of s\
       ection \"Verification\").",
         "required": [
           "asserted_by",
           "external_mail_delivered",
           "window_start",
           "window_end"
         ],
         "properties": {
           "asserted_by": {

Rapp & Adams              Expires 24 April 2027               [Page 108]
Internet-Draft                    AMMP                      October 2026

             "type": "string",
             "minLength": 1,
             "pattern": "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+@[A-Za-z0-9.-\
       ]+(?!\\n)$"
           },
           "external_mail_delivered": {
             "type": "boolean"
           },
           "window_start": {
             "type": "string",
             "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:\
       [0-9]{2}Z(?!\\n)$"
           },
           "window_end": {
             "type": "string",
             "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:\
       [0-9]{2}Z(?!\\n)$"
           },
           "last_external_delivery_ts": {
             "type": "string",
             "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:\
       [0-9]{2}Z(?!\\n)$"
           },
           "derived_from": {
             "type": "array",
             "items": {
               "type": "string",
               "minLength": 1,
               "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.-]*(?!\\n)$"
             }
           }
         },
         "if": {
           "required": [
             "external_mail_delivered"
           ],
           "properties": {
             "external_mail_delivered": {
               "const": true
             }
           }
         },
         "then": {
           "required": [
             "last_external_delivery_ts"
           ]
         }
       },

Rapp & Adams              Expires 24 April 2027               [Page 109]
Internet-Draft                    AMMP                      October 2026

       "sender_since": {
         "type": "string",
         "description": "OPTIONAL (section \"Optional: Sender Age, and \
       Deployment Admission Policy\"): when the sending runtime first e\
       nrolled the sending agent's identity (as that runtime defines id\
       entity), from its durable registry; same format as ts and not la\
       ter than ts. A runtime assertion, not a sender claim. Never rese\
       t for a continuation of the same identity (restart, rebind, re-c\
       reation, key rotation); a new enrolment starts a new value. A re\
       ceiver enforcing a sender age keeps the last value per (runtime\
       , from) and refuses an earlier one as inconsistent. Absence mean\
       s NOT ASSERTED, never 'new'.",
         "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9\
       ]{2}Z(?!\\n)$"
       }
     }
   }

               Figure 16: schemas/peer-statement.schema.json

   A receiving Runtime reports what it did with a statement it did not
   place (refused, dropped or demoted) to the configured sending Runtime
   in a signed Control Notice, carried in one AMAP-Peer-Control header
   field with the same grammar and key (Section 12.6.8).  Its kind is
   "control", so neither document validates as the other; it is closed
   and carries no free text.

   NOTE: '\' line wrapping per RFC 8792

   {
     "$schema": "https://json-schema.org/draft/2020-12/schema",
     "$id": "https://amap-spec/schemas/peer-control.schema.json",
     "title": "peer-control",
     "description": "runtime -> runtime: the signed control notice a re\
       ceiving runtime sends to a configured sending runtime about wha\
       t it did with an authenticated peer statement that it did not pl\
       ace (refused, dropped, or demoted). AMMP, section \"Control Noti\
       ces\". Carried in an AMAP-Peer-Control header field with the sam\
       e k/s/b grammar as AMAP-Peer-Statement, signed with the same per\
       -runtime key; `kind` is \"control\", so a control notice never v\
       alidates as a peer statement and is never placed for an agent. C\
       LOSED at every level, and it carries no free text: the receivin\
       g runtime composes any agent-facing words itself (the delivery f\
       ailure notice of section \"Correlation and Replies\"). The schem\
       a cannot check that `about` names a peer message this runtime is\
       sued to the notice's signer; that is the receiving runtime's che\
       ck (section \"Control Notices\").",
     "type": "object",

Rapp & Adams              Expires 24 April 2027               [Page 110]
Internet-Draft                    AMMP                      October 2026

     "additionalProperties": false,
     "required": [
       "v",
       "key_id",
       "kind",
       "id",
       "from",
       "to",
       "ts",
       "about",
       "outcome",
       "cause"
     ],
     "properties": {
       "v": {
         "type": "integer",
         "const": 1
       },
       "key_id": {
         "type": "string",
         "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}(?!\\n)$"
       },
       "kind": {
         "const": "control"
       },
       "id": {
         "type": "string",
         "description": "this notice's own id, minted like a peer messa\
       ge id; replay-checked in the same store, scoped to the signing r\
       untime.",
         "pattern": "^[A-Za-z0-9_][A-Za-z0-9_-]*(?!\\n)$"
       },
       "from": {
         "type": "string",
         "description": "the signing (receiving) runtime's configured r\
       untime address.",
         "pattern": "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+@[A-Za-z0-9.-]+(?\
       !\\n)$"
       },
       "to": {
         "type": "string",
         "description": "the configured runtime address of the runtime \
       that signed the statement this notice is about.",
         "pattern": "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+@[A-Za-z0-9.-]+(?\
       !\\n)$"
       },
       "ts": {
         "type": "string",

Rapp & Adams              Expires 24 April 2027               [Page 111]
Internet-Draft                    AMMP                      October 2026

         "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9\
       ]{2}Z(?!\\n)$"
       },
       "about": {
         "type": "string",
         "description": "the `id` of the peer statement this notice is \
       about.",
         "pattern": "^[A-Za-z0-9_][A-Za-z0-9_-]*(?!\\n)$"
       },
       "outcome": {
         "enum": [
           "refused",
           "dropped",
           "demoted"
         ]
       },
       "cause": {
         "description": "why, from the outcome table of section \"Outco\
       mes\". `policy` is a deployment refusal (for example a first-sig\
       ht quarantine), named further by policy_code.",
         "enum": [
           "statement_value",
           "key_domain",
           "ts_window",
           "exposure_signer",
           "body_charset",
           "body_digest",
           "attachment_count",
           "attachment_digest",
           "message_id",
           "duplicate",
           "misrouted",
           "unresolved_reply",
           "no_edge",
           "policy"
         ]
       },
       "policy_code": {
         "type": "string",
         "description": "deployment-defined token, only with cause `pol\
       icy` (e.g. `quarantined`).",
         "pattern": "^[a-z][a-z0-9_]{0,63}(?!\\n)$"
       }
     },
     "allOf": [
       {
         "if": {
           "required": [

Rapp & Adams              Expires 24 April 2027               [Page 112]
Internet-Draft                    AMMP                      October 2026

             "policy_code"
           ]
         },
         "then": {
           "properties": {
             "cause": {
               "const": "policy"
             }
           }
         }
       },
       {
         "if": {
           "properties": {
             "cause": {
               "const": "policy"
             }
           },
           "required": [
             "cause"
           ]
         },
         "then": {
           "required": [
             "policy_code"
           ]
         }
       },
       {
         "if": {
           "properties": {
             "outcome": {
               "const": "refused"
             }
           },
           "required": [
             "outcome"
           ]
         },
         "then": {
           "properties": {
             "cause": {
               "enum": [
                 "unresolved_reply",
                 "no_edge",
                 "policy"
               ]
             }

Rapp & Adams              Expires 24 April 2027               [Page 113]
Internet-Draft                    AMMP                      October 2026

           }
         }
       },
       {
         "if": {
           "properties": {
             "outcome": {
               "const": "dropped"
             }
           },
           "required": [
             "outcome"
           ]
         },
         "then": {
           "properties": {
             "cause": {
               "enum": [
                 "duplicate",
                 "misrouted"
               ]
             }
           }
         }
       },
       {
         "if": {
           "properties": {
             "outcome": {
               "const": "demoted"
             }
           },
           "required": [
             "outcome"
           ]
         },
         "then": {
           "properties": {
             "cause": {
               "enum": [
                 "statement_value",
                 "key_domain",
                 "ts_window",
                 "exposure_signer",
                 "body_charset",
                 "body_digest",
                 "attachment_count",
                 "attachment_digest",

Rapp & Adams              Expires 24 April 2027               [Page 114]
Internet-Draft                    AMMP                      October 2026

                 "message_id"
               ]
             }
           }
         }
       }
     ]
   }

                Figure 17: schemas/peer-control.schema.json

Appendix G.  Peer-Directory Schema

   _(Normative.)_ JSON Schema 2020-12 [JSON-SCHEMA-2020-12]; governs the
   optional peer directory defined in Section 10, which a Runtime MAY
   publish at <agent-id>/directory.json.  The document is Runtime-
   authored and therefore open (Section 13.1), and it is advisory: the
   schema pins its shape, never what it predicts.

   NOTE: '\' line wrapping per RFC 8792

   {
     "$schema": "https://json-schema.org/draft/2020-12/schema",
     "$id": "https://amap-spec/schemas/directory.schema.json",
     "title": "directory",
     "description": "runtime -> connector: the router's most recent wor\
       d about which peers this agent may address, published per instan\
       ce at <agent-id>/directory.json. AMMP, section \"The Peer Direct\
       ory\". ADVISORY, NEVER AUTHORITATIVE: an address's presence is n\
       ot a prediction that a submit to it will be accepted, nor its ab\
       sence a prediction of refusal — authorisation is evaluated by th\
       e runtime at submit time and only the result (section \"Outbound\
       : Result\") reports what happened. A runtime MUST NOT read this \
       file back as authorisation; enforcement stays the runtime's own \
       graph check on every message. The file MAY be absent. Runtime-au\
       thored: envelope is OPEN at every level per the tolerance rule o\
       f section \"Versioning\" — a consumer MUST ignore members it doe\
       s not recognize; emitting an unrecognized member remains produce\
       r non-conformance.",
     "type": "object",
     "required": [
       "router",
       "written_at",
       "delegate_to",
       "mail_to"
     ],
     "properties": {
       "contract_version": {

Rapp & Adams              Expires 24 April 2027               [Page 115]
Internet-Draft                    AMMP                      October 2026

         "const": "2"
       },
       "router": {
         "type": "string",
         "description": "the publishing runtime's own address, one bar\
       e addr-spec. Never appears in delegate_to or mail_to.",
         "pattern": "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+@[A-Za-z0-9.-]+(?\
       !\\n)$"
       },
       "written_at": {
         "type": "string",
         "minLength": 1,
         "description": "RFC 3339, the publishing runtime's clock. The \
       only staleness signal a consumer gets; an honest marker, not a f\
       reshness guarantee.",
         "pattern": "^[^\\u0000-\\u001f\\u007f]*(?!\\n)$"
       },
       "delegate_to": {
         "type": "array",
         "description": "sorted addresses this agent may task — the aut\
       horisation graph transposed for this instance. Always present; e\
       mpty when the lane is closed. Never this agent's own address, an\
       d never who may task THIS agent (an allowlist, withheld by const\
       ruction).",
         "items": {
           "type": "string",
           "pattern": "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+@[A-Za-z0-9.-]+\
       (?!\\n)$"
         }
       },
       "mail_to": {
         "type": "array",
         "description": "sorted addresses on the mutual mail lane. Alwa\
       ys present; empty when the lane is closed, which is the normal c\
       ase for a delegation-only deployment.",
         "items": {
           "type": "string",
           "pattern": "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+@[A-Za-z0-9.-]+\
       (?!\\n)$"
         }
       }
     }
   }

                  Figure 18: schemas/directory.schema.json

Rapp & Adams              Expires 24 April 2027               [Page 116]
Internet-Draft                    AMMP                      October 2026

Appendix H.  Fleet-Roster Schema

   _(Normative.)_ JSON Schema 2020-12; governs the fleet roster defined
   in Section 11.  The document is Runtime-authored and therefore open
   (Section 13.1), and it pins shape only: whether a member exists says
   nothing about whether it may be addressed.

   NOTE: '\' line wrapping per RFC 8792

   {
     "$schema": "https://json-schema.org/draft/2020-12/schema",
     "$id": "https://amap-spec/schemas/roster.schema.json",
     "title": "roster",
     "description": "runtime -> every agent: the fleet roster, the runt\
       ime's most recent word about which instances it has admitted, wr\
       itten once for the whole fleet to a location the deployment desi\
       gnates and exposes read-only to each agent it points at the rost\
       er. AMMP, section \"The Fleet Roster\". EXISTENCE IS NOT AUTHORI\
       SATION: a member's presence says the instance exists, never tha\
       t this agent may address it or that a submission to it will be a\
       ccepted; authorisation is evaluated by the runtime at submit tim\
       e and only the result reports what happened. NOT A SOURCE OF TH\
       E READER'S IDENTITY: the reader's own address may appear, and at\
       tribution stays the namespace a file arrived in. A runtime MUST \
       NOT read this file back. The file MAY be absent; absent or stal\
       e means unknown, never an empty fleet. Runtime-authored: envelop\
       e is OPEN at every level — a consumer MUST ignore members it doe\
       s not recognize; emitting an unrecognized member remains produce\
       r non-conformance.",
     "type": "object",
     "required": [
       "contract_version",
       "router",
       "written_at",
       "members"
     ],
     "properties": {
       "contract_version": {
         "const": "2"
       },
       "router": {
         "type": "string",
         "description": "the publishing runtime's own address, one bar\
       e addr-spec. Never appears in members.",
         "pattern": "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+@[A-Za-z0-9.-]+(?\
       !\\n)$"
       },
       "written_at": {

Rapp & Adams              Expires 24 April 2027               [Page 117]
Internet-Draft                    AMMP                      October 2026

         "type": "string",
         "minLength": 1,
         "description": "RFC 3339, the publishing runtime's clock. The \
       only staleness signal a consumer gets; an honest marker, not a f\
       reshness guarantee.",
         "pattern": "^[^\\u0000-\\u001f\\u007f]*(?!\\n)$"
       },
       "interval_s": {
         "type": "number",
         "minimum": 0,
         "description": "OPTIONAL: the runtime's nominal rewrite interv\
       al in seconds, so a consumer can judge written_at against it. Om\
       itted when the runtime does not know it."
       },
       "members": {
         "type": "array",
         "description": "the instances the runtime has admitted, sorte\
       d by address. Always present; never written as empty when member\
       ship is unknown (the previous file is left to age instead). The \
       router is never a member.",
         "items": {
           "type": "object",
           "required": [
             "address",
             "state"
           ],
           "properties": {
             "address": {
               "type": "string",
               "description": "the member's address, one bare addr-spec\
       .",
               "pattern": "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+@[A-Za-z0-9\
       .-]+(?!\\n)$"
             },
             "state": {
               "type": "string",
               "minLength": 1,
               "description": "OPEN advisory vocabulary; \"admitted\" i\
       s the only value defined at this version. A consumer MUST tolera\
       te an unknown value as opaque.",
               "pattern": "^[^\\u0000-\\u001f\\u007f]*(?!\\n)$"
             }
           }
         }
       }
     }
   }

Rapp & Adams              Expires 24 April 2027               [Page 118]
Internet-Draft                    AMMP                      October 2026

                   Figure 19: schemas/roster.schema.json

Appendix I.  Golden Examples

   _(Normative.)_ The documents below are reproduced byte-verbatim from
   the conformance suite (Appendix J).  They are a small selection
   covering the core artifact types and three rejections; the suite
   itself is the authority.  One further example — a Binding Record — is
   shown inline at Section 9 instead of being repeated here.

I.1.  Valid Submit Request with attachments

   Sidecar blobs are named by array index (req-<id>.attachments/<i>); no
   content_ref may appear on a request and the schema rejects one
   (Section 5.1).

   NOTE: '\' line wrapping per RFC 8792

   {
     "contract_version": "2",
     "req_id": "00000010",
     "agent_id": "agent.research",
     "ts": "2026-07-21T04:10:00Z",
     "draft": {
       "to": ["owner@example.com"],
       "subject": "Report attached",
       "body_text": "See the attached report and notes.",
       "attachments": [
         { "filename": "report.pdf", "media_type": "application/pdf",
           "size_bytes": 48213,
           "sha256": "970e441b6c4ecd37abf2541e1276c680bdec8481cef9368ff\
       1d010fd6b784b07" },
         { "filename": "notes.txt", "media_type": "text/plain",
           "size_bytes": 128,
           "sha256": "087d07499f2c097ac5396af7a0f1ef5e7a977fa91c631ebd1\
       5eb61bb23ef20aa" }
       ]
     }
   }

             Figure 20: fixtures/valid/request-attachments.json

I.2.  Valid Deliver Notice with a clean attachment Descriptor

   content_ref's prefix equals the enclosing document's own notice_id
   and its trailing index equals the Descriptor's own array position
   (Section 7.2); a Connector that relays the Notice without ever
   dereferencing the reference is fully conformant.

Rapp & Adams              Expires 24 April 2027               [Page 119]
Internet-Draft                    AMMP                      October 2026

   NOTE: '\' line wrapping per RFC 8792

   {
     "contract_version": "2",
     "notice_id": "1721457600000000003-4242",
     "ts": "2026-07-21T04:06:00Z",
     "kind": "deliver",
     "message": {
       "id": "45",
       "from": "colleague@example.com",
       "subject": "Clean attachment (deferred-release wire shape)",
       "preview": "byte-access forward-valid example",
       "mailbox": "inbox",
       "attachments": [
         { "filename": "photo.jpg", "media_type": "image/jpeg",
           "size_bytes": 204800,
           "sha256": "04c22051bc3f76bc44b952d9fdd3e59214662e7b3fd8a84b8\
       9de09352aeb2264",
           "disposition": "clean",
           "content_ref": "1721457600000000003-4242.attachments/0" }
       ]
     }
   }

         Figure 21: fixtures/valid/notice-attachment-clean-ref.json

I.3.  Valid Inbound Message

   The body spool for a delivered message is keyed on the Runtime-minted
   notice_id, never on the provider message.id (Section 7.3).

   {
     "contract_version": "2",
     "notice_id": "1721457600000000000-4242",
     "id": "42",
     "date": "Mon, 20 Jul 2026 04:00:00 +0000",
     "from": "Alex Doe <owner@example.com>",
     "to": "agent@example.org",
     "subject": "status?",
     "body_text": "hey, how's the run going",
     "attachments": [
       { "filename": "report.pdf", "media_type": "application/pdf",
         "size_bytes": 48213, "disposition": "unscanned" }
     ]
   }

                Figure 22: fixtures/valid/message-clean.json

Rapp & Adams              Expires 24 April 2027               [Page 120]
Internet-Draft                    AMMP                      October 2026

I.4.  Valid Result — a policy hold with per-attachment diagnostics

   attachments[i].index == i on a Result, contiguous and unique over
   0..n-1; per-attachment entries are diagnostic detail under one
   governing top-level outcome and do not license a partial send
   (Section 6.1).

   NOTE: '\' line wrapping per RFC 8792

   {
     "contract_version": "2",
     "req_id": "00000010",
     "outcome": "queued_for_human",
     "detail": "no outbound screener wired; attachments fail-closed to \
       human review",
     "recipients": ["owner@example.com"],
     "message_id": null,
     "job_id": null,
     "attachments": [
       { "index": 0, "outcome": "dlp_held", "detail": "no verdict sourc\
       e" },
       { "index": 1, "outcome": "dlp_held", "detail": "no verdict sourc\
       e" }
     ]
   }

           Figure 23: fixtures/valid/result-attachments-held.json

I.5.  Invalid Deliver Notice — a content_ref that fails the binding

   The drop rule of Section 7.1 is two independent checks: this document
   is schema-valid and is caught only by the content_ref post-check
   (Section 7.2, Section 13.3).

   content_ref names a different notice's Sidecar directory and a non-
   matching index — it is neither <notice_id>.attachments/0 for its own
   array position 0, nor prefixed by this document's own notice_id.
   Schema validation alone reports zero errors; only the content_ref
   index/prefix post-check catches it.

   The conformance gate's own errors for this document:

   $.attachments[0].content_ref: 'OTHER-NOTICE.attachments/7' !=
     'n-index-mismatch.attachments/0'

Rapp & Adams              Expires 24 April 2027               [Page 121]
Internet-Draft                    AMMP                      October 2026

   {
     "contract_version": "2",
     "notice_id": "n-index-mismatch",
     "ts": "2026-07-21T04:06:00Z",
     "kind": "deliver",
     "message": {
       "id": "46",
       "from": "colleague@example.com",
       "subject": "wrong content_ref",
       "preview": "descriptor points at another notice's sidecar",
       "mailbox": "inbox",
       "attachments": [
         { "filename": "photo.jpg", "media_type": "image/jpeg",
           "size_bytes": 204800,
           "disposition": "clean",
           "content_ref": "OTHER-NOTICE.attachments/7" }
       ]
     }
   }

   Figure 24: fixtures/invalid/notice-attachment-ref-index-mismatch.json

I.6.  Valid Deliver Notice carrying an unrecognized member

   The Runtime-authored envelope is OPEN: a consumer MUST ignore a
   member it does not recognize, at every level (Section 13.1).

   NOTE: '\' line wrapping per RFC 8792

   { "contract_version": "2", "notice_id": "x", "ts": "t", "kind": "del\
       iver", "x_injected": "surprise",
     "message": { "id": "1", "from": "a@b", "subject": "s", "preview": \
       "p", "mailbox": "inbox",
                  "x_message_injected": "also surprise" } }

            Figure 25: fixtures/valid/notice-unknown-member.json

I.7.  Invalid Submit Request carrying an unrecognized member

   The Agent-authored envelope stays CLOSED at every level: an
   unrecognized member is a designed, legible rejected refusal, never a
   silent drop (Section 13.1).

   additionalProperties: false at the top level of the submit-request
   schema rejects any member this document does not define.

   The conformance gate's own errors for this document:

Rapp & Adams              Expires 24 April 2027               [Page 122]
Internet-Draft                    AMMP                      October 2026

   $: additional property 'x_injected' not allowed

   NOTE: '\' line wrapping per RFC 8792

   { "contract_version": "2", "req_id": "00000021", "x_injected": "surp\
       rise",
     "draft": { "to": ["a@b"], "subject": "s", "body_text": "b" } }

            Figure 26: fixtures/invalid/request-extra-field.json

I.8.  Invalid Peer Notice — a deliver-kind document under the peer tree

   Origin class comes from the tree a notice arrived in, never from a
   self-declared field: a kind: "deliver" document found under peer/
   notices/ fails the peer-notice schema, the profile's cross-lock
   (Section 12.2).

   kind is const "peer" in the peer-notice schema; a document carrying
   "deliver" fails that const check.

   The conformance gate's own errors for this document:

   $.kind: 'deliver' != const 'peer'

   NOTE: '\' line wrapping per RFC 8792

   { "contract_version": "2", "notice_id": "9f1c2b7e4d3a4f60b8e2c1d5a7f\
       39e04", "ts": "t", "kind": "deliver",
     "message": { "id": "9f1c2b7e4d3a4f60b8e2c1d5a7f39e04", "from": "pl\
       anner@fleet.example", "subject": "s", "preview": "p", "mailbox"\
       : "peer" } }

             Figure 27: fixtures/invalid/peer-kind-deliver.json

Appendix J.  Conformance Suite

   _(Normative for the classes of Section 13.3; the honesty statement
   below is part of the specification.)_

   The suite consists of golden artifacts under fixtures/valid/ and
   fixtures/invalid/, plus a validator, fixtures/validate.py.

   The suite contains *121 fixtures — 42 valid and 79 invalid*.

Rapp & Adams              Expires 24 April 2027               [Page 123]
Internet-Draft                    AMMP                      October 2026

   +======================================+============================+
   | fixture                              | schema                     |
   +======================================+============================+
   | valid/directory-both-lanes.json      | directory.schema.json      |
   +--------------------------------------+----------------------------+
   | valid/directory-delegation-only.json | directory.schema.json      |
   +--------------------------------------+----------------------------+
   | valid/directory-minimal.json         | directory.schema.json      |
   +--------------------------------------+----------------------------+
   | valid/directory-unknown-member.json  | directory.schema.json      |
   +--------------------------------------+----------------------------+
   | valid/identity-basic.json            | binding-                   |
   |                                      | record.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/identity-labeled.json          | binding-                   |
   |                                      | record.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/message-clean.json             | inbound-                   |
   |                                      | message.schema.json        |
   +--------------------------------------+----------------------------+
   | valid/message-unknown-member.json    | inbound-                   |
   |                                      | message.schema.json        |
   +--------------------------------------+----------------------------+
   | valid/notice-attachment-clean-       | deliver-                   |
   | ref.json                             | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/notice-attachments-            | deliver-                   |
   | descriptors.json                     | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/notice-enriched.json           | deliver-                   |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/notice-minimal.json            | deliver-                   |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/notice-provenance-             | deliver-                   |
   | unrecognized-value.json              | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/notice-unknown-member.json     | deliver-                   |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/peer-attachment-clean-ref.json | peer-                      |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/peer-exposure-false.json       | peer-                      |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/peer-exposure-true.json        | peer-                      |

Rapp & Adams              Expires 24 April 2027               [Page 124]
Internet-Draft                    AMMP                      October 2026

   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/peer-minimal.json              | peer-                      |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/peer-result.json               | peer-                      |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/peer-unknown-member.json       | peer-                      |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/request-attachment-empty-      | submit-                    |
   | file.json                            | request.schema.json        |
   +--------------------------------------+----------------------------+
   | valid/request-attachments-empty.json | submit-                    |
   |                                      | request.schema.json        |
   +--------------------------------------+----------------------------+
   | valid/request-attachments.json       | submit-                    |
   |                                      | request.schema.json        |
   +--------------------------------------+----------------------------+
   | valid/request-minimal.json           | submit-                    |
   |                                      | request.schema.json        |
   +--------------------------------------+----------------------------+
   | valid/request-reply.json             | submit-                    |
   |                                      | request.schema.json        |
   +--------------------------------------+----------------------------+
   | valid/result-accepted.json           | result.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/result-attachments-            | result.schema.json         |
   | accepted.json                        |                            |
   +--------------------------------------+----------------------------+
   | valid/result-attachments-held.json   | result.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/result-attachments-rejected-   | result.schema.json         |
   | malformed.json                       |                            |
   +--------------------------------------+----------------------------+
   | valid/result-queued.json             | result.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/result-unknown-member.json     | result.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/roster-basic.json              | roster.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/roster-empty.json              | roster.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/roster-unknown-member.json     | roster.schema.json         |
   +--------------------------------------+----------------------------+
   | valid/control-demoted-body-          | peer-                      |
   | digest.json                          | control.schema.json        |

Rapp & Adams              Expires 24 April 2027               [Page 125]
Internet-Draft                    AMMP                      October 2026

   +--------------------------------------+----------------------------+
   | valid/control-dropped-duplicate.json | peer-                      |
   |                                      | control.schema.json        |
   +--------------------------------------+----------------------------+
   | valid/control-refused-no-edge.json   | peer-                      |
   |                                      | control.schema.json        |
   +--------------------------------------+----------------------------+
   | valid/control-refused-policy.json    | peer-                      |
   |                                      | control.schema.json        |
   +--------------------------------------+----------------------------+
   | valid/statement-exposure-true.json   | peer-                      |
   |                                      | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | valid/statement-minimal.json         | peer-                      |
   |                                      | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | valid/statement-result-              | peer-                      |
   | attachment.json                      | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | valid/statement-sender-since.json    | peer-                      |
   |                                      | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/directory-display-name-      | directory.schema.json      |
   | address.json                         |                            |
   +--------------------------------------+----------------------------+
   | invalid/directory-missing-mail-      | directory.schema.json      |
   | to.json                              |                            |
   +--------------------------------------+----------------------------+
   | invalid/directory-version-1.json     | directory.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/identity-extra-field.json    | binding-                   |
   |                                      | record.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/identity-missing-            | binding-                   |
   | address.json                         | record.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/message-missing-body.json    | inbound-                   |
   |                                      | message.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/notice-attachment-bad-       | deliver-                   |
   | disposition.json                     | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/notice-attachment-content-   | deliver-                   |
   | ref-trailing-newline.json            | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/notice-attachment-missing-   | deliver-                   |
   | disposition.json                     | notice.schema.json         |
   +--------------------------------------+----------------------------+

Rapp & Adams              Expires 24 April 2027               [Page 126]
Internet-Draft                    AMMP                      October 2026

   | invalid/notice-attachment-ref-index- | deliver-                   |
   | mismatch.json                        | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/notice-attachment-ref-not-   | deliver-                   |
   | clean.json                           | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/notice-attachment-sha-not-   | deliver-                   |
   | clean.json                           | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/notice-attachment-traversal- | deliver-                   |
   | ref.json                             | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/notice-kind-peer.json        | deliver-                   |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/notice-missing-preview.json  | deliver-                   |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/notice-notice-id-trailing-   | deliver-                   |
   | newline.json                         | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/notice-traversal-            | deliver-                   |
   | noticeid.json                        | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/notice-version-1.json        | deliver-                   |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/notice-wrong-kind.json       | deliver-                   |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/peer-attachment-ref-index-   | peer-                      |
   | mismatch.json                        | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/peer-exposure-missing-       | peer-                      |
   | asserted-by.json                     | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/peer-exposure-true-missing-  | peer-                      |
   | last-ts.json                         | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/peer-from-crlf.json          | peer-                      |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/peer-from-display-name.json  | peer-                      |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/peer-in-reply-to-            | peer-                      |
   | encoded.json                         | notice.schema.json         |
   +--------------------------------------+----------------------------+

Rapp & Adams              Expires 24 April 2027               [Page 127]
Internet-Draft                    AMMP                      October 2026

   | invalid/peer-kind-deliver.json       | peer-                      |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/peer-mailbox-inbox.json      | peer-                      |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/peer-message-id-encoded.json | peer-                      |
   |                                      | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/peer-notice-id-trailing-     | peer-                      |
   | newline.json                         | notice.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/request-attachment-bad-      | submit-                    |
   | sha.json                             | request.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/request-attachment-content-  | submit-                    |
   | ref.json                             | request.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/request-attachment-filename- | submit-                    |
   | crlf.json                            | request.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/request-attachment-media-    | submit-                    |
   | type-trailing-newline.json           | request.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/request-attachment-missing-  | submit-                    |
   | sha.json                             | request.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/request-attachment-negative- | submit-                    |
   | size.json                            | request.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/request-empty-to.json        | submit-                    |
   |                                      | request.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/request-extra-field.json     | submit-                    |
   |                                      | request.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/request-recipient-display-   | submit-                    |
   | name.json                            | request.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/request-recipient-trailing-  | submit-                    |
   | newline.json                         | request.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/request-reply-id-crlf.json   | submit-                    |
   |                                      | request.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/request-reqid-trailing-      | submit-                    |
   | newline.json                         | request.schema.json        |
   +--------------------------------------+----------------------------+

Rapp & Adams              Expires 24 April 2027               [Page 128]
Internet-Draft                    AMMP                      October 2026

   | invalid/request-subject-crlf.json    | submit-                    |
   |                                      | request.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/request-subject-trailing-    | submit-                    |
   | newline.json                         | request.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/request-traversal-reqid.json | submit-                    |
   |                                      | request.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/request-version-absent.json  | submit-                    |
   |                                      | request.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/result-attachment-bad-       | result.schema.json         |
   | outcome.json                         |                            |
   +--------------------------------------+----------------------------+
   | invalid/result-attachments-dup-      | result.schema.json         |
   | index.json                           |                            |
   +--------------------------------------+----------------------------+
   | invalid/result-attachments-gap-      | result.schema.json         |
   | index.json                           |                            |
   +--------------------------------------+----------------------------+
   | invalid/result-attestation-missing-  | result.schema.json         |
   | form.json                            |                            |
   +--------------------------------------+----------------------------+
   | invalid/result-bad-attestation-      | result.schema.json         |
   | form.json                            |                            |
   +--------------------------------------+----------------------------+
   | invalid/result-bad-outcome.json      | result.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/roster-display-name-         | roster.schema.json         |
   | address.json                         |                            |
   +--------------------------------------+----------------------------+
   | invalid/roster-member-missing-       | roster.schema.json         |
   | state.json                           |                            |
   +--------------------------------------+----------------------------+
   | invalid/roster-missing-members.json  | roster.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/roster-version-1.json        | roster.schema.json         |
   +--------------------------------------+----------------------------+
   | invalid/control-free-text.json       | peer-                      |
   |                                      | control.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/control-kind-peer.json       | peer-                      |
   |                                      | control.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/control-outcome-cause-       | peer-                      |
   | mismatch.json                        | control.schema.json        |
   +--------------------------------------+----------------------------+

Rapp & Adams              Expires 24 April 2027               [Page 129]
Internet-Draft                    AMMP                      October 2026

   | invalid/control-policy-code-without- | peer-                      |
   | policy.json                          | control.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/control-policy-without-      | peer-                      |
   | code.json                            | control.schema.json        |
   +--------------------------------------+----------------------------+
   | invalid/statement-attachment-        | peer-                      |
   | unknown-member.json                  | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-digest-            | peer-                      |
   | uppercase.json                       | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-exposure-date-     | peer-                      |
   | free-text.json                       | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-exposure-unknown-  | peer-                      |
   | member.json                          | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-from-display-      | peer-                      |
   | name.json                            | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-id-dotted.json     | peer-                      |
   |                                      | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-id-leading-        | peer-                      |
   | dash.json                            | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-in-reply-to-       | peer-                      |
   | leading-dash.json                    | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-id-encoded.json    | peer-                      |
   |                                      | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-key-id-empty.json  | peer-                      |
   |                                      | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-missing-           | peer-                      |
   | attachments.json                     | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-sender-since-date- | peer-                      |
   | only.json                            | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-subject-crlf.json  | peer-                      |
   |                                      | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-to-array.json      | peer-                      |
   |                                      | statement.schema.json      |
   +--------------------------------------+----------------------------+

Rapp & Adams              Expires 24 April 2027               [Page 130]
Internet-Draft                    AMMP                      October 2026

   | invalid/statement-ts-fractional.json | peer-                      |
   |                                      | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-ts-offset.json     | peer-                      |
   |                                      | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-unknown-           | peer-                      |
   | member.json                          | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-v-boolean.json     | peer-                      |
   |                                      | statement.schema.json      |
   +--------------------------------------+----------------------------+
   | invalid/statement-v2.json            | peer-                      |
   |                                      | statement.schema.json      |
   +--------------------------------------+----------------------------+

                                  Table 7

   *Running it.* The validator is stdlib-only and takes no arguments:

   python3 fixtures/validate.py

   It exits 0 if and only if every fixture under valid/ passes and every
   fixture under invalid/ fails.  It requires no counterpart, no
   network, and no dependency: *the fixtures are the other side.*

   *Signed cases for the peer-origin profile.* The validator then runs
   fixtures/peer-signed/check_cases.py, which checks three further sets,
   all of which a Runtime + peer-origin implementation that verifies
   peer traffic from other hosts MUST pass (Section 13.3):

   *  rfc8032-vectors.json: the test vectors of Section 7.1 of [RFC8032]
      (TEST 1 to 3), and negative vectors for the verification policy of
      Section 12.6.7, each marked for rejection either at verification
      or at configuration, which are two separate gates;

   *  mime/: 10 raw messages, and the candidate the selection rule of
      Section 12.6.2 makes of each, reduced by a byte-exact splitter;

   *  cases/: 114 sequences of candidates and events (a crash at each
      point of Section 12.6.5, recovery, loss of the store with and
      without H, an operator bound, clock and window changes, and edge
      and key changes), each step with the outcome, cause, published
      members and Control Notice that the receiver configured in
      receiver.json or receiver-host-a.json MUST reach.  Single-step
      cases break exactly one rule; multi- cases pin the precedence of
      Section 12.6.3; seq- cases pin state across steps; config- cases
      are configurations a receiver MUST refuse to load.  The receiver

Rapp & Adams              Expires 24 April 2027               [Page 131]
Internet-Draft                    AMMP                      October 2026

      files set retention to its minimum, P, and the cases that probe
      the retention boundary assume it; a receiver that retains records
      longer, as Section 12.6.5 permits, maps those steps by its own
      retention.

   check_cases.py models only the state that Section 12.6.5 makes
   normative.  It is a self-check of the fixtures, not a Runtime, and
   its Ed25519 implementation, which is slow and not constant-time,
   exists for the gate alone.

   *How a fixture is mapped to a schema.* By filename prefix — request-
   to the submit-request schema, notice- to deliver-notice, result- to
   result, message- to inbound-message, identity- to binding-record, and
   peer- to peer-notice, statement- to peer-statement, control- to peer-
   control (Appendix F), directory- to peer-directory (Appendix G) and
   roster- to roster (Appendix H).

   *What the validator checks.* Schema validation in the JSON Schema
   2020-12 dialect [JSON-SCHEMA-2020-12], over the subset of keywords
   these schemas use (type, const, enum, minLength, pattern, minItems,
   minimum, items, required, properties, additionalProperties, allOf,
   and a single if/then pair) — plus two *named post-checks*, neither
   expressible in standard JSON Schema:

   *  the Result-side binding attachments[i].index == i, contiguous and
      unique over 0 through n-1 (Section 6.1);

   *  the content_ref index and prefix binding on Notice, Inbound
      Message, and peer-notice documents: every Descriptor carrying
      content_ref must equal <notice_id>.attachments/<i> for its own
      array position i, binding both the trailing index and the leading
      prefix (Section 7.2).

   A Connector conformance claim requires running *both* the schema
   validation and the second post-check, because exactly one invalid
   notice-* fixture — and its peer-* counterpart — is schema-valid on
   its own and is caught only by that post-check (Appendix I,
   Section 13.3).

Rapp & Adams              Expires 24 April 2027               [Page 132]
Internet-Draft                    AMMP                      October 2026

   *A note on pattern anchoring, and a portability cost.* Every schema
   pattern in this suite that ends in $ is written as (?!\n)$. The
   reason is that a bare $ matches before a single trailing newline in
   several widely used regex engines — Python's re, Java, and .NET among
   them — unlike the ECMA-262 dialect JSON Schema specifies.  Since
   every one of these patterns exists to forbid exactly the control
   characters a permissive $ would let through at the end of a string,
   the lookahead closes that gap in the schemas themselves rather than
   in any one validator.  Seven fixtures pin the trailing-newline case
   specifically, distinct from the CRLF fixtures, which embed \r\n and
   were already rejected even under a permissive engine.

   The cost is disclosed rather than discovered: *lookahead-less engines
   cannot compile these patterns at all.* RE2 (Go's regexp), Rust's
   regex crate, and POSIX ERE reject (?! outright, so the eight schemas
   carrying the guard (every schema except result and binding-record)
   fail to load rather than validating loosely.  Those engines already
   treat $ as strict end-of-string and never had the gap, so an
   implementer on one of them should strip the lookahead — not add a
   check outside the pattern, which would be both unnecessary and not
   what breaks.  The failure is loud (a compile error, never silent
   acceptance).

   *What a green run proves.* That every document shape this protocol
   defines is accepted or rejected as specified, by an implementation's
   own parser, with no counterpart present.

   *What a green run does NOT prove.* The following are filesystem and
   policy invariants that no document validator can observe.  They are
   obligations of the conformance classes regardless (Section 13.3), and
   are checked operationally:

   *  Sidecar file existence — an extra file, a missing file, or a
      directory present when the Descriptor array is empty or absent.

   *  size_bytes and sha256 actually matching the bytes on disk.

   *  filename display-sanitization, and the "never a path" invariant.

   *  The binding between an ordinal and a directory entry on disk.  The
      schema checks only the _Descriptor_ array's own index member, not
      that directory entry N exists.

   *  Inbound Sidecar directories keyed on the Runtime-minted notice
      identifier rather than the provider message.id.

   *  The location of Runtime-private Staging, outside the mounted
      Volume.

Rapp & Adams              Expires 24 April 2027               [Page 133]
Internet-Draft                    AMMP                      October 2026

   *  Write ordering and commit-sentinel atomicity — Sidecars before
      req-<id>.json; .tmp plus rename.

   *  Caps and allowlists — type allowlists, size caps, Volume quotas.

   *  The absence of a spooled body for a quarantined or blocked message
      (enforcement by absence, Section 7.3).

   *  A Connector's tolerance of a read-only inbound/ tree.  The
      prescribed check is operational, not a fixture: run the Connector
      against an inbound tree it cannot write and confirm that it
      starts, relays every pending Notice, resolves bodies, and submits
      outbound requests, without requiring any inbound write to succeed
      (Section 13.3).

   *  Ingest by copy — validation, policy evaluation, and composition
      running over a Runtime-private snapshot taken before any of those
      steps, rather than re-reading an Agent-writable path
      (Section 4.1).

   *  Write-side path discipline, all three conjunctive legs: full
      parent-chain resolution through pinned directory descriptors from
      the Namespace root; each directory component opened
      O_NOFOLLOW|O_DIRECTORY at the moment it is added to that chain;
      and the final component created O_EXCL and never followed, failing
      closed on EEXIST and ELOOP.  Any one leg alone is a known-
      insufficient partial mitigation (Section 4.1).

   *  Read-side open discipline — per-component no-symlink-follow opens,
      non-blocking opens, fstat-based regular-file verification through
      the open descriptor, and bounded reads and caps on every Agent-
      influenced path (Section 4.1).

   *  Single writer and single drainer per Namespace — an operational
      and deployment invariant, such as an OS-level lock held for the
      process lifetime (Section 4.1).

   *  Compose-time header-boundary stripping: even where the schemas'
      control-character patterns are bypassed upstream, the Runtime
      rejects or strips CR, LF, and NUL before composing the outbound
      message (Section 5.1).

   *  The peer-origin profile's operational obligations (Section 12),
      none of them visible to a document validator: the write-authority
      partition — no component that admits messages from a transport not
      restricted to authorised peer senders holds write authority over
      peer/, and the prescribed check is a write attempt from inside the
      admitting component that must fail; intake isolation for shared-

Rapp & Adams              Expires 24 April 2027               [Page 134]
Internet-Draft                    AMMP                      October 2026

      transport peer candidates, in a location that is neither inbound/
      nor peer/ and unreachable from any Agent Namespace; verification
      placement in a component holding peer write authority that hashes
      body and attachment bytes itself; sender_exposure never sender-
      settable; a Peer Notice carrying in_reply_to only when the placing
      Runtime resolved it from its own record, an unresolvable key being
      refused rather than stripped; and at-most-once action by the
      Connector, with an ambiguous target held rather than fanned out.
      Also invisible to the gate: the durability of the replay store, of
      H, of each record's disposition and of pending Control Notices
      across restarts; the closing of admission when H is lost; and the
      protection of the signing key (Section 12.6).

   Beyond these, the behavioral obligations of Sections 7 and 8 — the
   untrusted-content posture, the treatment of task_id as correlation
   rather than authorization, and the requirement that verified sender
   identity never be taken from a mail message.from — sit outside the
   fixture gate entirely.  They constrain what an implementation _does_
   with a valid document, which no document-level gate can see.

Appendix K.  A Deferred Seam-Visible "Handled" Signal (Non-Normative)

   *This appendix is non-normative and is not in force at this document
   version.* It records the reasoning behind a gap deliberately left
   open, not an addition to the protocol.  Nothing below is a directory,
   a member, or an artifact that a conforming implementation can rely
   on, and no requirement keyword appears anywhere in it.

   *The gap.* Making inbound consumption-tracking Connector-private
   (Section 4.1) closes the writability conflict but leaves a knowledge
   and capability split unbridged: only the Connector knows that a
   Notice has been relayed to its Agent, while on a read-only inbound
   tree only the Runtime can delete anything.  Neither side alone can
   turn "consumed" into "reclaimed."

   *A sketch, not a specification.* One shape that would bridge it is a
   Connector-written, zero-byte marker per acknowledged Notice, written
   into outbound/, where the Connector already has write access in every
   posture:

   outbound/acks/notice-<id>   # sketch only - not part of the protocol

             Figure 28: Sketch only — not part of this protocol

   A Runtime could then delete the acknowledged Notice and its spooled
   message, and finally the marker itself.  This is presented purely as
   a sketch: no directory and no wire member is introduced by this
   appendix.

Rapp & Adams              Expires 24 April 2027               [Page 135]
Internet-Draft                    AMMP                      October 2026

   *Why a Connector-forgeable signal would be acceptable here.* A
   garbage-collection signal is a resource decision rather than a
   security decision.  An Agent that falsely claims consumption loses
   only its own unread mail; one that never claims consumption only
   bloats its own inbox.  Neither crosses a trust boundary that matters,
   and neither touches any record of the exchange the Runtime keeps on
   its own side.  That is what distinguishes this case from every other
   "the Agent writes it, so it cannot be trusted" case in this document
   — but it is a distinction a future version would need to state
   explicitly rather than assume.

   *Why a high-water mark cannot substitute.* Section 8 requires task_id
   to be unguessable and never sequential, and nothing in this document
   orders notice identifiers, so no single "processed through N" cursor
   is expressible.  Any consumption signal has to be a _set_ of
   identifiers, or time-ordered — and time-ordering would require the
   consumer to process strictly in delivery order, which nothing today
   requires.

   *The lifecycle this would enable, and its boundaries.* A reliable "is
   this one done?" answer is the watermark a real retention lifecycle
   promotes against: cycling a working set, rolling handled traffic into
   period-compacted archives, then expiring or tiering to cold storage.
   Three boundaries would need to hold if this is ever specified.

   *  *The seam is not the record.* A Runtime that needs an exchange of
      record already keeps one outside this protocol, tamper-evident.
      Retention here is about an Agent's access to its own history, not
      about the durability of evidence; conflating the two produces an
      archive that satisfies neither.

   *  *An Agent-writable archive is never evidence.* A compacted archive
      living under outbound/, which is Agent-writable in every posture,
      can be rewritten by the Agent it belongs to.  Anything meant to be
      relied on has to be Runtime-written or kept outside this protocol
      entirely.

   *  *It must not grow the read surface.* The read available today is a
      point lookup from a notice identifier to a body.  "Search my
      archive" is a materially larger capability — decompression,
      indexing, query — for a deliberately dumb, credential-free relay
      to take on.  If archives are ever exposed to an Agent, that likely
      belongs behind a Runtime-served surface rather than the file-drop
      seam.

   *Why this is deferred rather than specified now.* A capability starts
   with a change to this document once a second implementation
   independently wants it, with members and fixtures proposed together.

Rapp & Adams              Expires 24 April 2027               [Page 136]
Internet-Draft                    AMMP                      October 2026

   Today exactly one reference Connector carries a private workaround
   for the underlying problem — a writability-probing fallback with
   three modes: move, ledger, and in-memory.  There is no second data
   point yet to generalize from, and specifying now would freeze one
   side's convention as the protocol's answer.

   *One honesty note for a future implementer.* The sketch's own
   garbage-collection step is not atomic: if a Runtime crashes between
   deleting the Notice artifacts and deleting the acknowledgment marker,
   the marker is orphaned, pointing at a Notice that no longer exists.
   Any real design needs idempotent collection, in which a missing
   target is not an error — recorded here so that it is not rediscovered
   from scratch.

Appendix L.  Historical Background

   This section is to be removed before publishing as an RFC.

   _(Informative.)_

   AMMP was specified and implemented before it was brought to the IETF.
   Its contract was versioned on its own scale, from 1.0.0 to 3.1.0,
   separately from the wire version that documents carry.  This appendix
   records that history, newest first, because implementations were
   built against each version, and because the reasons for several rules
   in this document are clearest as the story of what went wrong without
   them.  An entry that turned out to be wrong is left standing, with a
   correction attached, rather than quietly rewritten.

L.1.  draft-rapp-ammp-00 — brought to the IETF (contract 3.1.0)

   *  *One document.* The peer-origin profile's prose, previously a
      companion document in the repository, is now Section 12.  The
      contract's own version numbers no longer appear in the protocol
      sections: revisions of this document are draft revisions, and the
      wire version is contract_version (Section 13.1), which stays "2".
      The reasoning that explained earlier version changes moved into
      this appendix (Appendix L.2).

Rapp & Adams              Expires 24 April 2027               [Page 137]
Internet-Draft                    AMMP                      October 2026

   *  *The protocol is AMMP.* It was AMAP, the Agent Mailbox Access
      Protocol, and is renamed the Agent Mediated Messaging Protocol
      before its first submission: "mailbox access" named only its first
      channel, and the contract is a mediation seam for an Agent's
      correspondence, mail and peer traffic alike.  The abstract and the
      definitions of Agent and Runtime describe it that way, as a store-
      and-forward messaging runtime; email remains the only transport
      this document defines.  The acronym AMAP was also already in use
      by an expired 2003 individual draft.  Nothing on the wire changes:
      no document carries the protocol's name, and the header fields of
      Section 15.1 keep their names.

   *  *Editorial review for submission.* Terminology was alphabetized
      and Drop and Fail Closed became defined terms; requirements
      previously phrased as "never" are, where they are requirements,
      stated with [RFC2119] keywords; references to earlier revisions
      were removed from the protocol sections and the security
      considerations; the open questions the authors know of are listed
      in Section 1.6; and IANA considerations were added for the two
      header fields the profile defines.

   *  *The signed statement between hosts*, after reports from the first
      implementations of revision s5-r4, which now stands as the
      profile's text, without a revision label (an implementation names
      the draft revision it implements): the id grammar of the Peer
      Statement and Control Notice schemas is narrowed to ^[A-Za-
      z0-9_][A-Za-z0-9_-]*$, the notice_id character set without "."
      (Section 12.6.1; see the correction to the 3.1.0 entry below); a
      point is decoded canonically before the small-order check
      (Section 12.6.7), and the gate's reference Ed25519 decoder was
      corrected to match, with two "negative zero" vectors added; the
      12288-octet limit is stated to apply to a field body as
      transmitted (Section 12.6.2); Fmax after the loss of H is taken
      from the retained history of F if provable, and is 900 otherwise
      (Section 12.6.5); and how a Runtime divides its intake is stated
      to be an implementation choice (Section 12.6.10).  Fixtures: two
      invalid statement fixtures (121 in all: 42 valid, 79 invalid);
      signed cases for a leading "-" and for the loss of the F history
      (114); and a MIME case for the size limit (10).

L.2.  How the versioning rules came about

   *The move from wire major "1" to "2".* When attachment support was
   added, every top-level schema and every nested object except verdict
   and attestation.claims was additionalProperties: false.  Under that
   regime, adding a member was not a safe additive change: a strict
   major-1 validator rejected the *whole document*, not just the new
   member.  Inbound, that was *silent total message loss* — an

Rapp & Adams              Expires 24 April 2027               [Page 138]
Internet-Draft                    AMMP                      October 2026

   attachment-bearing email vanished instead of degrading.  Outbound, it
   was an accidental schema error rather than a designed refusal.  And
   there was no signal to gate on while contract_version stayed "1".
   That is why attachments required a major change: a major-1 consumer
   receiving a "2" document failed closed _by version_, a legible and
   designed refusal, and both sides upgraded deliberately.

   *Two version axes.* From contract 3.0.0 the contract's own version
   and the wire major were allowed to diverge.  The wire major tracks
   whole-document acceptance compatibility; the contract version tracked
   the fuller set of obligations, including narrower, disclosed
   producer-side tightenings.  Contract 3.0.0 was a major contract
   change — it moved fixtures between the valid and invalid sets, and
   added Runtime obligations that a strictly compliant earlier
   implementation did not meet — while the wire major stayed "2".
   Neither of its two producer-side narrowings was the failure a wire
   major exists to prevent: the Submit Request tightening yields a per-
   request rejected Result, and the notice_id character-set tightening
   yields a per-Notice drop under check 1 of Section 7.1, a real cost,
   disclosed, but narrow and legible.  Changing the wire major to "3"
   would instead have forced every major-2 counterpart on both sides to
   refuse all traffic, including all the traffic the new tolerance rule
   protects.  Contract 3.1.0 was an additive minor change over 3.0.0,
   and the wire major stayed "2".

   *Corrections.* Two claims that shipped in earlier revisions of the
   contract's normative text were false, and were named rather than
   quietly replaced, because an implementer who read them may have built
   against them.

   1.  *The write-rule "strengthening" that was a regression.* An
       earlier revision described the exclusive-create and no-symlink-
       follow requirement on the final path component as having been
       "strengthened to" the pinned directory-descriptor walk — that is,
       replaced by it — and deleted the earlier clause outright.  That
       was a *weakening*: a parent chain pinned by descriptor is silent
       about the final .tmp filename itself, so dropping exclusive-
       create there let an Agent that pre-plants a symlink at a
       predictable .tmp name have the Runtime write through it.
       Section 4.1 states the rule conjunctively, with all three legs
       required together.

   2.  *The drop rule described as a closure over schema validation.* An
       earlier revision described the inbound drop rule as a pure
       closure over schema validation, and claimed that formulation was
       exact against the fixture set.  Both statements were false: the
       content_ref index and prefix binding is not expressible in the
       schema dialect used here, and a fixture exists that is schema-

Rapp & Adams              Expires 24 April 2027               [Page 139]
Internet-Draft                    AMMP                      October 2026

       valid on its own and must nonetheless be dropped.  Section 7.1
       states two independent checks, and so does the Connector
       conformance class.

   A third correction concerns the change history itself: the contract
   version that introduced message.provenance was labelled an additive
   minor change, which was unsound against the strictly validating
   consumers of the time.  The 2.1.0 entry below records what actually
   happened.

   *Lessons from implementation.* Several rules in this document exist
   because an early implementation got them wrong: it trusted a req_id
   member inside a request body to name the path its Result was written
   to; it used the provider's message identifier as a path component; it
   minted guessable, sequential task_id values; and its schema coverage
   of control characters stopped at subject and filename, missing the
   two members composed directly into headers.  Each was a confirmed
   exploit path, and each is now a requirement (Section 4.1,
   Section 5.1, Section 8).

   *The peer-origin profile's presentation rule.* A prior revision of
   the profile said only that a Connector "MAY present peer bodies under
   the same untrusted-content framing it uses for mail".  That was a
   permission with no floor: it allowed the mail framing without saying
   what any framing had to preserve.  A Connector following it chose the
   cautious option, which on this lane is the wrong one, in the specific
   direction of telling an Agent not to act on work it was authorized to
   do.  Section 12.8 states what a presentation must and must not
   convey.

L.3.  3.1.0 — the protocol is AMAP; the outbound extension space; the
      peer-origin profile

   An additive *minor* bump; the wire major stays "2".  The entries
   below are as recorded at the time: the companion document they cite
   is now Section 12, and the items marked proposed were adopted in
   draft-rapp-ammp-00 (Appendix L.1).

   *  *The signed cross-host peer statement and control notice*
      (2026-10-09, proposed, not yet reviewed; companion-document
      section 5 revision s5-r4).  Two new schemas (Appendix F): the
      statement a sending Runtime signs with Ed25519 and carries in one
      AMAP-Peer-Statement header field, and the control notice a
      receiving Runtime returns about a statement it did not place, both
      CLOSED at every level; statement-* and control-* fixtures; and, in
      fixtures/peer-signed/, RFC 8032 vectors with negative cases, MIME
      selection cases, and signed sequences of candidates and events
      with the outcome a receiver MUST reach at each step.  The rules

Rapp & Adams              Expires 24 April 2027               [Page 140]
Internet-Draft                    AMMP                      October 2026

      are the companion document's (spec/peer-origin.md, section 5).
      Additive: nothing existing changes shape, and a deployment without
      cross-host peer traffic is unaffected.  The 30 new fixtures (8
      valid, 22 invalid) bring version 3.1.0 to 119 (42 valid, 77
      invalid).

      *Correction (draft-rapp-ammp-00).* The statement schema accepted
      an id or in_reply_to beginning with "-", which the notice_id
      character set forbids, so a receiver could verify a statement it
      could not then publish; one signed case minted such an id.  The
      grammar is narrowed (Appendix L.1), and the case was minted again.

   *  *The fleet roster* (2026-09-24).  A new, optional artifact
      (Section 11, schema in Appendix H): one Runtime-written file
      naming the instances the Runtime has admitted, so that an Agent on
      a deployment whose addresses carry an underivable suffix can learn
      which peers exist.  It answers existence, never authorisation, and
      states that separately from the Peer Directory.  Its readability
      obligation is split between the Runtime, which publishes only into
      a designated location and never creates it, and the deployment,
      which exposes that location.  Additive: a new schema and seven
      fixtures, and nothing existing changes shape.

   *  *The protocol is AMAP; the schema $id authority is amap-spec*
      (2026-09-15).  The protocol formerly called AMP ("Agent Mailbox
      Protocol") is renamed AMAP ("Agent Mailbox Access Protocol")
      because AMP collides with the IETF DTN working group's adopted
      draft-ietf-dtn-amp; the specification repository moves from agent-
      mailbox-protocol to amap-spec with it.  The six schema $id values
      move from https://agent-mailbox-protocol/schemas/… to
      https://amap-spec/schemas/…. Nothing on the wire changes shape: no
      document carries the protocol's name as a field, and $id is an
      identifier the gate never dereferences.  Implementations that
      mirror the Runtime's address spell its local part amap.router from
      this version.  This draft's filename is a separate decision and is
      unchanged here.  This version is marked DRAFT: its profile prose
      is a companion document (spec/peer-origin.md, itself DRAFT), and
      its schema and fixtures may still change before it is settled.

   *  *Outbound extension space.* outbound/ext/<name>/ is defined as a
      Connector-owned side channel a Runtime MAY read by out-of-band
      agreement, with no shape specified and nothing security-relevant
      permitted to depend on it (Section 4.1). <name> is pinned as a
      stable Connector id — chosen once, unchanged by any rename of the
      Connector's repository, package, or deployment, and opaque to the
      Runtime that reads it; the wording it replaced ("a Connector or
      deployment name") permitted a repository name to leak into a wire-
      visible path.  The reference Claude Code Connector chose claude-

Rapp & Adams              Expires 24 April 2027               [Page 141]
Internet-Draft                    AMMP                      October 2026

      code as its id; ids are chosen by each Connector, and this
      document neither lists nor registers them.  One normative
      clarification sits beside it: an outcome written there is a claim
      by the receiving side, never proof of delivery or of non-delivery,
      and the Runtime's own delivery record is authoritative over it
      (Section 14.15).  There is deliberately no inbound extension
      space.

   *  *Peer-notice schema.* The peer-origin profile's artifact is pinned
      by peer-notice.schema.json (Appendix F): the deliver-notice layout
      with kind and mailbox fixed to peer, message.from a bare addr-
      spec, message identifiers in the notice-identifier character set,
      and the optional Runtime-asserted sender_exposure stamp with its
      required members.  The profile's prose is the companion document
      spec/peer-origin.md (v3.1.0 DRAFT).

   *  *The Peer Directory.* A Runtime may publish a per-instance
      projection of the authorisation graph, directory.json, at the
      Agent's Namespace root (Section 10, schema in Appendix G), so that
      a Connector can learn which peers it may address without the host
      rendering a snapshot onto an Agent-writable mount that goes stale
      whenever the fleet changes.  It is advisory and never
      authoritative, and a Runtime never reads it back as authorisation,
      which is what stops it decaying into an allowlist the Agent can
      see.  The Namespace root gains a Runtime-owned posture for
      Runtime-written files: the file sits beside the lanes rather than
      inside one, because it spans both.  Additive: a new optional
      artifact, a new schema and seven fixtures (4 valid, 3 invalid); no
      existing schema or fixture changes, and the wire contract_version
      stays "2".

      _Restored 2026-10-05._ This entry was in the versioning record
      this document was reconciled against and was lost in the
      reconciliation, along with its fixtures from the count below.

Rapp & Adams              Expires 24 April 2027               [Page 142]
Internet-Draft                    AMMP                      October 2026

   *  *Clarifications from implementation* (proposed, 2026-10-07).
      Questions from Runtime implementers reconciling against this
      document, each answered from the existing text and then stated: a
      Runtime knows the <agent-id> of every Namespace it serves and
      rejects a request from a Namespace it cannot name (Section 4.1);
      outbound/ext/<name>/'s id is a lowercase single path segment
      (Section 4.1); a Runtime may be built from several trusted
      components, and its obligations bind the composite (Section 3.5,
      informative); the Deletion rights table gains rows for outbound/
      ext/<name>/ and directory.json (Section 4.2); message.id is the
      mail store's identifier, not the Message-ID (Section 7); and a
      Runtime does not hand mail for special-use or private-use domain
      names to a mail system outside the deployment (Section 14.16).
      The companion peer-origin profile clarifies what a routable peer
      domain is.  No schema or fixture changes.

   *  *Fixtures: 75 total (27 valid, 48 invalid), up from 58.* 16 peer-*
      fixtures were added (6 valid, 10 invalid), covering a minimal
      notice, a reply, the exposure stamp asserted both ways, a clean
      attachment reference, an unknown-member case, and rejections for a
      deliver kind under the peer tree, a peer kind under the inbound
      tree, a display-name or CRLF from, encoded message identifiers, a
      missing asserted_by, a missing last-delivery timestamp when
      exposure is asserted true, an inbox mailbox, a content_ref index
      mismatch, and a trailing-newline identifier.  The validator maps
      the peer- prefix to the new schema and applies the content_ref
      post-check to it.  Three existing fixtures had example strings
      normalised to example.* identities (one free-text body, two
      addresses in injection examples); each stays valid or invalid for
      the same reason as before, and no schema changed.

      *Correction (2026-10-05).* The count above omits the seven
      directory-* fixtures (4 valid, 3 invalid) added with the Peer
      Directory.  With them and the fleet roster's seven, version 3.1.0
      has 89 fixtures (34 valid, 55 invalid).

      *Correction (draft-rapp-ammp-00).* The breakdown above does not
      sum: 58 + 16 is 74, and 37 + 10 is 47.  The record does not
      identify the seventeenth addition.  The totals from 89 onward are
      the ones verified against the suite as it ships (Appendix J).

L.4.  3.0.0 — direction-aware tolerance, and six closed safety holes

   A *major document bump*; the wire major stays "2".  Appendix L.2
   states why the two numbers diverge here, and why bumping the wire
   would have caused the exact harm this version exists to repair.

Rapp & Adams              Expires 24 April 2027               [Page 143]
Internet-Draft                    AMMP                      October 2026

   *  *Unknown-member tolerance, direction-aware.* The three Runtime-
      authored artifacts — deliver-notice, inbound-message, and result —
      became *open* at every level: additionalProperties: false was
      removed from the top level, from message, from Descriptor items,
      and from attestation, and a consumer must tolerate an unrecognized
      member.  Emitting one remains producer non-conformance.  The
      Agent-authored submit-request envelope stays *closed* at every
      level.  Consequence: adding an optional member to a Runtime-
      authored artifact is now genuinely additive; adding any member to
      the submit-request is a major change.

   *  *...and for values.* The enum constraint was removed from
      message.sender_standing and message.provenance, the two explicitly
      advisory vocabularies, and a consumer must tolerate an
      unrecognized value in either. kind, outcome (both levels), and
      disposition remain closed, enum-locked vocabularies.

   *  *Ingest by copy.* A Runtime must copy an Agent-authored request
      and its Sidecars into private staging before validating, must act
      only on that copy, and must not re-read an Agent-writable path
      after a decision depending on its content (Section 4.1).

   *  *Address syntax and header-injection discipline.* A new obligation
      in Section 5.1 plus schema patterns on draft.to/cc, draft.subject,
      attachment filename and media_type, draft.reply_to_message_id,
      in_reply_to, agent_id, and ts — including the two members composed
      directly into headers, which earlier schema coverage missed.

   *  *Path discipline, write- and read-side both.* The write-side rule
      became *conjunctive* (Section 4.1), and a new read-side rule
      applies the equivalent discipline to every Agent-influenceable
      read.  A notice_id character-set pattern was added to the deliver-
      notice and inbound-message schemas so that every legal notice_id
      is also a legal content_ref prefix. *Disclosed cost:* notice_id
      was previously unconstrained, so a Runtime minting identifiers
      outside the new character set — an ISO-8601 timestamp, standard
      base64, a URN, or anything else carrying punctuation outside it —
      now emits Notices and Inbound Messages that fail schema validation
      on a _pattern violation on a required member_, and a conforming
      Connector drops them. *Migration:* such a Runtime must re-mint
      identifiers under the new character set (unpadded base64url or
      lowercase hex, for instance) before any Connector it talks to
      upgrades, because the failure is otherwise silent on both sides.

   *  *Single writer, single drainer.* Section 4.1 now names the
      concurrency assumption that every atomicity, commit-sentinel, and
      req_id rule already depended on.

Rapp & Adams              Expires 24 April 2027               [Page 144]
Internet-Draft                    AMMP                      October 2026

   *  *message.from named untrusted.* The untrusted-content sentence now
      covers explicitly the member most likely to be rendered to a human
      or used for a trust judgement (Section 7.1).

   *  *Document authority.* The contract, its schemas, and its fixtures
      were made normative; the rendering then generated from them was
      non-authoritative where they disagreed.  (This document is now the
      prose of record.)  The conformance-class text was moved into the
      contract itself so that the demotion deletes nothing, and a stray
      claim of a second, message-specific Sidecar directory was
      corrected to match the layout tree.

   *  *Corrections shipped with this version*, rather than a tidied
      history: (i) the write rule described in an earlier revision as
      "strengthened" had in fact been weakened — the exclusive-create
      requirement on the final component had been deleted rather than
      supplemented (Appendix L.2); (ii) the inbound drop rule described
      as a pure closure over schema validation was refuted by this
      version's own content_ref-binding fixture, and is now stated as
      two independent checks (Section 7.1, Section 13.3); (iii) the
      claim that this version's producer bytes are unchanged is
      explicitly _not_ made — two producer-side narrowings are disclosed
      above, each travelling a legible per-artifact refusal path; and
      (iv) the 2.1.0 entry below is corrected in place.

   *  *Fixtures: 58 total (21 valid, 37 invalid), up from 44.* The
      unknown-member notice fixture moved from the invalid set to the
      valid set and was extended with an unrecognized member inside
      message; valid unknown-member fixtures were added for result and
      inbound-message; the withheld-body invalid fixture was deleted,
      having become schema-valid under the relaxed envelope (its name
      always over-claimed what additionalProperties alone tested); the
      bad-provenance fixture moved to the valid set as an unrecognized-
      value case; an invalid extra-member submit-request fixture was
      added to pin the closed side of the asymmetry, previously untested
      at the top level; four invalid fixtures were added for the
      address-syntax and control-character rules; a traversal notice_id
      fixture was added; the content_ref index-mismatch fixture was
      added, and is the one invalid notice-* case that is schema-valid
      on its own; and six fixtures pin the trailing-newline anchoring
      case.

Rapp & Adams              Expires 24 April 2027               [Page 145]
Internet-Draft                    AMMP                      October 2026

L.5.  2.3.0 — inbound byte access in force

   A *minor bump*, prose only: no schema rule and no fixture changed,
   and the wire major stayed "2".  It resolved an inconsistency carried
   since 2.0.0.  A fixture showing a clean Descriptor with content_ref
   and sha256 sat in the valid set as a deliberately forward-valid
   shape, while the prose forbade any Runtime from producing it — the
   gate blessed what the prose forbade.  This version sided with the
   fixtures: the release-scoped byte embargo was dropped, and the
   standing rule became the one stated in Section 7.2 — a Runtime may
   publish bytes and emit content_ref/sha256 only for a Descriptor it
   asserts clean, and a Runtime that cannot make that determination must
   not assert clean.  The substantive clarification is that disposition
   is the Runtime's _assertion_, its basis deliberately unspecified; the
   older prose presumed a scanner, which is what made byte access read
   as a deferred capability rather than as a policy decision whose wire
   shape major 2 already carried.  Nothing was tightened, and there is
   still no outbound content_ref.

L.6.  2.2.0 — deletion rights, read-only inbound

   A *permissive minor bump*: no wire member, no schema change, no
   fixture changed or added.  It (a) declared a read-only inbound tree a
   valid Runtime posture and made the inbound processed/ archive
   directory optional, (b) obliged a Connector not to require inbound
   write access, making consumption-recording Connector-private, and (c)
   named deletion rights per artifact class, along with the two
   constraints attached to them: a Connector must not delete or rewrite
   a published request, and a Connector exercising its new deletion
   rights must preserve req_id monotonicity by other durable means.  It
   also pinned three previously implicit reason_code rules: setting one
   on every Result became a keyworded SHOULD, and a consumer must
   tolerate an unrecognized code and must not infer outcome from it.

L.7.  2.1.0 — the body spool, and three optional Result members

   Recorded at the time as an *additive minor bump*: one new optional
   spooled artifact (the Inbound Message) and its schema, one new
   optional Notice member (provenance), and three new optional Result
   members (reason_code, attestation, ts).  No existing schema gained a
   required member, no existing fixture changed shape, and the wire
   major stayed "2".

   *Correction (recorded at 3.0.0).* That "additive minor" label was
   unsound as written.  Against a strictly validating 2.0.0 consumer —
   every schema at that version was closed at every level — a Notice
   carrying the new provenance member failed schema validation as a
   *whole document* and was silently dropped: precisely the failure mode

Rapp & Adams              Expires 24 April 2027               [Page 146]
Internet-Draft                    AMMP                      October 2026

   cited as the reason attachments had required a major bump,
   reintroduced as a minor.  Version 3.0.0 repairs the versioning model
   rather than relabelling this history, which is why this entry still
   records what shipped rather than what would have been correct.

L.8.  2.0.0 — attachments, and contract_version required

   A *major bump*, moving the wire major from "1" to "2".  It added
   attachment support in both directions — outbound Descriptors with
   required size_bytes and sha256 and their Sidecar bytes, inbound
   Descriptors with a disposition — and made contract_version REQUIRED
   on the submit-request, deliver-notice, and result artifacts, with no
   compatibility mode.  The reason the bump had to be major rather than
   minor is the reasoning in Section 13.1: under the then-universal
   closed envelopes, a new member caused a major-1 consumer to reject
   the _whole document_, which inbound is silent total message loss.  A
   version signal, rather than an unknown-member rejection, is what
   makes that refusal legible.

L.9.  Fixture identifier normalization

   Across these versions the fixture suite's identities were normalized
   to example.com and example.org placeholders, and deployment-specific
   identifiers were removed from fixture content.  This changed no shape
   and no expectation: every affected fixture validates exactly as
   before, and no identifier removed in that pass is reintroduced by
   this document.

Authors' Addresses

   Daniel Rapp
   Proofpoint, Inc.
   Email: drapp@proofpoint.com

   J. Trent Adams
   Proofpoint, Inc.
   Email: tadams@proofpoint.com

Rapp & Adams              Expires 24 April 2027               [Page 147]