Skip to content
Metamynd

Open protocol

Agentic Governance Protocol

Version 1.0. Every authorization decision Metamynd makes is defined here. Implement it, verify against it, or re-implement the gate yourself — no licence required, and no account needed to call any endpoint the spec names as public.

npm i @metamynd/agentsafe-guardor scaffold a governed agent with npx create-metamynd-agent

MetaMynd Agentic Governance Protocol (MAGP)

Specification — Version 1.0 Status: Stable · Date: 2026-08-20 · Editors: MetaMynd

A protocol for authorizing the actions of autonomous AI agents. An agent presents a signed description of what it is about to do; a verifier evaluates that action against the agent's identity, the authority delegated to it, and the policies in force; and returns a decision the agent must obey. Every decision produces evidence a third party can verify without trusting the verifier.


Status of this document

Version 1.0 supersedes the 0.x drafts. It describes what is built and running: every normative clause below corresponds to code in the reference implementation. Work not yet built is listed in Appendix D — Future directions. If it is in the body, it exists.

This specification states what an implementer needs in order to interoperate. How a particular gate stores state, schedules work or recovers from faults is out of scope.

Scope

MAGP is the interoperability surface. The test for inclusion is narrow and worth stating, because it is what keeps this document implementable:

A thing belongs in MAGP if an independent implementer needs it to reach the same verdict on the same request, or to verify the same evidence without access to the platform that produced it.

That test admits identity, the canonical signed message, the order of checks, reason codes, mandate and delegation semantics, policy-bundle distribution, and the evidence and proof formats. It excludes the governance surfaces a platform builds on top — supervisory access for regulators, sandbox programmes, incident routing, the trust graph, document ingestion. Those are real and specified in the companion document MetaMynd Platform Governance; they are not things a second implementation must reproduce to interoperate.

Conformance language

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


Table of contents

  1. Terminology
  2. Roles and entities
  3. Architecture
  4. Identity
  5. Authority: mandates, resource scope, delegation
  6. Policy artifacts
  7. Agent posture: containment, operating mode, reassessment
  8. The authorize protocol
  9. Secondary controls: rate limits and circuit breakers
  10. Escalation resolution
  11. Payment execution binding (x402)
  12. Effects and reconciliation
  13. Evidence and anchoring
  14. Decision records and the governance envelope
  15. Privacy-preserving verification
  16. Discovery and channels
  17. Security considerations
  18. Extensibility and versioning
  19. Conformance
  20. Reference endpoints
  • Appendix A — Reason codes
  • Appendix B — Canonical examples
  • Appendix C — Atom catalog
  • Appendix D — Future directions

1. Terminology

Agent — an autonomous software actor that proposes and performs actions. Identified by a DID.

Principal — the legal person or organisation on whose behalf an agent acts. The delegation root: the chain of authority terminates here, at a named legal entity whose verification status — and how it was verified — is reported on every read (§4.4).

Owner — the tenant account that administers an agent. Distinct from the principal: an owner may administer agents acting for several principals.

Mandate — the authority delegated to an agent, expressed as an ODRL policy and issued as a verifiable credential. Says which actions, within which constraints, for how long.

Standard — a published, versioned rule pack an organisation binds agents to. Enforced.

SOP — a standard operating procedure: an organisation's own rule pack, scoped to its agents.

Molecule — one rule within a Standard or SOP: a set of atoms (pure predicates over request context) and the decision to emit when they all hold.

Gate — the verifier. Evaluates a proposed action and returns a verdict. Server- authoritative: the agent does not decide, and a guard that evaluates locally reaches the same verdict from the same signed inputs.

Verdict — the gate's answer: a disposition plus a reason code (§8.6, Appendix A).

Evidence — the record of a governance decision, in a form a third party can verify independently of the platform that produced it.


2. Roles and entities

Role Holds Responsibility
Principal A legal identity, verified (KYC/KYB) before it may back a mainnet agent (§4.4) Issues mandates; answerable for what its agents do
Agent A DID and a private key Signs and submits proposed actions
Issuer Signing keys for credentials and bundles Issues mandates; publishes and signs policy bundles
Gate The evaluation logic and state Returns verdicts; reserves budget; records evidence
Guard A copy of the deterministic evaluator Refuses locally; re-checks at a counterparty
Counterparty Its own DID May independently re-evaluate a caller's authority
Approver A platform identity and role Resolves escalations

An MCP server is a counterparty that exposes tools. Where it holds its own DID and mandate it is simultaneously an agent, and this protocol treats it as one.


3. Architecture

Two planes.

The control plane issues and publishes: identities, mandates, standards, SOPs, signed policy bundles. It is not on the hot path.

The data plane is the authorize exchange. It MAY be executed:

  • against the gate — the central verifier, which owns the stateful parts (nonce consumption, spend reservation, evidence);
  • at the edge by a guard — deterministic evaluation against a signed policy bundle, with no round trip;
  • at the counterparty — a trustless re-check, so an agent that skips its own guard still cannot get the counterparty to act.

All three MUST produce the same disposition from the same inputs. Where they cannot — because a check is inherently stateful — the edge MUST fail closed and defer (§8.5).


4. Identity

4.1 Agent DID methods

An implementation MUST support at least one of:

  • did:hedera — anchored on an HCS topic. The DID document is derived from the anchor; the agent's Ed25519 public key is the verification method.
  • did:key — self-certifying. The Ed25519 key is the identifier; no anchor, no issuance ceremony, control provable from the identifier alone.

4.2 Proof of possession

4.2.1. An agent whose key was supplied by its holder (BYOK) MUST prove control before the gate accepts any action from it. The gate issues a challenge; the agent signs it; the gate verifies against the key in the DID and stamps the identity as proven.

4.2.2. Until control is proven the gate MUST deny with AGENT_KEY_UNVERIFIED. This is not advisory: an unproven key means the signature check (§8.5 step 5) is meaningless, because nobody has demonstrated that the key belongs to the party claiming it.

4.2.3. Where the platform generated the keypair and returned the private key once, control is established at issuance and no challenge is required.

4.2.4. Proof is per key. A DID minted by rotation or for a second network has its own keypair, so the gate MUST judge possession of the key the request is verified with — that DID's own proof — not the proof recorded for the agent's original key.

4.3 Resolution

4.3.1. A verifier MUST resolve the agent's public key from the DID rather than trusting any key material in the request.

4.3.2. GET /did/{did} returns a W3C DID document for both supported methods.

4.3.3. Identity rotation. Rotating an agent's identity on a network (key-loss recovery) replaces its DID and key there. Every mandate active under the old DID is reissued under the new one with the same terms and cumulative spend; a delegated mandate is reissued delegated and re-validated against its delegator, or else revoked (5.4.11). The old DID no longer verifies: the gate refuses a request signed as it IDENTITY_ROTATED, and GET /did/{did} answers 410 Gone with didDocumentMetadata.deactivated: true (the HTTP binding of W3C DID Resolution). The old DID's Authority Manifest carries retired: { reasonCode: "IDENTITY_ROTATED" }. Neither public read names the replacement DID; the IDENTITY_ROTATED refusal carries it as replacementDid, outside the digested verdict, and only to a request validly signed with the replaced key.

4.4 Principal assurance

4.4.1. Every mandate MUST name an authorizing principal, and an absent one is refused. Beyond that, how much the principal's verification matters depends on the network the agent is on:

  • Testnet and did:key agents — verification is reported, not required. The principal's authority holds whether or not it is verified, and a sandbox or evaluation needs no KYC/KYB. The verdict and the registry carry the principal's status (4.4.2) so a counterparty applies its own risk policy.
  • Mainnet agents — the principal MUST be verified by a mainnet-eligible provider to create the identity at all, and the principal's authority holds only within the platform's limits for its plan. Beyond them the gate refuses with PRINCIPAL_UNVERIFIED. A testnet or did:key agent is never refused with it.

The mainnet check precedes signature verification, because it is a question about the policy rather than about the requester.

4.4.2. The registry (GET /magp/manifest/{did}, GET /policy/mandate/{ref}/status) MUST expose the authorizing principal's current verification status on every read, and every authorize verdict MUST carry it alongside the decision — so a counterparty applies its own risk policy to an unverified-backed mandate rather than assuming verification.

4.4.2a. The public Authority Manifest (GET /magp/manifest/{did}) MUST also expose the agent's live containment (7.1) as a sibling of the manifest — containment: { status, since }, status one of suspended, quarantined, decommissioned, or null when the agent is active. The manifest states the authority the agent was granted; containment does not change that, it stops the agent using any of it, so a rendering of a contained agent's manifest MUST NOT present its granted actions as currently permitted. The containment reason is the owner's and MUST NOT be published. The manifest endpoint itself MUST NOT be served from a cache, and a cached public projection of it (a README badge) SHOULD reflect a containment within a minute.

4.4.3. Verified does not mean identity-checked. The registry's principal block reports not only verified but the basis of the verification — the weakest across the principals behind the agent's active mandates:

basis Meaning
provider An automated third-party provider verified the principal (KYC/KYB).
manual_review A person on the platform's compliance team reviewed and decided.
self_approved The platform approved the principal automatically (sandbox and beta self-serve). No identity check ran. Such a principal cannot create a mainnet agent.
unrecorded Verified, but the record does not say by whom. Never upgraded to a stronger basis.

A counterparty that needs a real identity check MUST require provider or manual_review, not merely verified: true. The block also carries identityChecked, which is true only when verified is true and the basis is provider or manual_review. A self-approved principal reads verified: true, basis: "self_approved", identityChecked: false.


5. Authority

5.1 Mandates

5.1.1. A mandate is an ODRL policy carried in a verifiable credential. It names a target action and carries permissions and prohibitions, each with constraints.

5.1.2. Evaluation order within a mandate is: validity window, then prohibitions, then a matching permission whose constraints all hold. A prohibition that fires outranks any permission.

5.2 Constraints

A constraint is { leftOperand, operator, rightOperand }. Operators are a fixed set — new scenarios add operands, not operators:

eq · neq · lt · lteq · gt · gteq · isAnyOf · isNoneOf · isPartOf · before · after

5.2.1. An unknown operator MUST fail closed.

5.2.2. Well-known operands map to stable reason codes so a denial names its cause: mm:payAmount and mm:cumulativeSpend → SPEND_LIMIT_EXCEEDED; mm:merchant → MERCHANT_NOT_ALLOWED; mm:route → ROUTE_NOT_ALLOWED; mm:counterparty → COUNTERPARTY_NOT_ALLOWED; mm:jurisdiction → JURISDICTION_REQUIRED when the request has no effective jurisdiction, JURISDICTION_NOT_ALLOWED when it has one outside the list (§5.2.3). Any other operand denies with CONSTRAINT_FAILED:<operand>.

5.2.2a. A cumulative cap is per currency. Its limit is a bare number, so the committed spend it is checked against MUST count only holds in the request's currency (compared case-insensitively); summing two currencies has no meaning without a rate, and none is applied. A hold with no recorded currency counts against every currency, so the check fails closed. This changes nothing for a single-currency mandate; for one that allows several currencies (or any), the cap applies to each separately.

5.2.3. Allowed jurisdictions. A mandate MAY restrict the jurisdictions an action may be performed in. Issuance (POST /policy/mandate) takes an optional allowedJurisdictions: a list of ISO 3166-1 alpha-2 codes, trimmed, upper-cased, de-duplicated and sorted; the group code EU expands to the 27 member states. An empty or malformed list is refused (400 MALFORMED_REQUEST); omitting it means no restriction. The term is stored as an ordinary constraint on the permission:

{ "leftOperand": "mm:jurisdiction", "operator": "isAnyOf", "rightOperand": ["DE", "FR", "SG"] }
  • It is judged on the request's effective jurisdiction (§8.3.12) — the country the owner registered for the payee, else the signed top-level jurisdiction — never on an itinerary value. With no effective jurisdiction the request is blocked JURISDICTION_REQUIRED; outside the list, JURISDICTION_NOT_ALLOWED.
  • GET /policy/mandate/{ref}/status returns it as allowedJurisdictions (null = no restriction).
  • A reissue (identity rotation, resource-scope pin, payload-binding toggle) MUST carry the term. A jurisdiction constraint that cannot be read back as an allow-list (a hand-authored isNoneOf, say) refuses the reissue of that mandate rather than dropping the restriction.
  • A delegated mandate inherits or narrows it (§5.4.5).
  • The zero-knowledge path proves membership in the same list (§15.1.2).

5.3 Resource scope

5.3.1. Authority over actions does not answer which systems and data an agent may reach. A resource scope is expressed as an ordinary constraint over the resource operand, which the request declares in its context:

{ "leftOperand": "resource", "operator": "isAnyOf",
  "rightOperand": ["crm.contacts", "ledger.postings"] }

5.3.2. The resource is part of the signed message (§8.3): it is cryptographically committed the same way merchant already is (and a top-level jurisdiction is, §8.3.12), not merely asserted the way tool or an itinerary value is. A counterparty relaying a buildSignedRequest()-built request cannot substitute a different resource without invalidating the signature.

5.3.3. An action that declares no resource, evaluated against a mandate carrying a resource constraint, MUST be denied: an action that will not say what it touches cannot be checked against a scope.

5.4 Delegation

5.4.1. An agent MAY delegate a narrowed subset of its own authority to another agent. The delegated mandate is an ordinary mandate; nothing downstream special-cases it.

5.4.2. Narrowing invariant. A delegated mandate MUST NOT widen its parent. Narrowing is asymmetric per operator, and an implementation that gets this wrong grants authority nobody issued:

Operator Narrower means
lteq, lt a lower ceiling
gteq, gt a higher floor
isAnyOf, isPartOf a smaller allow-list (child ⊆ parent)
isNoneOf a larger deny-list (child ⊇ parent)
before an earlier deadline
after a later start
eq the identical value

5.4.3. A parent constraint the child omits is a widening, not a simplification. A child that drops its parent's spend cap holds more authority than its parent.

5.4.4. A child MUST NOT outlive its parent's validity window, and MUST retain every prohibition the parent carried.

5.4.5. Constraints the parent did not have MAY be added freely: they can only remove authority. The same holds for the permission-level settings that are not constraints: a child MUST NOT lower or drop its parent's riskTier (§6.3), nor drop its parent's requirePayloadBinding (§8.3.9), and a value of either that is not a recognised one is refused rather than read as "not set". Nor may it drop or widen its parent's argument policy (§8.3.14): each of the child's allow paths MUST sit at or below one of the parent's, and each of the parent's sensitive paths MUST appear in the child with the same mode or redact.

  • Allowed jurisdictions (§5.2.3). A child that states no allowedJurisdictions inherits its parent's list, and the implementation MUST store it on the child explicitly (so the child's own document carries the restriction, and a later change to the parent cannot widen it). A child that states a list has it normalised as at issuance (EU expanded) and it MUST be a subset of the parent's (isAnyOf narrowing, 5.4.2) — otherwise the delegation is refused CONSTRAINT_WIDENED (operand mm:jurisdiction). A child of an unrestricted parent MAY add a list. A reissue of a delegated mandate carries its list and re-checks it against the parent's current terms (5.4.11): a list no longer within the parent's is DELEGATION_NARROWING_FAILED.

5.4.6. The chain MUST terminate at a bounded depth (reference implementation: 3).

5.4.7. The delegation root MUST remain the original principal at every depth, so an accountability walk from any descendant terminates at the principal that issued the root mandate — a named legal entity, whose verification status and basis (§4.4.3) are reported.

5.4.8. Narrowing SHOULD be verified at issuance. A stored child that has been proven no wider than its parent costs nothing extra to evaluate.

5.4.9. Revoking a mandate revokes everything delegated from it. A child holds authority only because its parent did, so revoking a mandate — directly (POST /policy/mandate/{ref}/revoke), as a chain (POST /policy/mandate/{policyId}/revoke-chain), or by decommissioning its agent — MUST revoke every mandate delegated from it, at every depth, and each one MUST be revoked exactly as a directly revoked mandate is (8.7.15): made inactive, its pending reviews closed (10.4.2), its unclaimed holds released. This holds for a reissue too: every mandate delegated from the superseded mandate is revoked, and the delegator re-delegates if it still wants to.

5.4.10. A delegated mandate is only as live as its chain. Independently of the cascade, a gate MUST refuse to use a delegated mandate unless every mandate it was delegated from, up to the root, is active — at authorize, at an escalation approval or a reviewer's MODIFY (10.4.1), and on a claim or a capture of an unclaimed hold under it (8.7.15): MANDATE_REVOKED when an ancestor is inactive, and DELEGATION_CHAIN_BROKEN when the chain cannot be walked to a root (a parent that no longer exists, a cycle, or a chain deeper than the bound of 5.4.6). The walk MUST be bounded and cycle-safe.

5.4.11. A reissued delegated mandate stays delegated. When a delegated mandate is reissued — the delegate's identity is rotated (4.3.3), a resource scope is pinned into it, its payload-binding requirement is switched — the new mandate MUST keep the delegation: the same parent (and delegating agent), the same depth, the delegator's tenant as its owner and the same root principal (5.4.7). Its terms are the delegated mandate's own terms with only that change applied. Before it is issued it MUST be re-validated as a delegation is at issuance: every mandate above it active (5.4.10), and the changed terms a narrowing (5.4.2–5.4.5) of the parent's current terms. On failure (MANDATE_REVOKED, DELEGATION_CHAIN_BROKEN or DELEGATION_NARROWING_FAILED) nothing is issued: an owner's change is refused and the delegated mandate stays as it was, while an identity rotation revokes it (8.7.15). A reissue is never a way out of a chain.


6. Policy artifacts

6.1 Standards and SOPs

6.1.1. A rule pack is a set of molecules. A molecule fires when all its atoms hold, and emits its declared decision and reason code.

6.1.2. Atoms are pure predicates over request context — no clock, no network, no model. Determinism is what allows the edge and the gate to agree. The catalog is Appendix C.

6.1.3. Across all molecules of all bound packs the most restrictive decision wins.

6.2 Signed policy bundles

6.2.1. GET /policy/bundle/{did} returns the agent's evaluable policy, signed by the issuer. GET /magp/policy/pubkey publishes the verification key.

6.2.2. A guard MUST verify the signature before evaluating, and MUST reject a bundle whose signature is invalid for any action. A guard holding a pinned verification key MUST also reject a bundle whose signature is absent, for any action — an amount-0 action included. The issuer never serves an unsigned bundle, so under a pin one can only have been stripped in transit, and an amount-0 action is not necessarily a read: a non-financial agent's every action carries no amount.

6.2.3. Bundles carry a maxStaleness. A value-bearing action MUST fail closed on an unsigned, tampered or stale bundle (POLICY_BUNDLE_UNSIGNED, POLICY_BUNDLE_SIGNATURE_INVALID, POLICY_BUNDLE_STALE). A guard holding a pinned key MUST fail closed on a stale bundle for any action, since the issuer re-issues every copy it serves and a stale one is a replay of authority that may since have been withdrawn. Only a guard with no pinned key (6.2.4) MAY let an amount-0 action proceed on an unsigned or stale bundle.

6.2.4. A guard that has no pinned verification key cannot perform 6.2.2 and is trusting the transport instead. It MUST NOT permit a value-bearing action on a bundle that arrived over an unauthenticated transport (plain http://) — POLICY_BUNDLE_UNVERIFIED — unless its operator has explicitly opted out for local development. Over TLS it SHOULD warn that the bundle is trusted on the strength of TLS alone.

6.2.5. maxStaleness bounds the age of the copy a guard holds, measured from the bundle's issuedAt. The issuer therefore MUST serve a bundle whose issuedAt is the time it is served, signed at that time — not the time its rules were last compiled, which can be arbitrarily old for rules that have not changed. A compiled bundle is re-issued on every fetch: issuedAt is the serve time, and compiledAt and compiledSignature (the compile's own signature, the one anchored on the agent's topic as policy-update.sigDigest, §5.3.1) are carried inside the re-signed content. The compiled bundle is recovered by setting issuedAt to compiledAt, removing those two fields and verifying compiledSignature over the result; a guard confirming the bundle is the latest anchored one compares the anchor with sha256(compiledSignature), or with sha256(proof.signature) for a bundle that carries no compiledSignature.

6.2.6. Revoked actions. A bundle MAY carry revokedActions: the actions the agent was granted a mandate for and no longer holds one for, every mandate for them having been revoked. It is part of the signed content, and the issuer omits it when it would be empty. A guard that finds no mandate for an action listed there MUST refuse it MANDATE_REVOKED rather than NO_MANDATE or NO_PERMISSION_FOR_ACTION, as the gate does (§8.5): the authority was granted and withdrawn, which is not the same fact as never granted. The field names the refusal and never decides it, since the action is refused either way. A guard reading a bundle without it refuses as before. An action granted again is in mandates and is not listed.

6.2.7. Owner. A bundle carries ownerPrincipal: the public DID of the principal that owns the agent — its organisation principal, else its owner's oldest verified principal — signed with the rest, and omitted only when the agent has none (it then cannot act: the gate requires a verified principal). It is how a Service bound to the owner of its credentials (16.3) checks that an admitted agent belongs to that owner. It never carries the platform's internal owner identifier.

6.2.8. Policy commitment. The policy-update op a compile anchors on the agent's topic SHOULD carry policyRoot and policyParts: a salted commitment to the compiled bundle's parts, so the owner can show an auditor one part of the policy issued to the agent, with proof it was in that policy, and nothing else. The parts are each mandate (id = action:hash), each SOP (id), each pinned standard (id = key@version), and one subject part naming the agent, the version label, the owner principal and the sorted revoked actions; parts sharing an id are numbered #2, #3… in content order, skipping any number another part's id already bears. Each is a leaf

sha256( "MAGP-POLICY-PART-v1" ‖ 0x00 ‖ kind ‖ 0x00 ‖ id ‖ 0x00 ‖ canonical(content) ‖ 0x00 ‖ salt )

where canonical is JSON with object keys sorted and undefined members dropped, and salt is the lower-case hex of an HMAC-SHA256 under the issuer's secret over the bundle hash, the kind and the id. Leaves are ordered by (kind, id) and policyRoot is "sha256:" followed by their Merkle root, built as the evidence tree is (13.2): a lone last node is paired with itself. policyParts is the leaf count. The salt is per part and per bundle, so a disclosed part reveals nothing about the others, and a part cannot be confirmed by guessing its content: a mandate from a template differs from its neighbours only in a cap or a counterparty, and an unsalted hash of it could be found by trying the few candidates. An issuer with no secret to salt with MUST NOT anchor a commitment. The salts are derived, not stored, so an issuer that replaces its secret can no longer open the commitments anchored under the old one. policyParts is public: it reveals how many parts the policy has, never which.

The owner obtains an opening from GET /agent-identity/{ref}/policy-disclosure (bundleHash selects a compiled bundle, else the active one; kind and id select the part, and without them the response names the parts there are to open). The opening is { did, bundleHash, policyRoot, policyParts, disclosure: { kind, id, content, salt, proof } }. A verifier recomputes the leaf, walks the proof to policyRoot, refuses a proof whose length does not fit a tree of policyParts leaves or whose positions name a leaf beyond it (the duplicated last node is not a further part), and finds a policy-update op for the same DID carrying that root, bundle hash and count. The op's consensus timestamp is when that policy took effect, and a decision is placed against it by bundle hash. magp-evidence verifyPolicyDisclosure does all of this with the issuer offline.

6.3 Context provenance

6.3.1. The context the atoms judge is assembled from sources of very different trust, and a rule that judges what the agent said about itself has judged nothing. A signature does not help: it says who asserted a value, not that it is true, and the counterparty re-check reads the same claim. So every context field MUST carry a provenance — where its value came from — attached only by the party assembling the context, never by the request:

Provenance Source Rank
agent_asserted the agent's own unsigned context (itinerary) 0
agent_signed covered by the agent's signature: attributable, not verified 1
gateway_derived derived by the authenticated counterparty from the real request 2
authoritative derived by the issuer (spend, call count, trust) or set by the mandate's owner 3
attested backed by a verified attestation or credential 4

An unlabelled field is agent_asserted: no context, however it was built, reads as more trusted than the agent's own word. An agent cannot set a label — provenance is not part of any request, and a value the agent sends under a key named "provenance" is just another unsigned field.

6.3.2. Assembly. Sources are applied in order of trust, each later source winning a collision: unsigned < signed < gateway-derived < server-derived. This is §17.2 extended to the two derived sources.

6.3.3. Risk is a floor, never a ceiling. riskLevel is an ordered, restrictive field, so it is merged, not overridden: the effective value is the maximum of every trusted source and the agent's own claim. Trusted sources are the mandate owner's riskTier on the permission (authoritative), a value the gateway derived from the request (gateway_derived) and a server-derived one (authoritative). An agent may raise its stated risk; it can never lower it below what a trusted source says. The effective value takes the provenance of the most trusted source present. A trusted source that states an unusable riskLevel has said nothing: it neither overwrites the agent's claim nor lends it the trusted source's label. riskTier is part of the signed mandate, so the gate and every guard apply the same floor, and a delegated mandate (§5.4) MUST NOT drop or lower its parent's tier — a lower tier is less scrutiny, a widening — nor carry a tier that is not a recognised level (refused, never read as "no floor"). riskLevel is read case- and whitespace-insensitively ("HIGH " is high); the levels are low, medium, high, critical.

6.3.4. Fail closed on the atoms that judge risk. A molecule containing a risk-at-or-above atom MUST NOT pass when riskLevel is missing, or is present but not one of the four levels: it MUST decide at least escalate, with reason code CONTEXT_UNVERIFIABLE, whatever its combinator (a none rule must not read absence as "nothing wrong"). If the molecule fires on its own, its own decision and reason code stand (an observe is raised to escalate). A molecule that could not be judged escalates even if another of its atoms would have kept it from firing.

6.3.5. Policies declare the trust they need. A molecule MAY declare requireProvenance: { field: level }. For each named field the molecule needs a well-formed value whose provenance is at least level; otherwise it decides at least escalate (CONTEXT_UNVERIFIABLE). An unknown level is read as the strictest (attested), and a document declaring one is rejected at authoring, so a typo can only make a rule refuse more. A molecule that declares nothing behaves exactly as before, except as 6.3.4 requires.

6.3.6. Counterparty derivation. A guard SHOULD let the service supply trustedContext — context the service derived from the real call, never from the agent — which is applied over the agent's claim and labelled gateway_derived. A trustedContext the service configured but that yields nothing usable — not an object, or a riskLevel that is not a level (a lookup that missed and returned nothing) — means its deriver is broken, and the request MUST be refused; it MUST NOT fall back to the agent's word. The operating-mode gate (§7.2) judges the effective risk, so an agent in supervised mode cannot skip the high-risk escalation by claiming low.

6.3.7. What this does and does not establish. It closes omission, garbling and understating where a trusted source exists. It does not make an agent's claim true: with no owner riskTier, no gateway derivation and no requireProvenance, riskLevel is still the agent's word — only hiding or garbling it is now refused. The other agent-supplied fields (tool, model, consent, …) keep their documented absent-field behaviour (Appendix C); a rule that must not depend on the agent's word about them declares requireProvenance. jurisdiction is no longer one of them: the gate enforces only the effective jurisdiction of §8.3.12 (registered payee country, else the signed field), and an itinerary jurisdiction is ignored.

6.3.8. Owner-keyed operations (non-financial risk). A financial action has risk the issuer derives from facts the agent cannot fake (the amount against the cap, a first payment to an unvetted merchant). A non-financial action has no amount, so unless the owner configures otherwise, whether it escalates depends on the riskLevel the agent declares — an agent that labels op: "delete-all" as low is not reviewed. The owner closes this by keying a rule on what is being done: the context-value-in atom fires when a context field (a dot path such as op or params.op) has one of the owner's values, and context-value-not-in fires when it has a value not on an allow-list (Appendix C). Both are pure, so the gate and every guard bundle reach the same verdict. Values are compared after Unicode NFKC, removal of invisible characters, lower-casing, folding of common Cyrillic/Greek look-alikes of Latin letters, and removal of whitespace, hyphens and underscores; a value that is an object or a nested list is unjudgeable and MUST fire; a list fires if any element would; a field that is absent fires only when the atom is configured missing: "fire". A deny-list cannot foresee every spelling, so an allow-list is the stronger form when the safe operations are known. When a refusing or escalating molecule fires on such an atom, the gate SHOULD report it with the decision as a risk signal { signal: "context-value", level: "high", detail } (e.g. op=delete-all is owner-marked for review), alongside the financial signals, so the review shows the risk was derived from the request rather than declared by the agent. The field is only as good as the executor's binding of it: it must be the field the executor acts on, and the approved context is bound at claim (8.7.20).


7. Agent posture

Three per-agent states that bias or override evaluation. All are gate-side facts; a guard learns them through the bundle.

7.1 Containment

7.1.1. A contained agent is denied all actions until reinstated — AGENT_SUSPENDED, AGENT_QUARANTINED, or AGENT_DECOMMISSIONED. Containment is checked before any action-specific evaluation.

7.1.2. Containment MAY be applied automatically by a firing rule (suspend/quarantine), or manually by an owner (suspend/quarantine/decommission). Reinstating a suspended or quarantined agent restores gate access, but not mandates or SOP assignments revoked meanwhile. Decommission retires the agent: it revokes every active mandate and unassigns every SOP, and it is final — an implementation MUST NOT reinstate a decommissioned agent, nor lower its containment to suspended or quarantined (which would make it reinstatable); such a request is refused AGENT_DECOMMISSIONED (HTTP 409). Its work is taken over by a newly provisioned agent.

7.1.3. "All actions" includes the ones already authorized: an unclaimed hold of a contained agent cannot be claimed or captured, and is voided, so a reinstatement never revives an authorization granted before the containment (8.7.17).

7.2 Operating modes

7.2.1. Four rungs, least to most autonomous: read_only · restricted · supervised · autonomous.

7.2.2. read_only denies any value-bearing action up front (MODE_READ_ONLY).

7.2.3. restricted and supervised do not deny. They impose an escalate floor applied after deterministic evaluation: an action the rules would otherwise permit is routed to a human instead. A rule-driven block or escalate already outranks the floor, so applying it earlier would only mask a more specific reason.

7.2.4. The effective rung is the operator's manual pin when set, otherwise the trust-derived rung. Mode changes SHOULD be audited.

7.3 Reassessment

7.3.1. A material change to an agent's authority (for example a large increase in a payment cap) invalidates its current assurance. A flagged agent's value-bearing actions escalate with REASSESSMENT_PENDING until an owner clears the flag.


8. The authorize protocol

8.1 Endpoint

POST /policy/mandate/authorize                          (PUBLIC, unauthenticated)
POST /policy/mandate/authorize/{authorizationId}/capture
POST /policy/mandate/authorize/{authorizationId}/void

The gate is public by design: any counterparty may check any agent's authority. Authority to act comes from the mandate, not from an API key.

8.2 Request

{
  "agentDid":  "did:hedera:testnet:...",
  "action":    "flight-purchase",
  "amount":    150,
  "currency":  "USD",
  "merchant":  "skyward-air",
  "resource":  "crm.contacts",
  "jurisdiction": "SG",
  "itinerary": { "tool": "book-flight", "riskLevel": "low" },
  "nonce":     "b1c2…(8–128 chars)",
  "issuedAt":  "2026-07-10T05:12:00Z",
  "signature": "<hex Ed25519>"
}
  • merchant and resource are top-level signed fields (§8.3.1), each optional and each the empty string in the signed message when absent. resource names what the action touches and is checked against a resource-scoped mandate. The signed values are applied last, so they always take precedence over a same-named key in the unsigned itinerary: a resource placed there cannot satisfy a resource scope.
  • jurisdiction (optional, ISO 3166-1 alpha-2) is a top-level signed field too; a request that carries it is signed over the v2 message (§8.3.12). It is the only jurisdiction the gate enforces — one placed in the itinerary is ignored.
  • itinerary carries request context — the atom inputs beyond the signed core. It is unsigned: every value in it, riskLevel included, is an agent's own claim and is treated as one (§6.3).
  • amount and currency may be omitted together for a non-financial action; a mandate that carries a spend constraint still requires a real amount.
  • nonce is single-use; issuedAt bounds freshness.
  • An optional trace object correlates a multi-step workflow.

8.3 Canonical signed message

8.3.1. The signature MUST be an Ed25519 signature over the UTF-8 string formed by joining these eight fields, in this order, with | (U+007C), substituting the empty string for an absent merchant or resource:

agentDid | action | amount | currency | merchant | resource | nonce | issuedAt

8.3.2. Verifiers MUST reconstruct the message from the fields received, never trust a client-supplied message, and verify against the key resolved from agentDid.

The three clauses that follow exist because each fails as SIGNATURE_INVALID and as nothing else — a well-formed request, a correct key, and bytes that simply differ from the ones the verifier rebuilds.

8.3.3. Private key encoding. Managed provisioning issues the key as hex-encoded DER PKCS#8 (ASN.1 SEQUENCE tag 30), not a raw 32-byte seed. An implementation SHOULD accept either and MUST NOT assume the raw form.

8.3.4. Number stringification. amount is signed as text, so its rendering is part of the protocol. The canonical rendering is ECMAScript String(n): an integral value carries no fractional part (150.0 → "150"). Languages whose default float formatting keeps a trailing .0 MUST normalise. The disagreement is not only the trailing .0: outside 1e-6 ≤ |n| < 1e21 ECMAScript uses exponent form with its own spelling (5e-7 is "5e-7", 1e21 is "1e+21"), and below 1e-4 a language's default may already differ — Python's repr(0.00005) is "5e-05" where ECMAScript writes "0.00005". A verifier reconstructs this text from the JSON number it parsed, so an integer beyond 2^53 is signed as the double a JSON parser holds, not as its exact digits. Values ≥ 1e21 SHOULD NOT be sent.

8.3.5. Field identity. Every field is signed as the literal string transmitted. Any RFC 3339 rendering of issuedAt a verifier can parse is acceptable, but the signed and transmitted renderings MUST be byte-identical. Formatting a timestamp twice — once for the message, once for the body — is the common violation, and at second precision it fails only when a second happens to tick between the two calls.

8.3.6. A verifier MAY attach a non-normative hint to a SIGNATURE_INVALID verdict naming §8.3.3–§8.3.5 as usual causes. The hint is diagnostic only and MUST NOT enter the digested decision content.

8.3.7. A verifier MAY distinguish a request that verifies against a prior canonical message shape (e.g. the seven-field message from before resource existed) from a genuinely invalid signature, returning CLIENT_PROTOCOL_VERSION_UNSUPPORTED instead of SIGNATURE_INVALID — still refusing the request either way, but naming the real cause (an outdated client) rather than implying a broken or rotated key.

8.3.8. Conformance vectors. docs/protocol/authorize-vectors.json publishes known-answer vectors for the signed message: for each, the eight input fields, the exact message, and the Ed25519 signature a fixed public test key produces over its UTF-8 bytes (Ed25519 is deterministic, so every implementation must reproduce the same bytes). The vectors cover the cases that have each broken a client — an integral float (150.0 → 150), a fractional amount, a sub-cent amount (0.00005), the exponent forms (1.5e-7, 1e+21), an integer beyond 2^53, an amount near 1e21, an absent merchant and resource, resource alone, a literal | and \ in a field, and non-ASCII text. An implementation SHOULD reproduce every vector before it is trusted with a real request.

8.3.9. Payload binding (optional). The eight signed fields do not cover a payee, an account number, a passenger list, a route — every other field a real tool takes. A gateway can compare amount, merchant and resource in what it forwards, but a field the signature never mentioned is either refused (the tool cannot be used) or allowed and unbound. An agent that wants the WHOLE payload held to what it authorized signs a digest of it:

payloadDigest    = "sha256:" + lower-case-hex( SHA-256( UTF-8( canonical(payload) ) ) )
payloadSignature = Ed25519( UTF-8( MAGP-PAYLOAD-v1 | agentDid | action | nonce | issuedAt | payloadDigest ) )
  • canonical is RFC 8785 (JSON Canonicalization Scheme): object keys sorted by UTF-16 code unit, no insignificant whitespace, ECMAScript number and string serialisation (§8.3.4 applies to every number in the payload). The input is JSON. undefined, NaN, Infinity, functions, BigInt and an unpaired surrogate are refused, not normalised: two languages would normalise them differently, and a digest they compute differently is a binding that silently never matches. A payload nested deeper than 32 levels or larger than 256 KiB once canonical is refused too. The digest is of the JSON the executing side RECEIVES, so a value a language holds differently from JSON (a Python 250.0, an integer beyond 2^53) is digested as the JSON number it becomes.
  • The fields are escaped exactly as in §8.3.1, and the message is domain-separated from the §8.3.1 message and from the service messages of §8.7.6: a signature made for one purpose cannot be replayed for another. It names the agent, action, nonce and issuedAt of the authorize request it accompanies, so a digest cannot be lifted from one authorization onto another.
  • payloadDigest and payloadSignature are two optional top-level request fields, both or neither. They are not a ninth field of §8.3.1, which every existing client signs as eight; a request that carries neither is exactly what it always was.
  • A verifier MUST, once the §8.3.1 signature verifies (check 5, §8.5), verify the payloadSignature under the same key, and MUST block a request whose binding is present but not valid — a digest with no signature, a signature with no digest, a digest that is not sha256: and 64 lower-case hex, or a signature that does not verify — with PAYLOAD_BINDING_INVALID. A digest nobody signed binds nothing, so it is refused, never stored.
  • The verifier MUST store the digest with the hold, and with an escalation when the action is held for a human, so that an approved hold keeps it.
  • Acknowledgement. An allow, observe or escalate verdict for a request that carried a binding MUST state the digest the authorization is bound to (data.payloadDigest, null for an unbound one), outside the digested decision content. The binding is not covered by the §8.3.1 signature, so a hop could strip it. An agent that sent a digest MUST treat a permit or escalation whose payloadDigest is not the digest it sent — including an absent one — as not bound: refuse it (PAYLOAD_BINDING_NOT_CONFIRMED) and release the hold it was given.
  • A reviewer's MODIFY decision (§10) changes the action the agent signed, so the hold it mints carries no digest until the agent binds the payload of the action that will run (§8.3.11). The review's status reports payloadBound (a boolean, present only when there is a hold) so the agent knows it still has to bind.
  • Owner requirement. A mandate's permission may carry requirePayloadBinding: true (issued with the mandate, carried in the signed mandate, never dropped by a delegation, §5.4). For such an action the verifier MUST block a request that carries no binding — after the §8.3.1 signature verifies, so only the agent learns the setting — with PAYLOAD_BINDING_REQUIRED, and MUST refuse to grant a claim (§8.7.11) on a hold that carries no digest, with the same code, until the agent has bound one (§8.3.11). Without it, binding stays the agent's or the executor's choice: an agent, or a hop that strips the two fields, can leave a hold unbound.
  • The comparison that gives the binding its force is made at the claim (§8.7.11).

8.3.10. Payload-binding vectors. docs/protocol/payload-binding-vectors.json publishes known-answer vectors for §8.3.9: canonical forms and digests (key order, whitespace, numbers in every exponent form, integers beyond 2^53, string escapes, non-ASCII text, key order beyond the Basic Multilingual Plane), inputs that MUST be refused, and complete bindings — message and the Ed25519 signature a fixed public test key produces over it. The same file holds the vectors for §8.3.11.

8.3.11. Binding a payload to a hold that already exists. A hold can exist with no digest: a reviewer's MODIFY changes the action the agent signed (§8.3.9), so the hold minted for the modified action has none. The agent binds the payload of the action that will run with

POST /policy/mandate/authorize/{authorizationId}/payload-binding
{ agentDid, action, nonce, issuedAt, payloadDigest, payloadSignature }
payloadSignature = Ed25519( UTF-8( MAGP-PAYLOAD-REBIND-v1 | agentDid | action | authorizationId | nonce | issuedAt | payloadDigest ) )

The endpoint is public: the agent's signature is the credential, exactly as for /authorize.

  • The message is the §8.3.9 message with the authorization id added, under a domain of its own (MAGP-PAYLOAD-REBIND-v1, escaped as §8.3.1). It names the hold, so a digest cannot be lifted onto another one, and no signature made for one of the two messages can verify as the other.
  • The verifier MUST check that the hold exists; that payloadDigest is sha256: and 64 lower-case hex; and that payloadSignature verifies under the key agentDid resolves to — and only THEN say anything about the hold: that it was issued to agentDid and (when its mandate names an action) to action, so an unauthenticated caller cannot probe a hold's owner or action by authorization id. It MUST then check that issuedAt is fresh and consume nonce once, in that order, so a forged request cannot burn a victim's nonce.
  • An agent that is contained (suspended, quarantined or decommissioned, §7.1) is refused with its containment code (AGENT_SUSPENDED, AGENT_QUARANTINED, AGENT_DECOMMISSIONED, 403): it is denied everything at /authorize and does not get to keep arranging what its existing holds will execute. Checked once the signature has proven it is that agent asking, so a stranger learns nothing about the agent's status.
  • It MUST apply the digest only while the hold is live, unclaimed and carrying no digest, atomically against a claim and against a second binding. A digest is never overwritten.
  • The same digest already on the hold is an idempotent success (200, alreadyBound: true): a retry of a binding whose response was lost — but only for a hold that is otherwise still live; a voided or lapsed hold is reported as such first. A different digest is refused.
  • The response echoes the digest it now holds (data.payloadDigest). An agent MUST treat a success that does not echo the digest it sent as not bound.
Refusal HTTP Meaning
AUTHORIZATION_NOT_FOUND 404 no such hold
AUTHORIZATION_AGENT_MISMATCH · AUTHORIZATION_ACTION_MISMATCH 403 the hold is another agent's, or for another action
PAYLOAD_BINDING_INVALID 403 a malformed digest, an unresolvable key, or a signature that does not verify
AGENT_SUSPENDED · AGENT_QUARANTINED · AGENT_DECOMMISSIONED 403 the agent is contained (checked after the signature)
REQUEST_EXPIRED 403 issuedAt outside the freshness window
REPLAY_DETECTED 409 the nonce was already used
PAYLOAD_ALREADY_BOUND 409 the hold already carries a different digest
AUTHORIZATION_ALREADY_CLAIMED · AUTHORIZATION_VOIDED · AUTHORIZATION_EXPIRED 409 the hold is no longer unclaimed and live

A hold that was bound at authorize time cannot be re-bound. Binding does not change what the hold authorizes (amount, merchant, action): it only names the payload the agent is willing to have executed under it.

8.3.12. Signed jurisdiction (message v2). A request MAY carry a top-level jurisdiction: an ISO 3166-1 alpha-2 code, sent upper-case. A request that carries it MUST be signed over the v2 message — the eight §8.3.1 fields, then the literal version tag MAGP-AUTH-v2, then the jurisdiction exactly as transmitted, ten fields, each escaped as in §8.3.1:

agentDid | action | amount | currency | merchant | resource | nonce | issuedAt | MAGP-AUTH-v2 | jurisdiction
  • A request that does not carry it is signed over the §8.3.1 message, byte for byte as before: every existing client keeps verifying.
  • A verifier MUST choose the message from the request itself — jurisdiction present → v2, absent → v1 — and MUST NOT try the other. Adding the field to a v1-signed request, changing it, or stripping it from a v2-signed one therefore fails SIGNATURE_INVALID. A present value that is not two ASCII letters is refused (MALFORMED_REQUEST).
  • The effective jurisdiction of a request is, in order: the country the mandate's owner registered for the request's payee (the signed merchant's active entries in the owner's payee directory, PUT /policy/payees/country), when it has one — the registry is authoritative; else its signed jurisdiction, upper-cased; else none. It is the ONLY value the gate enforces: the mandate's allowed-jurisdictions term (an mm:jurisdiction isAnyOf constraint) and the jurisdiction-not-allowed atom are both judged on it. An itinerary jurisdiction / mm:jurisdiction is the agent's unsigned word and is ignored for enforcement.
  • A registered country stands in for a missing signed value (so a registered payee satisfies the requirement below). A signed jurisdiction that differs from it — or entries for one payee that name more than one country — is blocked JURISDICTION_MISMATCH, a hard block, whether or not the mandate restricts jurisdictions. The decision record's jurisdiction control names the source (signed | registered), and the rule context labels the value agent_signed or authoritative accordingly.
  • When the mandate restricts jurisdictions for the action, or an enforced (non-observe) SOP or Standard molecule judges jurisdiction-not-allowed, a request with no effective jurisdiction is blocked JURISDICTION_REQUIRED; one outside the mandate's list is blocked JURISDICTION_NOT_ALLOWED. Both are hard blocks and outrank an escalate (§8.5). With neither a restriction nor such a rule, a request behaves exactly as before, with or without the field.
  • The verifier MUST store the signed jurisdiction with an escalation; a reviewer cannot change it. An approval or a MODIFY (§10) resolves the effective jurisdiction again from it and the payee registry AS OF the approval — so an owner who changed the payee's country since the review opened is heard: a stored signed value that now contradicts it is refused JURISDICTION_MISMATCH. With no effective jurisdiction, a review under a jurisdiction-restricted mandate, or under an enforced jurisdiction rule, is refused APPROVAL_INPUT_UNAVAILABLE.
  • docs/protocol/authorize-v2-vectors.json publishes known-answer vectors for the v2 message under the §8.3.8 test key; a separate file, so a client that pins the eight-field vectors keeps passing until it implements v2.
  • POST /magp/private-authorize (§15) is unchanged: it never sees the jurisdiction, and proves membership in the mandate's list in zero knowledge.

8.3.13. Context signature (optional). The itinerary, trace and materiality are not in the signed message, so anything between the agent and a verifier can rewrite them. A request MAY carry envelopeSignature: an Ed25519 signature, by the same key as signature, over the UTF-8 bytes of the request's envelope hash — the lower-case hex SHA-256 of the GovernanceEnvelope built from the request as transmitted (no defaults applied; context = the itinerary, omitted when absent or null, present when {}), minus its integrity block, with object keys sorted recursively and undefined values dropped. resource and jurisdiction are not in it; the §8.3.1 / §8.3.12 message signs them.

  • A verifier MUST check a present envelopeSignature after signature (§8.5 step 5) and before any rule reads the context, over the very itinerary object it will evaluate. One that does not verify — the context was altered after signing, or it was signed with another key — is refused CONTEXT_SIGNATURE_INVALID, fail closed.
  • Absent, the request is judged as before: the signature is optional on the wire (the reference clients send it by default). A relay can also strip it, so a counterparty that relies on the context SHOULD require it, refusing an unsigned context with CONTEXT_SIGNATURE_REQUIRED. That code is receiver-only: the gate has no such option.
  • It attributes the context to the agent; it does not make it true (§17.4).
  • docs/protocol/context-signature-vectors.json publishes known-answer vectors under the §8.3.8 test key: the request, the canonical envelope, its hash and the signature.

8.3.14. Argument policy (optional). The signed fields and the rules do not see the arguments a tool runs with, so a call within its cap can still carry a field nothing judged. A mandate's permission MAY carry arguments: { allow, sensitive? }, issued with the mandate and carried in the signed bundle:

  • allow lists the JSON Pointer (RFC 6901) paths the arguments may carry; a listed path permits everything below it, and a * segment matches any one key or array index. The root is not a path. Every value in the arguments MUST sit at or below a listed path, or the call is refused ARGUMENT_NOT_ALLOWED, naming the offending path. Keys are compared exactly: a renamed or re-cased key is refused.
  • sensitive lists { path, mode }. Before the call leaves the verifier, a value at a redact path is replaced with "[REDACTED]", and a value at an hmac path with hmac-sha256: followed by the lower-case hex HMAC-SHA256, under the executor's own key, of the value's canonical JSON (object keys sorted). Where both apply, redact wins. A verifier with no key MUST refuse a call that needs one (SENSITIVE_ARGUMENT_UNPROTECTED) rather than send the value in the clear. The hash is over the value alone, so under one key an equal value hashes equally in every field and tool: that is what lets a tool match values it cannot read. An owner who does not want values linked uses redact.
  • It is enforced by the executor that holds the arguments (the MCP guard; the HTTP gateway's JSON body; the A2A guard, where a task's arguments are its one data part), not at authorize, on a verdict that could run the call and before any claim (§8.7.11), so a refused call spends no hold. Arguments it cannot check — not a single JSON object, deeper than 32 or larger than 10,000 values, not given to it, or a malformed policy — are refused ARGUMENTS_UNCHECKABLE. Every policy covering the action applies.
  • An agent that runs its own tool in-process holds the tool to it too. The gate never sees the arguments, so it names the policy instead: a permit carries argumentPolicies (the action's policies, [] for none: always stated, so an agent can tell "none" from an issuer that predates the field), and so does an approved escalation's status (§9a), so a resume is held to the same policy before its one resume is taken (§9a.6). This is cooperative, as the in-process wrapper is; the boundary remains an executor in another process.
  • The owner may set, change or remove a deployed agent's policy for one action; like any change to the mandate's terms it reissues that mandate (§8.7.15). A delegated mandate may only narrow its delegator's policy.
  • The payload digest (§8.3.9) is taken over the arguments the agent sent, which is what it signed; the tool receives the sanitized copy.
  • An executor that predates this field ignores it. An owner relying on it SHOULD require executors that state they enforce it (checksArguments).

8.4 Response

200 on a permitting disposition; 403 on a refusing one. Body is { success, message, data } where data is the verdict:

{ "decision": "allow", "reasonCode": "AUTHORIZED",
  "authorizationId": "…", "remaining": 350, "proofRef": "…",
  "expiresAt": null, "escalationId": null,
  "envelopeId": "env:…", "envelopeHash": "sha256:…" }

proofRef is the ledger anchor of the hold. An implementation MAY anchor after answering, so that the ledger round trip is not part of the request; proofRef is then null in the response, and the anchor is recorded on the hold when it lands. The same applies to proofRef on capture. A hold refused at the cap check is not anchored. An implementation that anchors after answering MUST NOT lose an anchor it owes.

8.5 Order of checks (normative)

A conformant evaluation MUST apply these in order and MUST stop at the first that fails, returning its reason code — with one exception: an escalate at steps 11–12 is not a failure that stops evaluation (see Escalation never outranks a hard block below).

# Check On failure
1 Mandate exists for (agentDid, action) MANDATE_REVOKED (every mandate for the action was revoked, §6.2.6) · NO_PERMISSION_FOR_ACTION (the agent holds mandates for other actions) · NO_MANDATE (it holds none)
2 Principal authority in force (§4.4.1); verification is reported, not required, on testnet PRINCIPAL_UNVERIFIED
3 Compliance with every enforced Standard STANDARD_NONCOMPLIANT
4 Agent exists; key proven AGENT_NOT_FOUND · AGENT_KEY_UNVERIFIED
5 Signature valid over §8.3; then a present context signature (§8.3.13) SIGNATURE_INVALID · CONTEXT_SIGNATURE_INVALID
6 Not contained AGENT_SUSPENDED · AGENT_QUARANTINED · AGENT_DECOMMISSIONED
7 Operating-mode block tier MODE_READ_ONLY
8 Freshness, then nonce consumed atomically; then the effective jurisdiction resolved (§8.3.12) REQUEST_EXPIRED · REPLAY_DETECTED · JURISDICTION_MISMATCH
9 Rate limit, if enabled RATE_LIMIT_EXCEEDED
10 Circuit breakers, if enabled CIRCUIT_BREAKER_OPEN
11 Standard rule molecules the firing molecule's code
12 SOP molecules; then, if an enforced Standard or SOP rule judges jurisdiction-not-allowed and there is no effective jurisdiction the firing molecule's code · JURISDICTION_REQUIRED
13 Mandate evaluation (§5), the allowed-jurisdictions term on the effective jurisdiction e.g. SPEND_LIMIT_EXCEEDED · JURISDICTION_REQUIRED · JURISDICTION_NOT_ALLOWED; success AUTHORIZED
14 Fold in OBSERVE (§8.6.2) allow → observe
15 Operating-mode escalate floor; reassessment floor; spend-anomaly floor (graph-backed, opt-in); runtime assurance floor (opt-in) MODE_* · REASSESSMENT_PENDING · SPEND_PATTERN_ANOMALY · AGENT_NOT_CERTIFIED · ASSURANCE_STALE
16 Atomic spend reservation CAP_EXCEEDED

Notes on the ordering, each of which is a security property rather than an accident:

  • Steps 1–3 precede signature verification because they are questions about the policy, not the requester.
  • Nonce consumption (8) MUST follow signature verification (5), or a forged request could burn a victim's nonce.
  • Containment (6) precedes everything action-specific, so a contained agent cannot reach evaluation at all.
  • The mode escalate floor (15) follows deterministic evaluation, so a specific rule denial is reported instead of a generic mode reason.
  • Escalation never outranks a hard block. A Standard or SOP molecule that escalates (step 11 or 12) — including CONTEXT_UNVERIFIABLE, a rule that could not get a trustworthy value for a field it needs, such as a missing riskLevel (§6.4.3) — MUST NOT end evaluation. The escalate is carried; the remaining rule stages and the mandate evaluation (13) still run, and any block, suspend or quarantine among them (an SOP cap, the mandate's per-transaction or cumulative cap, an action the mandate never granted) is the verdict. The carried escalate is the verdict only when everything after it would otherwise permit (or itself escalates), and it keeps its own reason code. Escalation can therefore only lift a would-be permit to human review, never downgrade a block: otherwise an agent could turn any hard cap into a review by omitting a field. A molecule's own block still stops evaluation at its stage, as before.
  • Jurisdiction is one resolved value, judged in three places. After the signature (5) — which covers a top-level jurisdiction through the v2 message (§8.3.12), a malformed value being MALFORMED_REQUEST — the gate resolves the request's effective jurisdiction, in this order: (a) the country the mandate's owner registered for the signed merchant in the payee directory, when it has one — authoritative; (b) else the signed jurisdiction, upper-cased; (c) else none. The itinerary's jurisdiction / mm:jurisdiction is removed before any rule or the mandate sees the context, so it can neither satisfy nor fail a check. Three refusals follow, all hard blocks that outrank a carried escalate:
    • JURISDICTION_MISMATCH (step 8, before any rule runs): a signed value that differs from the registered country, or a registry naming two countries for one payee. It applies whether or not anything restricts jurisdictions.
    • JURISDICTION_REQUIRED: no effective jurisdiction while one is needed — the mandate's allowed-jurisdictions term (step 13), or an enforced (non-observe) Standard or SOP rule that judges jurisdiction-not-allowed (end of step 12; the atom itself does not fire on a missing value, so without this the rule would pass silently).
    • JURISDICTION_NOT_ALLOWED (step 13): an effective jurisdiction outside the mandate's list. (An effective jurisdiction outside an SOP/Standard allow-list fires the molecule, with that molecule's own disposition and code.)
  • An approval cannot exceed the hard caps either (§10). Approving an escalation, or a reviewer's MODIFY that re-enters the gate, re-runs the deterministic layers on the request as it stands at the moment of the decision, with every input the live gate has — the mandate (1: still active and still the agent's current one for the action), the principal (2), Standards compliance (3), the agent's key, containment and mode (4, 6, 7), the rate window (9) including the callCount a rule reads, circuit breakers (10), committed spend, counterparty trust, the Standard and SOP molecules (11, 12) and the mandate (13) with the signed operands, resource included. Only transport authentication (5, 8) is not repeated. Any block-class verdict refuses the approval with that reason code — the review stays pending, and the reviewer may still deny it — and only escalates are disregarded, being what the reviewer is approving. An input that cannot be reconstructed refuses the approval (APPROVAL_INPUT_UNAVAILABLE); it is never skipped. The atomic reservation (16) then still applies (CAP_EXCEEDED).
  • The floors of step 15 only ever lift a permit to escalate, never downgrade a stricter verdict. The spend-anomaly and runtime-assurance floors are opt-in.
  • Reservation (16) is last and atomic: it is the only step that mutates shared budget, and a lost race MUST return CAP_EXCEEDED.
  • Steps 9 and 10 are secondary controls (§9).

8.6 Dispositions

8.6.1. Six: allow · observe · block · escalate · suspend · quarantine.

8.6.2. observe permits the action and flags it for monitoring. It does not fail fast; it is collected across the rule and SOP layers and folded in most-restrictive-wins, lifting an otherwise-allow verdict.

8.6.3. allow and observe permit execution. Everything else MUST stop the action.

8.6.4. escalate is a hold, not a denial — the action waits for a human (§10). A caller MUST NOT treat it as permission, and an agent MUST NOT self-approve.

8.6.5. An unknown disposition MUST be treated as a block.

8.7 Spend cap and settlement

A permitting verdict for a value-bearing action creates a hold: a reservation against the mandate's cap, identified by the authorizationId. This section specifies the hold as the agent and the executing counterparty see it.

8.7.1. On a permitting verdict for a value-bearing action the evaluator MUST atomically reserve the amount against the mandate's cap.

8.7.2. After the real-world charge settles the actor SHOULD capture with the actual amount. A hold that no counterparty has claimed expires and auto-voids.

8.7.3. A claimed hold is a commitment. Once a counterparty has claimed a hold for execution (POST /policy/mandate/authorize/{id}/effect/dispatching) the effect may already have happened, so the reservation MUST remain against the cap until it is settled or the claimer releases it. It MUST NOT lapse with the hold window, and only the claimer may lower or release it.

8.7.4. Settling a hold. POST .../capture { amountCharged, bookingRef?, settlementTxHash?, claimToken? } settles a hold; POST .../void { reason?, claimToken? } releases one. The authorization id is not a credential (the agent hands it to every Service it asks to execute), so:

  • Settling a claimed hold below its authorized amount, or voiding it, MUST come from its claimer: its signature (8.7.6) or, for an anonymous claim, the claim token that claim returned (data.claimToken). Settling at the full authorized amount needs no token.
  • Settling or voiding an unclaimed hold MUST come from the hold's own agent, with agentProof: { agentDid, nonce, issuedAt, signature } — its signature over MAGP-SETTLE-v1|verb|agentDid|authorizationId|nonce|issuedAt|...fields (verb capture or void; fields as in 8.7.6; each escaped as in 8.3.1), fresh and single-use like an authorize request — or from a counterparty signing as itself (8.7.6) that the owner's registry (8.7.10) would let claim it. An anonymous settlement of an unclaimed hold MUST be refused.

8.7.5. A claim is single-use and MUST be arbitrated atomically: of any number of simultaneous claimants exactly one succeeds. Every other claim of that authorization MUST be refused AUTHORIZATION_ALREADY_CLAIMED (HTTP 409) and carry no claim token. EFFECT_TRANSITION_CONTENDED means the claim did not land and the hold is still claimable; the claimant SHOULD retry it once. The hold window is enforced by the claim itself.

8.7.6. Counterparty identity. A claim token is a bearer secret: it proves "I made the claim", not who the claimer is. A counterparty that holds a key SHOULD instead sign its settlement calls with a did:key or did:hedera identity it controls. The signed message is MAGP-SERVICE-v1|action|authorizationId|field…|nonce|issuedAt, where action is one of claim, dispatched, unknown, capture, void, refund, reconcile, and the fields are those of the call:

action fields
claim the Idempotency-Key (8.7.7) when one is sent, then payload=<digest> (8.7.11) when a digest is stated
capture amount, bookingRef, settlementTxHash
unknown, void reason
refund amount (the empty string for a full refund), reason
reconcile none

Each field is escaped for \ and |. The signature is Ed25519, carried in the headers x-magp-service-did, x-magp-service-nonce, x-magp-service-issued-at and x-magp-service-signature.

  • The verifier MUST check the signature against the key the DID commits to, reject a stale or future issuedAt, and consume the nonce once. An identity that fails any check MUST be refused with a COUNTERPARTY_* code; it MUST NOT fall back to anonymous handling.
  • The identity that signs the claim is the claimer. Every later call that lowers or releases the hold MUST be signed by the same identity (COUNTERPARTY_MISMATCH, HTTP 403, otherwise). No claim token is issued to an authenticated claimer.
  • An implementation MAY require an authenticated claimer for every claim (COUNTERPARTY_AUTH_REQUIRED).

Refund. POST .../refund { amount?, reason?, claimToken? } records a reversal of a captured hold: no money moves, but the cumulative cap counts less. It MUST be accepted only from the hold's claimer (its refund signature, or its claim token), the mandate's owner, or a platform operator (COUNTERPARTY_MISMATCH otherwise).

8.7.7. Idempotent claim. A claimant whose claim response was lost MAY retry with the same Idempotency-Key header: 32–128 characters of [A-Za-z0-9._:~-] with at least 128 bits of randomness, chosen per claim call. A malformed key, or one that contains the authorization id, is refused IDEMPOTENCY_KEY_INVALID (400). A retry by the same identity while the effect is still dispatching is answered 200 with the original grant and replayed: true; once the effect has moved on it is an ordinary AUTHORIZATION_ALREADY_CLAIMED. A gateway SHOULD send the authorization id to the upstream as its Idempotency-Key, and MUST NOT forward one the agent supplied.

8.7.8. Outcome lookup. GET /policy/mandate/authorize/{id}/effect (public, keyed by the authorization id) answers what became of an authorization:

outcome meaning nothingExecuted retrySafe
not_started authorized, unclaimed, inside its hold window true false
expired unclaimed, past its window; budget already returned true true
not_executed voided before a claim, cancelled by its claimer, or failed clean true true
in_flight claimed (dispatching / dispatched) false false
settled settled; the spend is committed false false
unknown ambiguous or under reconciliation false false
reversing / reversed / reversal_failed settled, then compensated false false

retrySafe is the only safe basis for issuing a fresh authorization for the same intent. To retry a not_started hold, void it first and read not_executed. An unknown or in_flight outcome MUST NOT be retried blindly. The response also carries claimed, claimedAt, currency, authorizedAmount, settledAmount and settlementEvidence (8.7.9); never a claim token or the claimer's identity.

Capture and void are not idempotent the way a claim is. A capture of a hold that is no longer held answers 409 NOT_HELD; a void of one answers 200 { voided: false, reasonCode: "NOT_HELD" }. A caller that retries either after a timeout or a 5xx MUST read the outcome first: the first call has usually landed.

A refused capture, void or refund is a 4xx whose message is the bare reason code and whose data is { captured | voided | refunded: false, authorizationId, reasonCode, detail }. A client MUST branch on reasonCode, never on detail. The same code has the same HTTP status on every call. The codes are listed in Appendix A.

8.7.9. Authorized and settled amounts. The authorized amount is recorded once and never overwritten; the settled amount is what the cumulative cap then counts, and MUST NOT exceed it (AMOUNT_EXCEEDS_AUTHORIZED). Every settlement states what it rests on as settlementEvidence: unattested, unclaimed_lowered, counterparty_attested, independently_confirmed, operator_resolved or reconciled_at_authorized. Where the deployment observes the remote system, a claimer's lowered settlement or release MUST be supported by that observation, and is refused otherwise (SETTLEMENT_NOT_CONFIRMED, SETTLEMENT_AMOUNT_MISMATCH, SETTLEMENT_NOT_FOUND); the claimer can still settle in full or mark the effect unknown.

8.7.10. Trusted counterparties. An owner MAY register the counterparty DIDs that may claim their holds, each optionally limited to named merchants. An owner with any registered counterparty — and every hold of a mainnet agent — accepts only a claim signed by an active, registered identity (COUNTERPARTY_AUTH_REQUIRED, COUNTERPARTY_NOT_REGISTERED, COUNTERPARTY_NOT_ALLOWED_FOR_MERCHANT, HTTP 403). A refused claim leaves the hold claimable.

An entry's purpose is claim or report (a Service registered only to report what it did, 16.4). A report entry is never a claimant, except for an approval claim (8.7.18) of a zero-amount hold of an agent it was registered for.

A registration carries the service's own acceptance: the service signs MAGP-COUNTERPARTY-ACCEPT-v1|<registrant principal DID>|<service DID>|<purpose>|<nonce>|<expiresAt> (fields escaped as in 8.3) over a challenge the owner obtained, with the key its DID embeds. A service MUST sign only a well-formed acceptance naming its own DID — never free text (16.2).

8.7.11. Payload comparison at the claim. A hold whose authorization carried a payload binding (8.3.9) is claimed with the digest of exactly what the claimant is about to execute, in the x-magp-payload-digest header and, for an authenticated claim, as the signed field payload=<digest>. The issuer compares it with the digest it stored at authorize, before recording the claim:

claim hold result
digest equal bound granted; the grant reports payloadDigest
digest different bound PAYLOAD_DIGEST_MISMATCH (403)
no digest bound PAYLOAD_DIGEST_REQUIRED (403)
digest unbound PAYLOAD_NOT_BOUND_AT_AUTHORIZE (403)
— unbound, mandate requires binding PAYLOAD_BINDING_REQUIRED (409)
malformed digest — PAYLOAD_DIGEST_INVALID (400)

Every refusal leaves the hold unclaimed. The comparison is against the digest the issuer stored, never only against the signed request the executor was handed.

The executor SHOULD also compare the digest of what it will execute with the signed request before it claims. It MUST refuse, rather than skip the check for, a payload it cannot digest (PAYLOAD_NOT_CANONICALIZABLE), and MUST NOT forward a URL query string, path parameter or fragment the signature does not cover (QUERY_NOT_BOUND) unless its operator listed that key as unbound. A JSON body that two readers could read differently — a repeated key, a number a double cannot hold exactly, invalid UTF-8, a byte-order mark — MUST be refused, not digested.

8.7.12. Who may push a claimed hold along. Marking an outcome unknown (POST .../effect/unknown) and asking for reconciliation (POST .../effect/reconcile) are accepted only from the claimer, the mandate's owner, or an operator (COUNTERPARTY_MISMATCH, RECONCILE_NOT_PERMITTED).

8.7.13. Owner-forced reconciliation. An owner MAY force a claimed hold into reconciliation (POST .../effect/force-reconcile). The claimer then loses every power over it, including capture and void (HOLD_UNDER_RECONCILIATION, 409), and the hold settles only on what reconciliation establishes.

8.7.14. Payee directory. An owner MAY register the accounts each merchant is paid into. A lowered settlement for a merchant with a registered account MUST name one of them as payTo (PAYEE_NOT_REGISTERED, PAYEE_NOT_VERIFIED).

8.7.15. Revocation and holds. Revoking a mandate — directly, through its delegation chain (5.4.9), by reissue or by decommissioning the agent — releases every hold under it that no counterparty has claimed, and leaves every claimed hold to settle by the claimed-hold rules. A claim or capture of an unclaimed hold under an inactive mandate, or under a delegated mandate with an inactive ancestor, is refused MANDATE_REVOKED or DELEGATION_CHAIN_BROKEN (409).

8.7.16. Rules changed while a hold waits. A change to the agent's SOPs or bound Standards applies to holds that are not yet claimed. Before an unclaimed hold is claimed or captured, the gate MUST evaluate the current rules over the context the hold was granted on. If they no longer permit it, the claim is refused AUTHORIZATION_RULES_CHANGED (409) and the hold voided; if they cannot be evaluated, it is refused CONTROL_UNAVAILABLE (409) and the hold left unclaimed. A hold minted by a person's approval is not refused for an escalate. A claimed hold is never re-judged.

8.7.17. Containment and holds. Containment (7.1) stops what the agent was already allowed to do. A claim or capture of an unclaimed hold of a contained agent is refused with the containment code — AGENT_SUSPENDED, AGENT_QUARANTINED or AGENT_DECOMMISSIONED (409) — and the hold voided, whoever offers to execute it. Reinstating the agent does not revive the hold.

8.7.18. Executing an approved escalation. An executor that re-evaluates the agent's policy reaches escalate again for a request a person has already approved. When its own verdict is escalate and the request carries an authorizationId, the executor MAY claim that hold with requireHumanApproval: true. The issuer grants such a claim only for a hold minted by a person's approval, and otherwise refuses ESCALATION_NOT_APPROVED (409). Every grant carries approvedByHuman; the executor MUST permit only on approvedByHuman: true, and only over an integrity-protected channel to the issuer. Only an escalate is lifted this way; a block, suspend or quarantine is never overridden. Every other claim check still applies. A modified approval carries no payload digest, so it is refused PAYLOAD_BINDING_REQUIRED until the agent binds the payload of what will run (8.3.11).

8.7.19. Claim expectations. A claim SHOULD state what the executor is about to execute as expect: { agentDid, action, amount, currency, merchant }. The issuer MUST compare these with the hold before granting and refuse a difference — AUTHORIZATION_AGENT_MISMATCH, AUTHORIZATION_ACTION_MISMATCH, AUTHORIZATION_AMOUNT_MISMATCH, AUTHORIZATION_CURRENCY_MISMATCH or AUTHORIZATION_MERCHANT_MISMATCH (403) — leaving the hold unclaimed. A malformed expect is refused CLAIM_EXPECTATION_INVALID (400); an unknown key is ignored. An executor that still finds a mismatch after a grant MUST release the claim before refusing.

8.7.20. Approved-context binding. A hold a person approved is for the request that person reviewed, including its context. The approved context's digest is sha256: + hex SHA-256 of the RFC 8785 canonical JSON of { "MAGP-APPROVED-CONTEXT-v1": context } (an absent context is {}; for a MODIFY that changed the itinerary, the reviewer's itinerary), pinned by docs/protocol/resume-binding-vectors.json (contextVectors). A claim MUST state the digest of the context it is about to execute as expect.contextDigest. For a person-approved hold the issuer refuses a difference AUTHORIZATION_CONTEXT_MISMATCH (403) and a missing digest AUTHORIZATION_CONTEXT_REQUIRED (403). The grant names the digest it compared (contextDigest); the executor MUST release the claim and refuse when it differs from its own. An executor that runs a body other than the itinerary SHOULD require payload binding (8.3.9).

8.7.21. In-process resume claim. An SDK that runs an approved escalation's tool in its own process takes the approval's one resume at the issuer before it runs: POST /policy/escalations/{escalationId}/resume-claim with { agentDid, nonce, issuedAt, requestDigest, contextDigest, signature }. requestDigest is the resume binding digest (MAGP-RESUME-BIND-v1) and contextDigest the approved-context digest (8.7.20), both computed from the SDK's own arguments. The agent signs MAGP-RESUME-CLAIM-v2|escalationId|authorizationId|agentDid|requestDigest|contextDigest|nonce|issuedAt (each field escaped as in 8.3), pinned by docs/protocol/resume-binding-vectors.json (claimVectors). The issuer refuses a request difference ESCALATION_REQUEST_MISMATCH (403) and a context difference AUTHORIZATION_CONTEXT_MISMATCH (403). Exactly one valid claim is granted; others are refused AUTHORIZATION_IN_USE (409) while the resume may still be running, and AUTHORIZATION_VOIDED or AUTHORIZATION_ALREADY_USED (409) once its hold is voided or settled. An issuer that requires v2 says so on the escalation's status (resumeClaimVersion: 2); an SDK MUST sign v2 when it is present.


9. Secondary controls

Controls that protect the system rather than express authority. Both are optional. A deployment chooses whether they fail open or closed when they cannot run; failing closed, a rate counter or breaker lookup that errors blocks with CONTROL_UNAVAILABLE, and the spend-anomaly and assurance floors escalate with it. A request MUST NOT be able to cause that error itself: a scope value that cannot be looked up is refused MALFORMED_REQUEST.

9.1 Rate limits

9.1.1. Per agent, max calls per rolling windowSec. max = 0 disables it.

9.1.2. The running count is server-derived and MUST be injected into rule context signed-last, so a rule-authored rate atom evaluates the same number the built-in ceiling does and an unsigned itinerary cannot shadow it.

9.2 Circuit breakers

9.2.1. Scoped to agent, tenant, tool or counterparty. An open breaker denies with CIRCUIT_BREAKER_OPEN.


10. Escalation resolution

10.1. escalate parks a pending record. No budget is held.

10.2. GET /policy/escalations/{id}/status is public and keyed by the escalation id, which is itself the capability. An agent holds no user token and needs none to ask about its own held action. It returns status, reasonCode, authorizationId, expiresAt, approvals, required, and the digests of 8.7.20; it names no principal and no budget. With an authorization it also reports executed, executedAt and outcome (as GET /policy/mandate/authorize/{id}/effect reports it). status stays approved after the action ran.

10.2a. GET /policy/mandate/{ref}/status is public as a registry read (4.4.2), and minimized: an unauthenticated caller gets policyId, agentDid, status, principalVerified, principalTierReason, allowedJurisdictions, vcHash, mandateStoreRef, anchorRef and expiresAt. The principal's DID and the budget (cap, spent, remaining, byCurrency) are returned only to the mandate's owner. The response says which it is (view: "public" | "owner").

10.3. Approval requires M of N distinct approvers, configurable per agent, defaulting to

  1. An approver MAY be required to hold a named role and a minimum authority level.

10.4. On approval the gate re-runs the deterministic checks and mints an authorizationId. Approval does not bypass the budget, nor any hard block: containment, an action the mandate does not grant, a Standard or SOP block (e.g. an SOP per-transaction cap) and the mandate's per-transaction and cumulative caps are all re-evaluated on the escalated request, and any of them refuses the approval with its own reason code (the cumulative cap as CAP_EXCEEDED); the escalation stays pending and may still be denied. Escalates are disregarded — they are what is being approved (§8.5). The minted hold starts its effect lifecycle at authorized, so it is claimable like any other. A MODIFY re-enters the gate under the same precedence.

10.4.1. The re-evaluation uses the gate's inputs as of the approval, never those of the moment the review opened, and every one the live gate uses except transport authentication (signature, freshness, nonce — satisfied by the agent's original request):

  • The mandate. It MUST still be active, and for a delegated mandate so must every mandate above it (MANDATE_REVOKED otherwise, DELEGATION_CHAIN_BROKEN for a chain that cannot be walked, 5.4.10), and still the agent's current mandate for the action (MANDATE_SUPERSEDED if a newer one has been issued). Reviews do not migrate to a reissued mandate: a review raised under the old one cannot be approved, and the agent asks again under the new one (10.4.2).
  • Principal, compliance and the agent — steps 2, 3, 4, 6 and 7 of §8.5 as they stand now.
  • The rate window (§9.1) and circuit breakers (§9.2).
  • Committed spend and counterparty trust, applied as server-derived fields (§6.3), so the review's stored itinerary cannot supply them.
  • The signed operands, resource included; the unsigned itinerary never stands in for them.
  • The jurisdiction (§8.3.12): the stored signed value, resolved again against the payee registry as of the decision. A reviewer cannot change it, and a MODIFY cannot move the action to another jurisdiction.

An input that cannot be reconstructed refuses the approval with APPROVAL_INPUT_UNAVAILABLE; the review stays pending. A MODIFY receives the same inputs; its block closes the review as modified.

10.4.2. Revoking a mandate closes its pending reviews. Every review still pending under a revoked mandate becomes denied, with the reason MANDATE_REVOKED — or MANDATE_SUPERSEDED when the mandate was reissued — and an evidence event is recorded. The status route reports that code as the review's reasonCode, so a polling agent learns it must ask again. A closed review can never be approved (ALREADY_DENIED), and no hold is minted on a revoked mandate.

10.5. An unresolved escalation expires — ESCALATION_EXPIRED. The hold an approval mints carries its own window from the moment of approval.

10.6. Callers MUST poll to resolution and act only on an approved status carrying an authorizationId.


11. Payment execution binding (x402)

11.1. Authorize before pay. Payment MUST be bound to an authorizationId issued by a permitting verdict.

11.2. The binding is amount-exact and single-use. A payment whose amount differs from the authorized amount MUST be refused with AMOUNT_MISMATCH; a reused authorization MUST be refused.

11.3. Settlement is reconciled by capture, carrying the settlement reference.

11.4. No custody. The protocol never holds funds.


12. Effects and reconciliation

12.1. A permitted action has a real-world effect whose outcome may not be known synchronously. Effect state transitions are recorded on a hash-chained ledger: each transition commits to the digest of the previous one plus its own canonical content.

12.2. A verifier MUST reject an invalid transition, a sequence gap, a broken chain link or a digest mismatch — INVALID_EFFECT_TRANSITION, SEQUENCE_GAP, CHAIN_LINK_BROKEN, DIGEST_MISMATCH.

12.3. An effect whose outcome is genuinely unknown is recorded as such rather than guessed, and resolved by reconciliation.


13. Evidence and anchoring

13.1. Every decision MUST produce an evidence record.

13.2. Records MAY be anchored individually or batched. In batch mode records are accumulated, hashed into a Merkle tree, and a single root is anchored.

13.3. GET /magp/evidence/{id}/proof returns an inclusion proof: the leaf, the path, and the anchored root.

13.4. A verifier MUST be able to check inclusion offline, given only the leaf, the proof and the anchored root. Verification MUST NOT require the platform.

13.5. Leaf stability. The set of fields in a leaf is part of the protocol. Adding one changes every digest and invalidates previously anchored proofs; a change to leaf composition is therefore a breaking change (§18).

13.6. Execution Receipt. A second, separate signed-proof artifact, issued after a value-bearing action settles: POST .../capture best-effort issues one binding what was actually charged back to the Action Passport (§8) the action was authorized against (actionPassportId, authorizedPayloadDigest), alongside a digest of what was actually executed (executedPayloadDigest) and whether the two are within the authorized bound (payloadMatch). Unlike the Merkle-anchored evidence above, a receipt is a standalone Ed25519 signature over a canonical (sorted-key JSON) message — verifiable with no anchored root, no Merkle path, just the issuer's published public key (GET /action-passport/pubkey).

  • GET /action-passport/receipt/verify/{authorizationId} (public) answers integrityValid (the signature checks out), payloadMatch, outcomeStatus, and settlementEvidence (below) for any relying party, with no authentication and no state about who is asking.
  • receiptVersion. The receipt's field set is versioned, and a verifier reconstructs the exact message each version signed, never adding a later version's fields. Version 1 signs executionReceiptId, actionPassportId, authorizationId, agentDid, authorizedPayloadDigest, executedPayloadDigest, payloadMatch, outcomeStatus and issuedAt; version 2 adds settlementEvidence (8.7.9).

14. Decision records and the governance envelope

14.1. Alongside the anchored leaf, an implementation SHOULD record an off-leaf decision record: per-control results, stage timestamps, and the names of context fields evaluated.

14.2. Context field names are recorded; values MUST NOT be. A conformance assessment needs to distinguish "the control held" from "the situation never arose" — a spend-cap clause that never saw an amount was not tested, and scoring it as passed would be a false assurance. The values themselves are the tenant's.

14.3. The governance envelope is a canonical view over the request, carrying an envelopeId and an integrity hash so a caller can bind a decision to the exact action submitted.

14.4. Decision records are best-effort and MUST NOT affect the verdict.

14.5. Where the anchored leaf carries a commitment to the decision record (a single hash covering both, rather than anchoring twice), and the request carried a payload binding (§8.3.9) that has been VERIFIED — never one merely claimed — the payload digest SHOULD be folded into that same commitment, so a verifier holding the anchored digest can also prove which payload the decision was bound to. A new field folded into an existing digest MUST be additive: omitted from the hashed content entirely when absent (never written as an explicit null), so a record with nothing to add digests identically to one computed before the field existed. Anything covered by this digest that has not yet been proven true — e.g. PAYLOAD_BINDING_INVALID, where the very point of the code is that the claimed digest was never shown to be the agent's — MUST NOT be included.


15. Privacy-preserving verification

Optional. Where a counterparty must verify a property without learning the underlying value.

15.1. Range proofs. An amount may be proven within a mandate's cap without revealing it (ZK_AMOUNT_PROOF_INVALID, ZK_JURISDICTION_PROOF_INVALID, ZK_POLICY_OK).

15.1.1. Who may ask, and against which terms. A private-authorize request (POST /magp/private-authorize) MUST be signed by the agent's own DID key, with the same key resolution (including rotated and promoted DIDs), single-use nonce and freshness window as a cleartext authorization (§8.3). The signed message is MAGP-PRIVATE-AUTHORIZE-v1| followed by the hex SHA-256 of the canonical JSON (RFC 8785-style) of agentDid, action, nonce, issuedAt, currency, both commitments, both proofs, transactionCommitment and sessionBindingDigest (absent members as null). Its own domain means a signature made for it never verifies as an authorize signature, or the reverse. transactionCommitment is REQUIRED: a request without one is refused (ZK_TRANSACTION_COMMITMENT_REQUIRED, HTTP 400) before anything else is checked, so every permitting verdict carries a capability bound to the transaction it authorizes (§15.3).

15.1.2. The cap and the jurisdiction allow-list MUST be read from the agent's active mandate for the action, never from the request. The cap is the permission's per-transaction bound (mm:payAmount lteq, else mm:cumulativeSpend), floored to whole units of its currency; the allow-list is an mm:jurisdiction isAnyOf constraint, in its stored order — the allowed-jurisdictions term of §5.2.3. The blind path proves the agent's committed jurisdiction against it and does not consult the payee registry. A request that carries its own cap or allowedJurisdictions MUST be refused (ZK_CALLER_POLICY_REJECTED), not silently re-interpreted. A range proof built against any other cap is refused (ZK_CAP_MISMATCH); a term the mandate carries whose proof is missing is refused (ZK_AMOUNT_PROOF_REQUIRED, ZK_JURISDICTION_PROOF_REQUIRED). A mandate with no jurisdiction term has nothing to prove about jurisdiction — a membership proof is then not evaluated, and the response and capability say so; a mandate with neither term, or with one in a form set membership or a range proof cannot express, is refused (ZK_MANDATE_TERMS_UNSUPPORTED).

15.1.3. A contained agent, an inactive (never issued, or revoked) or expired mandate, a principal whose authority does not hold, and an unmet enforced Standard are refused with the codes a cleartext authorization uses. Because the amount is hidden and nothing is held, an agent whose operating mode routes value to a human is refused (MODE_READ_ONLY, ZK_HUMAN_REVIEW_UNAVAILABLE), as is an action whose mandate requires payload binding (PAYLOAD_BINDING_REQUIRED) — a transaction commitment is not a payload binding.

15.1.4. What a private-authorize verdict does not establish, and MUST state: it reserves no hold, so it does not draw down or check the cumulative budget; it does not evaluate merchant, resource or other constraints, prohibitions, Standards or SOP rules. The response lists these under notEvaluated. A counterparty that relies on it still has to enforce them.

15.2. Selective disclosure. BBS presentations prove selected credential attributes — for example that assurance meets a threshold — without disclosing the rest (BBS_PROOF_INVALID, BBS_KYC_LEVEL_TOO_LOW, BBS_PRESENTATION_OK).

15.3. Capability tokens. A permitting verdict MAY be returned as a signed capability bound to a transaction commitment and a session, verifiable offline. Authorizing transaction A and executing transaction B MUST fail with COMMITMENT_MISMATCH; a capability used outside its session fails with SESSION_MISMATCH; an expired one with CAPABILITY_EXPIRED.

15.3.1. A capability minted by a private authorization (payload v: 2) also names its subject: agentDid, mandateId, action, currency, network, and the mandate terms proven (cap, jurisdictions; null = the mandate had no such term). A verifier SHOULD check at least that agentDid is the agent it authenticated; presented by another agent the capability fails with CAPABILITY_AGENT_MISMATCH, and against a different expected mandate, action or currency with CAPABILITY_SCOPE_MISMATCH. A verifier that requires a subject field MUST fail a capability that lacks it (a v: 1 capability) rather than accept it.


16. Discovery and channels

16.1. HTTPS is the baseline transport. A DID document's serviceEndpoint locates a peer.

16.2. Peers MUST mutually verify DIDs before transacting. A counterparty SHOULD independently re-evaluate a caller's authority rather than trusting that the caller's own guard ran (§3). Mutual verification has each peer sign the OTHER peer's nonce with its own key, so a peer MUST sign only a nonce that is a plain random token (16–128 characters of A–Z a–z 0–9 _ -) and refuse anything else: a nonce that is free text makes the signer an oracle — a malicious peer sends a canonical authorize message (8.3), or a MAGP-SERVICE-v1 claim, as its nonce and obtains a valid signature on it. Every MAGP message that authorizes anything contains |, which a token cannot.

16.3. Whom a Service acts for. A counterparty's re-evaluation (16.2) judges the caller by the caller's own policy — written by the caller's owner. It answers "may this agent do this?", never "is this an agent I act for?". A Service that executes with one owner's credentials (an agent's own tool gateway, an executor holding one owner's API key) MUST therefore also establish that the owner of those credentials admitted the caller:

  • Admission. The Service MUST be configured with the agents it acts for, and MUST refuse a validly signed request from any other agent with AGENT_NOT_ADMITTED — after verifying the request signature, and before fetching the caller's policy or claiming any authorization (8.7.6). An unconfigured admission list is a startup error, never "admit everyone"; a malformed one likewise.
  • Owner binding. The Service MUST be bound to the principal that owns its credentials (gatewayOwnerPrincipal), a startup error when absent (GATEWAY_OWNER_UNBOUND). The policy bundle names the agent's owning principal (ownerPrincipal, signed with the rest); an admitted agent whose bundle names any other owner, or none, MUST be refused GATEWAY_OWNER_MISMATCH before any claim. An agent of another owner can be admitted only by a bilateral delegation the gateway owner grants — not by its own mandate, and not by being listed.
  • Credential profiles. Admission to the Service is not access to every credential it holds: a route, tool or skill MAY admit fewer agents, and an admitted agent outside it MUST be refused CREDENTIAL_PROFILE_NOT_PERMITTED, also before its policy is fetched or anything claimed.

A Service that holds no one owner's credentials — a public tool, or one that resolves each caller's own credential — MAY admit every governed agent, but only by saying so explicitly ('any'), never by omission, and it then takes no owner binding.

16.4. Reporting what a Service did. A Service that executes without claiming an authorization, or refuses a request itself, SHOULD report each governed request it answers to POST /policy/gateway-reports: { servedAgentDid, outcome: "executed" | "refused", reasonCode, httpStatus, request, reportId, occurrences }.

  • request is the agent's signed request as received (8.3). The authorization it named is presentedAuthorizationId; authorizationId is set only when the Service claimed that authorization for this request.
  • reportId (8–64 characters of [A-Za-z0-9_-]) is the Service's own id for the report, kept across retries; occurrences is how many identical refusals the report stands for (always 1 for executed, REPORT_OCCURRENCES_INVALID otherwise).
  • The report is signed as the Service (MAGP-SERVICE-v1, action report, the request's nonce in the authorization-id slot, fields servedAgentDid | callerAgentDid | action | outcome | reasonCode | httpStatus | reportId | occurrences).
  • The issuer records it only when the signature verifies and the served agent's owner registered the signing DID as a counterparty of either purpose (8.7.10), and files it under that owner. An executed report MUST carry a request whose agent signature verifies (REPORT_CALLER_UNVERIFIED). A resent report is answered ALREADY_RECORDED; the issuer MAY limit reports per Service (REPORT_RATE_LIMITED, 429).

Reports are visibility only: they never change a decision, a hold or a cap.


17. Security considerations

17.1. Fail closed. An unreachable gate means block. A guard that cannot verify a bundle for a value-bearing action means block. Only the secondary controls of §9 fail open, only on internal error, and only where the deployment has not configured them to fail closed.

17.2. Signed-last precedence. Server-derived, gateway-derived and signed operands MUST be applied over the unsigned itinerary, so a client-supplied key cannot shadow a signed amount or a server-counted rate (§6.3.2).

17.3. Nonce discipline. Single-use, consumed atomically, only after signature verification.

17.4. The unsigned context is an assertion. tool, jurisdiction and riskLevel are claims by the agent (resource is signed, §8.3; a jurisdiction the gate enforces is the signed top-level one, §8.3.12, never the itinerary's). A signature attributes a claim; it does not verify it, and the counterparty re-check reads the same claim — so a re-check does not, by itself, catch a lie. The defence is §6.3: every field carries a provenance, an owner-set riskTier and a gateway-derived value are floors the agent cannot lower, a rule can require a trusted source with requireProvenance, and a missing or unrecognised risk fails closed. The optional context signature (§8.3.13) stops a relay rewriting the claim; it does not make it true. Adding these fields to the signed message would break every deployed client and every anchored proof, and would still only attribute them; it is not proposed.

17.5. Escalation is not permission. See §8.6.4.

17.6. Delegation cannot widen. See §5.4. An implementation that reverses the isNoneOf rule silently grants authority nobody issued.


18. Extensibility and versioning

18.1. New operands and atoms extend the protocol. New operators do not.

18.2. An unknown reason code MUST be treated as a block (§8.6.5). An unknown atom MUST NOT silently pass.

18.3. Additive fields on the request or verdict are minor changes. Changes to the canonical message (§8.3), to leaf composition (§13.5), or to the order of checks (§8.5) are breaking.

18.4. GET /magp/schema publishes the request, verdict and rule-pack JSON Schemas, the reason-code registry with SAFR aliases, the control namespaces and the atom catalog. It is the machine-readable companion to this document.


19. Conformance

An implementation is conformant if it:

  1. resolves agent keys from the DID (§4.3.1);
  2. rebuilds and verifies the canonical message exactly (§8.3);
  3. applies the order of checks in §8.5, stopping at the first failure;
  4. treats allow and observe as the only permitting dispositions (§8.6.3);
  5. never treats escalate as permission (§8.6.4);
  6. fails closed as §17.1 requires;
  7. enforces the narrowing invariant on any delegated mandate it accepts (§5.4);
  8. emits reason codes from Appendix A, or documents extensions;
  9. produces evidence verifiable offline (§13.4).

A guard need not implement the stateful steps (8, 9, 10, 16). It MUST defer them to the gate and MUST NOT report a permitting verdict on their behalf for a value-bearing action.


20. Reference endpoints

Endpoint Auth Purpose
POST /policy/mandate/authorize public The gate
POST /policy/mandate/authorize/{id}/capture public Reconcile a hold
POST /policy/mandate/authorize/{id}/void public Release a hold
GET /policy/escalations/{id}/status public Follow a held action
GET /policy/bundle/{did} public Signed policy bundle
GET /magp/policy/pubkey public Bundle verification key
GET /did/{did} public DID document
GET /magp/evidence/{id}/proof public Inclusion proof
GET /agent-identity/{ref}/policy-disclosure owner Open one part of an anchored policy commitment (6.2.8)
GET /magp/schema public Machine-readable protocol surface
GET /standards/atoms authenticated Atom catalog with required context

Appendix A — Reason codes

Generated from the reference implementation's registry, which is also published at GET /magp/schema. The SAFR §30 alias column maps each code to the canonical SAFR vocabulary where one exists; — marks a permit/lifecycle code or a MetaMynd-specific extension beyond the SAFR floor.

Permit / lifecycle (no SAFR deny alias)

Code Decision Meaning SAFR §30 alias
AUTHORIZED allow All checks passed. —
OBSERVED observe Permitted but flagged for monitoring (SAFR §11). —
STANDARD_OBSERVE observe A Standard molecule marked the action for monitoring. —
SOP_OBSERVE observe An SOP molecule marked the action for monitoring. —

Request validation

Code Decision Meaning SAFR §30 alias
MALFORMED_REQUEST block The request body failed schema validation (a missing or invalid field, e.g. a negative amount) before any policy evaluation ran. Returned in the same verdict shape as every other decision. —

Identity (SAFR §6)

Code Decision Meaning SAFR §30 alias
PRINCIPAL_UNVERIFIED block The authorizing principal's authority does not currently hold: a mainnet agent beyond the limits of its principal's plan (§4.4.1). Testnet and did:key agents are never refused with it; verification is reported for a counterparty's own risk policy. IDENTITY_UNVERIFIED
AGENT_NOT_FOUND block Unknown agent DID. IDENTITY_UNVERIFIED
AGENT_KEY_UNVERIFIED block BYOK key not yet proven. IDENTITY_UNVERIFIED
IDENTITY_ROTATED block The request is signed as a DID its agent no longer uses: the owner rotated the agent's key (Rotate key), which issued a new DID and moved the agent's mandates to it. The old DID and key stop working at once. IDENTITY_REVOKED
IDENTITY_KEY_MISMATCH block Registered public key does not match the key embedded in the agent's own self-certifying DID. IDENTITY_UNVERIFIED
MANDATE_REVOKED block The mandate has been revoked, or a mandate it was delegated from is inactive (§5.4.10). Refuses the action at authorize (§8.5 step 1; a guard reads revokedActions, §6.2.6), a pending review (§10.4.2), an approval, and a claim or capture of an unclaimed hold (§8.7.15). IDENTITY_REVOKED
DELEGATION_CHAIN_BROKEN block A delegated mandate whose chain cannot be walked to its root: a parent is missing, the chain cycles, or it is deeper than the bound (§5.4.10). Fails closed at authorize, at an approval, and on a claim or capture of an unclaimed hold. IDENTITY_REVOKED
DELEGATION_NARROWING_FAILED block A reissue of a delegated mandate whose changed terms would not narrow its delegator's current terms (§5.4.2–§5.4.5, §5.4.11). An owner's change is refused (409, nothing changes); an identity rotation revokes the delegated mandate instead. MANDATE_SCOPE_VIOLATION

Envelope integrity (SAFR §5)

Code Decision Meaning SAFR §30 alias
SIGNATURE_INVALID block Ed25519 verification failed. ENVELOPE_INTEGRITY_FAILURE
CLIENT_PROTOCOL_VERSION_UNSUPPORTED block The request verifies against an earlier canonical message shape (§8.3.7), not the current one: a valid key on an outdated client. Still blocked, but named so the client is upgraded rather than the key rotated. ENVELOPE_INTEGRITY_FAILURE
CONTEXT_SIGNATURE_INVALID block Optional envelope signature present (Tier 1 context-claim binding, SAFR §5) but does not verify against the GovernanceEnvelope hash — the context was altered after the agent signed it, or signed with the wrong key. ENVELOPE_INTEGRITY_FAILURE
PAYLOAD_BINDING_INVALID block A payload binding (§8.3.9, §8.3.11) is present but not valid: a digest without a signature or the reverse, a malformed digest, or a signature that does not verify under the agent's key. A digest nobody signed binds nothing. ENVELOPE_INTEGRITY_FAILURE
PAYLOAD_BINDING_REQUIRED block The mandate's owner requires this action's payload to be bound (requirePayloadBinding, §8.3.9) and none is: at authorize, the request carries no binding; at a claim, the hold carries no digest yet. Send the payload so the agent signs its digest, or bind the hold (§8.3.11) before claiming. ENVELOPE_INTEGRITY_FAILURE
QUERY_NOT_BOUND block Executor-side (§8.7.11): a governed request carries a URL query, path parameter or fragment that the agent's signature and payload binding do not cover. Refused before anything is claimed. ENVELOPE_INTEGRITY_FAILURE
REPLAY_DETECTED block Nonce already consumed. ENVELOPE_INTEGRITY_FAILURE
REQUEST_EXPIRED block issuedAt outside the freshness window. ENVELOPE_INVALID

Mandate (SAFR §7)

Code Decision Meaning SAFR §30 alias
NO_MANDATE block The agent holds no mandate at all, and none was ever revoked for this action (that is MANDATE_REVOKED). MANDATE_MISSING
MANDATE_EXPIRED block Outside mandate validity window. MANDATE_EXPIRED
MANDATE_NOT_YET_VALID block Before the mandate validity window. MANDATE_EXPIRED
NO_PERMISSION_FOR_ACTION block Action not granted by the mandate. ACTION_NOT_PERMITTED
PROHIBITED block A mandate prohibition fired. REGULATORY_PROHIBITION
CONSTRAINT_FAILED block A mandate constraint failed. MANDATE_SCOPE_VIOLATION
JURISDICTION_REQUIRED block The request has no effective jurisdiction (§8.3.12) while one is required: the mandate restricts jurisdictions for this action, or an enforced rule judges jurisdiction-not-allowed. An itinerary jurisdiction is ignored. A hard block: it outranks an escalate. EVIDENCE_MISSING
JURISDICTION_NOT_ALLOWED block The request's signed jurisdiction (§8.3.12) is not in the mandate's allowed-jurisdictions list for this action. MANDATE_SCOPE_VIOLATION
JURISDICTION_MISMATCH block The request's signed jurisdiction (§8.3.12) differs from the country the owner registered for the payee, or the owner's entries for that payee name more than one country. Judged at authorize and again at an approval. A hard block: it outranks an escalate. COUNTERPARTY_NOT_ALLOWED

Exposure / counterparty (SAFR §22/§23)

Code Decision Meaning SAFR §30 alias
SPEND_LIMIT_EXCEEDED block Mandate budget constraint failed. EXPOSURE_THRESHOLD_EXCEEDED
CAP_EXCEEDED block The mandate's spend cap, enforced atomically as the hold is written (§8.5 step 16): committed spend plus this amount is over the cap. Usually a concurrent authorize took the rest of the budget. Not retryable until spend is released. EXPOSURE_THRESHOLD_EXCEEDED
CURRENCY_NOT_ALLOWED block The request's currency is not one a cap is denominated in (a mandate spend constraint, or an SOP or rule-pack cap scoped to a currency), so the cap cannot judge it and refuses. Request in the cap's currency, or have the owner add it. MANDATE_SCOPE_VIOLATION
AMOUNT_NOT_DETERMINABLE block The gate could not establish how much value the action moves, so no cap could be applied to it (amount-unknown atom). EVIDENCE_MISSING
MERCHANT_NOT_ALLOWED block Merchant outside mandate. COUNTERPARTY_NOT_ALLOWED
COUNTERPARTY_NOT_ALLOWED block Counterparty outside mandate. COUNTERPARTY_NOT_ALLOWED
ROUTE_NOT_ALLOWED block Route outside mandate. ACTION_NOT_PERMITTED
RATE_LIMIT_EXCEEDED block / escalate / observe A time-windowed rate limit fired (rate-limit-exceeded atom). RATE_LIMIT_EXCEEDED
CONTROL_UNAVAILABLE block / escalate A secondary control could not run and the deployment is configured to fail closed on them: the rate window or circuit-breaker lookup blocks; the spend-anomaly check or assurance lookup escalates to a person (§9). EVIDENCE_MISSING

Policy / SOP / standards (SAFR §8)

Code Decision Meaning SAFR §30 alias
UNAUTHENTICATED_MCP_CALL block An MCP tool call arrived without an authenticated principal for the gate to attribute it to (seeded MCP default policies). IDENTITY_UNVERIFIED
STANDARD_NONCOMPLIANT block Missing/stale pin for an enforced Standard. POLICY_VIOLATION
STANDARD_RULE block / escalate / suspend / quarantine A bound Standard molecule fired. POLICY_VIOLATION
SOP_RULE block / escalate / suspend / quarantine A bound SOP molecule fired. SOP_VIOLATION
AUTHORIZATION_RULES_CHANGED block A claim or capture of an unclaimed hold refused because the agent's current SOP or Standard rules no longer permit it (§8.7.16). The hold is voided and nothing executes; the agent asks again under the new rules. POLICY_VIOLATION
ESCALATION_NOT_APPROVED escalate A claim that asked to execute only a hold a person approved (§8.7.18) — the executor's own policy judged the request as needing review — presented a hold that no approval minted. The hold is left unclaimed and unchanged; the request still needs a person, so the agent escalates and waits for the decision. HUMAN_REVIEW_REQUIRED
AUTHORIZATION_CONTEXT_MISMATCH block A claim or an in-process resume claim of a hold a person approved stated a context other than the one that person approved (§8.7.20, §8.7.21). Refused; the hold is left for the approved request. Resume with the arguments the escalation was raised with. MANDATE_SCOPE_VIOLATION
AUTHORIZATION_CONTEXT_REQUIRED block A claim of a hold a person approved did not state the context it is about to execute (§8.7.20), or an in-process resume claim was signed with a message that binds no context (§8.7.21). Refused, the hold untouched. The detail names the executor or SDK version to upgrade to. ENVELOPE_INVALID
AUTHORIZATION_CONTEXT_UNAVAILABLE block A claim of a hold a PERSON approved, whose approving review could not be found to read the approved context from (§9a.5). Refused rather than claimed unchecked; the hold is left as it was. POLICY_VIOLATION
ESCALATION_REQUEST_MISMATCH block The arguments given to resume an approved escalation do not reproduce the request the owner approved (§8.7.21): its amount, currency, merchant, resource, payload or context. Nothing runs; resume with the arguments the escalation was raised with. MANDATE_SCOPE_VIOLATION
CONTEXT_TOO_LARGE block An agent with bound SOP or Standard rules sent a rule context (its itinerary and envelope metadata) too large to record whole with the hold (§8.7.16). Refused rather than trimmed: a trimmed context could drop a field a rule reads and let the hold slip the claim-time re-check. Send a smaller context. ENVELOPE_INVALID
SANDBOX_CALLER_SHARE_EXCEEDED block The shared public sandbox only: this caller (told apart by a keyed hash of its network) already holds its share of the sandbox's rolling spend budget, so one visitor cannot keep the budget drained for everyone else. The budget heals as the window rolls past; a real tenant's mandate never returns this. POLICY_VIOLATION

Evidence-quality controls (SAFR §24). Author-facing codes for the evidence atoms.

Code Decision Meaning SAFR §30 alias
EVIDENCE_INSUFFICIENT block / escalate A required evidence type was not attested (evidence-requirement atom). EVIDENCE_MISSING
EVIDENCE_CONFIDENCE_LOW block / escalate Attested evidence confidence below the required minimum (evidence-confidence-below atom). EVIDENCE_QUALITY_LOW

Containment / circuit breaker (SAFR §21)

Code Decision Meaning SAFR §30 alias
AGENT_SUSPENDED suspend Agent contained (suspended) by a prior firing rule — reinstate to act again. CIRCUIT_BREAKER_ACTIVE
AGENT_QUARANTINED quarantine Agent contained (quarantined) by a prior firing rule — reinstate to act again. CIRCUIT_BREAKER_ACTIVE
AGENT_DECOMMISSIONED decommission Agent decommissioned (retired) by its owner — final: it is never reinstated; a new agent takes over its work. The owner's reason, if they gave one, is the record's note. CIRCUIT_BREAKER_ACTIVE
CIRCUIT_BREAKER_OPEN block A scoped circuit breaker (agent/tenant/tool/counterparty) is open — tripped manually or by failure rate. CIRCUIT_BREAKER_ACTIVE

Human review (SAFR §12)

Code Decision Meaning SAFR §30 alias
MODE_READ_ONLY block READ_ONLY operating mode denies a value-bearing action. (The owner's act of pinning that mode is recorded under the same code, as MODE_AUTONOMOUS / MODE_SUPERVISED / MODE_RESTRICTED are.) ACTION_NOT_PERMITTED
MODE_REVIEW escalate Operating-mode floor routed to human review. HUMAN_REVIEW_REQUIRED
MODE_RESTRICTED_REVIEW escalate RESTRICTED mode: every value-bearing action needs a human. HUMAN_REVIEW_REQUIRED
MODE_SUPERVISED_REVIEW escalate SUPERVISED mode: high-consequence action needs a human. HUMAN_REVIEW_REQUIRED
CONTEXT_UNVERIFIABLE escalate A rule needed a context field (e.g. riskLevel) and could not get a well-formed value from a source it trusts — missing, unrecognised, or less trusted than the rule requires (spec §6.3). HUMAN_REVIEW_REQUIRED
REASSESSMENT_PENDING escalate A material authority change awaits owner review. HUMAN_REVIEW_REQUIRED
SPEND_PATTERN_ANOMALY escalate Amount is far above the agent's own recent payments for this action (a learned baseline, not a preset cap). Amounts the mandate's per-transaction cap and budget already bound closely are not re-checked. HUMAN_REVIEW_REQUIRED
AGENT_NOT_CERTIFIED escalate The agent's latest deployment-assurance certificate is 'blocked' (a required assurance gate failed or was never assessed). HUMAN_REVIEW_REQUIRED
ASSURANCE_STALE escalate The agent's latest deployment-assurance certificate has passed its validUntil — the assessed facts are no longer current. HUMAN_REVIEW_REQUIRED
ESCALATION_PENDING escalate Held awaiting approver resolution (§9a). HUMAN_REVIEW_REQUIRED
ESCALATION_EXPIRED block Escalation TTL elapsed (§9a.4). HUMAN_REVIEW_TIMEOUT
ESCALATION_CONTEXT_ALTERED block The pending escalation's stored request context no longer matches its row-integrity commitment made at escalate time (Tier 2 context-claim binding, SAFR §5) — reservation refused. ENVELOPE_INTEGRITY_FAILURE
MANDATE_SUPERSEDED block Approval refused: the mandate the review was raised under is no longer the agent's current mandate for the action (a newer one has been issued) — the agent asks again (§10.4.1). Also the reason a reissue closes the reviews pending under the mandate it replaced (§10.4.2). MANDATE_MISSING
APPROVAL_INPUT_UNAVAILABLE block Approval refused: an input the gate judges the request on could not be read or reconstructed at approval time (rate counter, breaker store, or the signed resource or signed jurisdiction of a review stored before it was kept) — refused, never skipped; the review stays pending (§10.4.1). EVIDENCE_MISSING
ESCALATION_APPROVED allow Approver upgraded the held request (§9a). —
ESCALATION_DENIED block Approver denied the held request (§9a). —

Decision token / capability binding (SAFR §20)

Code Decision Meaning SAFR §30 alias
AMOUNT_MISMATCH block x402 amount exceeds the authorized amount (§7a). DECISION_TOKEN_INVALID
COMMITMENT_MISMATCH block Executed transaction does not match the authorized commitment. DECISION_TOKEN_INVALID
SESSION_MISMATCH block Capability bound to a different agent↔MCP session. DECISION_TOKEN_INVALID
CAPABILITY_EXPIRED block Capability past its expiry (§7.7). DECISION_TOKEN_INVALID
CAPABILITY_SIGNATURE_INVALID block Capability signature invalid or wrong issuer key. DECISION_TOKEN_INVALID
CAPABILITY_OK allow Capability signature + transaction/session binding verified. —
CAPABILITY_AGENT_MISMATCH block The capability was issued to a different agent DID than the one presenting it — or carries no agent at all (a v1 capability) while the verifier requires one (§15.3). DECISION_TOKEN_INVALID
CAPABILITY_SCOPE_MISMATCH block The capability names a different mandate, action or currency than the verifier expects, or names none (a v1 capability) where one is required (§15.3). DECISION_TOKEN_INVALID

Evidence quality (SAFR §24)

Code Decision Meaning SAFR §30 alias
BBS_KYC_LEVEL_TOO_LOW block Disclosed KYC level below the required floor. EVIDENCE_QUALITY_LOW

MetaMynd-specific extensions beyond the SAFR floor (no §30 alias)

Code Decision Meaning SAFR §30 alias
HOLD_VOIDED void Reservation expired or explicitly voided (§7a.4). —
SETTLEMENT_FAILED block Facilitator could not verify/settle (§7a). —
AGENT_NOT_ADMITTED block A Service-side guard refused a validly signed request from an agent it does not admit (allowedAgents, §16.3) — before fetching its policy or claiming anything. COUNTERPARTY_NOT_ALLOWED
CREDENTIAL_PROFILE_NOT_PERMITTED block The agent is admitted to the Service but not to this credential profile (a route, tool or skill that admits fewer agents, §16.3) — refused before its policy is fetched or anything claimed. COUNTERPARTY_NOT_ALLOWED
GATEWAY_OWNER_MISMATCH block The agent's signed bundle names an owner other than the principal that owns this Service's credentials (gatewayOwnerPrincipal, §16.3), or names none — refused before any claim. COUNTERPARTY_NOT_ALLOWED
GATE_UNREACHABLE block Guard fail-closed on transport error: the issuer could not be reached (a network failure, or a 5xx). A Service-side guard returns it when the policy bundle fetch fails that way; a 4xx answer is not an outage. —
ZK_POLICY_OK allow All zero-knowledge predicates verified blind. —
ZK_AMOUNT_PROOF_INVALID block Bulletproofs range proof (amount ≤ cap) failed. EXPOSURE_THRESHOLD_EXCEEDED
ZK_JURISDICTION_PROOF_INVALID block Set-membership proof (jurisdiction ∈ allow-list) failed. REGULATORY_PROHIBITION
ZK_CALLER_POLICY_REJECTED block A private-authorize request carried its own policy (cap, allowedJurisdictions). The terms are read from the agent's active mandate; a caller-chosen policy is refused (HTTP 400) rather than ignored, so an old client fails loudly (§15.1). ENVELOPE_INVALID
ZK_TRANSACTION_COMMITMENT_REQUIRED block A private-authorize request carried no transactionCommitment (HTTP 400). Every allow returns a capability bound to the transaction it authorizes, so a request with no transaction to bind to is refused rather than answered with an unbound verdict (§15.1). ENVELOPE_INVALID
ZK_CAP_MISMATCH block The range proof was built against a cap other than the agent's mandate cap, so it proves nothing about this mandate. Rebuild it against the cap in the agent's policy bundle (§15.1). ENVELOPE_INVALID
ZK_AMOUNT_PROOF_REQUIRED block The mandate caps this action but the private-authorize request carries no amount commitment + range proof (§15.1). EVIDENCE_MISSING
ZK_JURISDICTION_PROOF_REQUIRED block The mandate restricts jurisdictions for this action but the private-authorize request carries no jurisdiction commitment + membership proof (§15.1). EVIDENCE_MISSING
ZK_MANDATE_TERMS_UNSUPPORTED block The mandate has no term the zero-knowledge path can prove for this action (no spend cap, no jurisdiction allow-list), or has one in a form it cannot prove (a non-upper-bound cap, a jurisdiction exclusion). Use /policy/mandate/authorize (§15.1). MANDATE_SCOPE_VIOLATION
ZK_CURRENCY_NOT_ALLOWED block The private-authorize request names a currency outside the mandate cap's currencies, or names none where the cap allows several (§15.1). MANDATE_SCOPE_VIOLATION
ZK_HUMAN_REVIEW_UNAVAILABLE block The agent's operating mode (SUPERVISED/RESTRICTED) routes value-bearing actions to a human, and the zero-knowledge path can neither see the amount nor hold anything for a reviewer — so it refuses. Use /policy/mandate/authorize (§15.1). HUMAN_REVIEW_REQUIRED
BBS_PRESENTATION_OK allow Issuer-signed attributes verified via BBS selective disclosure. —
BBS_PROOF_INVALID block BBS proof invalid, wrong issuer key, or nonce mismatch (replay). ENVELOPE_INTEGRITY_FAILURE
POLICY_BUNDLE_OK allow Policy bundle signature + freshness verified. —
POLICY_BUNDLE_STALE block Bundle older than maxStaleness — fails closed for any action under a pinned key, else for a value-bearing one. POLICY_VIOLATION
POLICY_BUNDLE_UNSIGNED block Bundle carries no signature — fails closed for any action under a pinned key, else for a value-bearing one. POLICY_VIOLATION
POLICY_BUNDLE_SIGNATURE_INVALID block Bundle signature invalid or tampered (any tier). ENVELOPE_INTEGRITY_FAILURE
POLICY_BUNDLE_UNVERIFIED block No pinned policy key and the bundle came over plain http — nothing authenticates it, so a value-bearing action fails closed. ENVELOPE_INTEGRITY_FAILURE

Action Passport (MetaMynd Governed Execution scope, Module E/F)

Code Decision Meaning SAFR §30 alias
PASSPORT_EXPIRED block The Action Passport bound to this authorization has passed its expiry — capture refused. MANDATE_EXPIRED
PASSPORT_ALREADY_CONSUMED block The Action Passport was already consumed by a prior capture — single-use, replay refused. DECISION_TOKEN_INVALID
PASSPORT_REVOKED block The Action Passport was revoked (its hold was voided) before capture. DECISION_TOKEN_INVALID

Platform rule packs

Code Decision Meaning SAFR §30 alias
SOP_SPEND_CAP block A starter SOP or rule pack's per-transaction cap: the amount is over the cap in the cap's currency. EXPOSURE_THRESHOLD_EXCEEDED
SOP_BUDGET_CAP block A rule pack's total budget: committed spend plus this amount is over the budget. EXPOSURE_THRESHOLD_EXCEEDED
RISK_REVIEW escalate A starter SOP or rule pack's risk review: the effective risk level is at or above the pack's threshold. HUMAN_REVIEW_REQUIRED
PII_REVIEW escalate A rule pack sends a request that says it carries personal data to a human. HUMAN_REVIEW_REQUIRED
CONTENT_REVIEW escalate A rule pack's starter term list matched the request text (a STARTER list, meant to be replaced with the owner's own). HUMAN_REVIEW_REQUIRED
JURISDICTION_BLOCKED block A rule pack's jurisdiction list does not include the request's jurisdiction. MANDATE_SCOPE_VIOLATION
DATA_RESIDENCY_REVIEW escalate A rule pack's data-residency list does not include the request's data region. HUMAN_REVIEW_REQUIRED
EVIDENCE_INCOMPLETE escalate A rule pack requires evidence types the request did not carry. EVIDENCE_MISSING
EVIDENCE_CONFIDENCE_REVIEW escalate The request's evidence confidence is below a rule pack's minimum. HUMAN_REVIEW_REQUIRED
COUNTERPARTY_TRUST_REVIEW escalate The counterparty's trust score is below a rule pack's review threshold. HUMAN_REVIEW_REQUIRED
RATE_LIMIT_REVIEW escalate The agent's call count in its rate-limit window passed a rule pack's early-review mark (before the hard RATE_LIMIT_EXCEEDED ceiling). HUMAN_REVIEW_REQUIRED

Automated containment (MetaMynd Governed Execution scope, Module H)

Code Decision Meaning SAFR §30 alias
REPEATED_DENIALS_CONTAINED quarantine Automated containment: this agent crossed the repeated-denial threshold within the rolling window set by the deployment and was quarantined. CIRCUIT_BREAKER_ACTIVE
MANUAL_CONTAINMENT suspend / quarantine The agent was contained (suspended or quarantined) by its owner or an operator from the dashboard or API. The owner's reason, if they gave one, is the record's note. Reinstate it to act again. CIRCUIT_BREAKER_ACTIVE
REINSTATED_AFTER_REGRESSION_CHECK allow The agent's owner reinstated it after its current policy passed every Scenario Bank set that applies to it (the sets checked are on the record); it may act again. —

Claim, resume and settlement (an executor's or SDK's refusal)

Code Decision Meaning SAFR §30 alias
AUTHORIZATION_REQUIRED block The executor requires an authorization (an authorizationId from an allow or an approval) and the request carried none. DECISION_TOKEN_INVALID
AUTHORIZATION_NOT_FOUND block No hold exists for the authorizationId presented. DECISION_TOKEN_INVALID
AUTHORIZATION_EXPIRED block The hold was not claimed within its time-to-live; ask again. MANDATE_EXPIRED
AUTHORIZATION_VOIDED block The hold was released before this claim or resume — by its agent, its owner, a revoke, a containment or a rule change. Nothing can run under it, and its owner must approve the request again. DECISION_TOKEN_INVALID
AUTHORIZATION_ALREADY_CLAIMED block Another executor (or an earlier attempt) already claimed this single-use hold. DECISION_TOKEN_INVALID
AUTHORIZATION_ALREADY_USED block A resume of an approval that is already finished with: its hold was settled, claimed by a gateway, or expired, so the tool is not run again (§8.7.21). DECISION_TOKEN_INVALID
AUTHORIZATION_IN_USE block The one resume of this approval is already taken and may still be running — by a concurrent resume in this process or another one (§9a.6); its authorization has not settled yet. Exactly one runs. DECISION_TOKEN_INVALID
AUTHORIZATION_AGENT_MISMATCH block The hold belongs to another agent than the one whose request presents it (§8.7.19). IDENTITY_UNVERIFIED
AUTHORIZATION_ACTION_MISMATCH block The hold authorizes another action than the request's (§8.7.19). MANDATE_SCOPE_VIOLATION
AUTHORIZATION_AMOUNT_MISMATCH block The hold authorizes another amount than the request's (§8.7.19). MANDATE_SCOPE_VIOLATION
AUTHORIZATION_CURRENCY_MISMATCH block The hold authorizes another currency than the request's (§8.7.19). MANDATE_SCOPE_VIOLATION
AUTHORIZATION_MERCHANT_MISMATCH block The hold authorizes another merchant than the request's (§8.7.19). COUNTERPARTY_NOT_ALLOWED
AUTHORIZATION_CLAIM_UNREACHABLE block The executor could not reach the issuer to claim the hold, so it did not execute (fail closed). —
COUNTERPARTY_AUTH_REQUIRED block A claim or settlement of a hold was not signed by the agent or by a counterparty the owner registered: an authorizationId alone settles nothing. IDENTITY_UNVERIFIED
COUNTERPARTY_MISMATCH block Only the counterparty that claimed the hold may settle it lower, void it or mark it unknown. COUNTERPARTY_NOT_ALLOWED
PAYLOAD_DIGEST_INVALID block The payload digest presented is not a well-formed sha256 digest. ENVELOPE_INVALID
PAYLOAD_DIGEST_MISMATCH block What the executor is about to run is not the payload the agent signed (payload binding, §8.3.9). ENVELOPE_INTEGRITY_FAILURE
PAYLOAD_NOT_BOUND block The executor requires the agent to bind its payload (§8.3.9) and the request bound none. ENVELOPE_INVALID
PAYLOAD_NOT_CANONICALIZABLE block The payload cannot be canonicalised (a value JSON cannot carry, too deep or too large), so it cannot be bound or compared. ENVELOPE_INVALID
ARGUMENT_NOT_ALLOWED block The tool call carries an argument outside the paths its mandate's argument policy allows (Permission.arguments.allow); the refusal names the JSON Pointer path. Raised by the guard that executes the call, before any hold is claimed. Remove the field, or have the owner allow it. MANDATE_SCOPE_VIOLATION
ARGUMENTS_UNCHECKABLE block The mandate sets an argument policy for this tool and the guard cannot check the call against it: the arguments are not a single JSON object (or are too deep or too large), the guard was not given them, or the policy itself is malformed. Refused rather than run unchecked. ENVELOPE_INVALID
SENSITIVE_ARGUMENT_UNPROTECTED block The mandate's argument policy says a value must leave keyed-hashed (sensitive: hmac) and the guard has no key to hash it with (argumentKey, AGENTSAFE_ARGUMENT_KEY), so the call is refused rather than sent in the clear. Configure the key on the Service. —
PAYLOAD_ALREADY_BOUND block A late payload binding (§8.3.11) was refused: the hold is already bound to another payload. ENVELOPE_INTEGRITY_FAILURE
PAYLOAD_BINDING_UNSUPPORTED block Raised by the SDK: its key provider cannot sign a payload binding, so the request is not sent unbound. —
PAYLOAD_BINDING_NOT_CONFIRMED block Raised by the SDK: the issuer answered a late binding without confirming the digest (it predates §8.3.11), so the hold is treated as unbound. —
HOLD_UNDER_RECONCILIATION block The hold is being reconciled (its outcome is unknown) and cannot be claimed or settled until that resolves. —
EFFECT_TRANSITION_CONTENDED block An overlapping attempt on the same hold is in progress; retry the same request (not a refusal of it). —
AMOUNT_CHARGED_REQUIRED block A settlement must state the amount actually charged. —
REFUND_AMOUNT_INVALID block A refund must be a positive amount no greater than what was captured. —
SETTLEMENT_REUSED block A settlement proof (x402 payment) was already used for another authorization. DECISION_TOKEN_INVALID
ESCALATION_MODIFIED_APPROVED allow A reviewer MODIFIED the escalated action and the modified action passed the gate: a hold was minted on the reviewer's values. —

Fail-closed executor and guard errors

Code Decision Meaning SAFR §30 alias
GUARD_ERROR block The executor guard failed while verifying the request (not an issuer outage); nothing executed. —
LOCAL_EVAL_ERROR block The agent's guard failed while evaluating its cached rules locally; nothing ran. —
ISSUER_UNREACHABLE block The executor could not reach the issuer it needs (bundle, claim or status), so it did not execute. —
ISSUER_API_REQUIRED block The executor is configured to claim or settle but was given no issuer API to do it with. —
NO_ISSUER block No issuer is configured for an operation that needs one. —
BUNDLE_SUBJECT_MISMATCH block The policy bundle fetched is for another agent than the request's. ENVELOPE_INTEGRITY_FAILURE
MAGP_ENVELOPE_MISSING block An A2A task arrived without the MAGP extension envelope (the signed request). ENVELOPE_INVALID
CAPABILITY_REQUIRED block The tool requires a capability credential the request did not present. DECISION_TOKEN_INVALID
CAPABILITY_CHECK_ERROR block The capability credential could not be checked, so the call was refused. —
SERVICE_IDENTITY_REQUIRED block The executor has no service identity (serviceDid / key) for an operation that must be signed. —
SERVICE_SIGNING_FAILED block The executor could not sign with its service key. —
NO_SERVED_AGENT block The executor was asked to act for an agent it does not serve. IDENTITY_UNVERIFIED
NO_FACILITATOR block An x402 payment needs a facilitator and none is configured. —
REPORT_OUTCOME_INVALID block An executor's outcome report was malformed and was not sent. —
REPORT_UNREACHABLE block An executor's outcome report could not be delivered (it is kept and re-sent). —

Rules MetaMynd writes for you: the Controlled Testing plans and the SOP compiler's drafts

Code Decision Meaning SAFR §30 alias
SANDBOX_PER_TXN_CAP block Controlled Testing baseline plan: an amount over its per-transaction cap. EXPOSURE_THRESHOLD_EXCEEDED
SANDBOX_CUMULATIVE_CAP block Controlled Testing baseline plan: committed spend over its cumulative cap. EXPOSURE_THRESHOLD_EXCEEDED
SANDBOX_RISK_REVIEW escalate Controlled Testing baseline plan: a high-risk action sent for review. HUMAN_REVIEW_REQUIRED
SANDBOX_JURISDICTION block Controlled Testing baseline plan: a jurisdiction outside the plan's list. MANDATE_SCOPE_VIOLATION
SANDBOX_TOOL_NOT_ALLOWED block Controlled Testing baseline plan: a tool outside the plan's allow-list. ACTION_NOT_PERMITTED
SANDBOX_PII_DISCLOSURE block Controlled Testing baseline plan: a request that discloses personal data. POLICY_VIOLATION
SANDBOX_EVIDENCE_CONFIDENCE escalate Controlled Testing baseline plan: evidence confidence below the plan's minimum. EVIDENCE_QUALITY_LOW
SANDBOX_RATE_EXCURSION observe Controlled Testing baseline plan: call rate above the plan's watch mark (flagged, not refused). —
PROBE_CAP block Governance Respect Probes: the over-cap probe was refused, as the probe expects (the agent must then stop). EXPOSURE_THRESHOLD_EXCEEDED
PROBE_ESCALATE escalate Governance Respect Probes: the high-risk probe was escalated, as the probe expects (the agent must then wait). HUMAN_REVIEW_REQUIRED
AMOUNT_ABOVE_LIMIT block A rule the SOP compiler drafted from "no payment may exceed X": the amount is over X. EXPOSURE_THRESHOLD_EXCEEDED
AMOUNT_NEEDS_APPROVAL escalate A rule the SOP compiler drafted from "payments over X need approval": the amount is over X. HUMAN_REVIEW_REQUIRED
BUDGET_EXCEEDED block A rule the SOP compiler drafted from a total budget: committed spend plus this amount is over it. EXPOSURE_THRESHOLD_EXCEEDED
BUDGET_NEEDS_APPROVAL escalate A rule the SOP compiler drafted from a total budget that needs approval beyond it. HUMAN_REVIEW_REQUIRED
NO_FINANCIAL_AUTHORITY block A rule drafted from "the agent has no financial authority": any request with a positive amount is refused (one with no amount is not). ACTION_NOT_PERMITTED
CONSENT_MISSING block A rule the SOP compiler drafted from a consent requirement: the request says consent was not given. POLICY_VIOLATION
PII_PRESENT block A rule the SOP compiler drafted from "personal data must not be shared": the request carries personal data. POLICY_VIOLATION
PII_NEEDS_REVIEW escalate A rule the SOP compiler drafted that sends a request carrying personal data to a human. HUMAN_REVIEW_REQUIRED
PROHIBITED_TERM block A rule the SOP compiler drafted from a list of banned words: the request text contains one. POLICY_VIOLATION
PROHIBITED_CONTENT block A rule the SOP compiler drafted from a prohibited-content requirement: the request text matched. POLICY_VIOLATION
PROHIBITED_CONTENT_NEEDS_REVIEW escalate A rule the SOP compiler drafted that sends prohibited or abusive content to a human. HUMAN_REVIEW_REQUIRED
RISK_TOO_HIGH block A rule the SOP compiler drafted that refuses actions at or above a risk level. POLICY_VIOLATION
RISK_NEEDS_REVIEW escalate A rule the SOP compiler drafted that sends actions at or above a risk level to a human. HUMAN_REVIEW_REQUIRED
OPERATION_NEEDS_APPROVAL escalate An owner-keyed operation rule: the request context names an operation (e.g. op=delete-all) the owner sends to a human, whatever risk level the agent declared. HUMAN_REVIEW_REQUIRED
OPERATION_NOT_ALLOWED block An owner-keyed operation rule: the request context names an operation the owner does not allow, or omits a field the rule requires. ACTION_NOT_PERMITTED
SCENARIO_PER_TXN_CEILING block A Scenario Bank set's per-transaction ceiling: the amount is over it. Remedy: lower the amount, or change the owner's cap the set checks. EXPOSURE_THRESHOLD_EXCEEDED
SCENARIO_CUMULATIVE_CEILING block A Scenario Bank set's cumulative ceiling: committed spend plus this amount is over it. EXPOSURE_THRESHOLD_EXCEEDED
SCENARIO_RATE_CEILING block A Scenario Bank set's rate ceiling: the agent's calls in the window are over it. RATE_LIMIT_EXCEEDED
SCENARIO_CONSENT_MISSING block A Scenario Bank set: the request says consent was not given for the data it uses. POLICY_VIOLATION
SCENARIO_SOURCE_NOT_APPROVED block A Scenario Bank set: the request's data source is not on the approved list. POLICY_VIOLATION
SCENARIO_DATA_RESIDENCY block A Scenario Bank set: the request's data region is outside the allowed regions. REGULATORY_PROHIBITION
SCENARIO_TOOL_NOT_ALLOWED block A Scenario Bank set: the tool is not on the set's allow-list. ACTION_NOT_PERMITTED
SCENARIO_MODEL_NOT_ALLOWED block A Scenario Bank set: the model is not on the set's allow-list. ACTION_NOT_PERMITTED
SCENARIO_HIGH_RISK_REVIEW escalate A Scenario Bank set: a high-risk action is sent to a human. HUMAN_REVIEW_REQUIRED

MCP tool-call policies (the built-in policy catalog)

Code Decision Meaning SAFR §30 alias
APPROVED allow An MCP tool-call policy governs this action and none of its rules refused it: the call may run. —
NO_POLICY allow No active MCP tool-call policy governs this action (the verdict's standardRef is UNGOVERNED): the call is allowed ungoverned. Remedy: attach a policy if the tool should be governed. —
DATA_SOURCE_NOT_APPROVED block A catalog policy: the tool call's data source is not on the policy's approved list. POLICY_VIOLATION
RISK_THRESHOLD escalate A catalog policy: a high-risk tool call is sent to a human. HUMAN_REVIEW_REQUIRED
PROHIBITED_CLAIM block A catalog policy: the text makes a prohibited financial claim (e.g. "guaranteed returns", "risk-free"). REGULATORY_PROHIBITION
SENSITIVE_ADVICE escalate A catalog policy: the text reads as medical or legal advice, so a human reviews it. HUMAN_REVIEW_REQUIRED

HTTP gateway refusals (agentsafe-http-gateway)

Code Decision Meaning SAFR §30 alias
MISSING_GOVERNANCE block HTTP 401: a governed route was called without a signed MAGP request (by default the x-magp-request header). Remedy: sign the call with the agent's guard (buildSignedRequest) and send it in that header, or in whatever the gateway's extractGovernance reads. ENVELOPE_INVALID
GATEWAY_ACTION_MISMATCH block HTTP 403: the signed request names a different action than the one this gateway route serves. Nothing is claimed and no nonce is consumed. Sign the call with the route's action, or call the route that serves the action you signed. ACTION_NOT_PERMITTED
PAYLOAD_UNBINDABLE block HTTP 403: the JSON body carries none of the governed fields (amount, currency, merchant) at its top level — nested, renamed, differently cased or an array — so the gateway cannot show the signature covers it. Remedy: put the governed fields at the body's top level, or give the route its own bind. ENVELOPE_INVALID
BIND_ERROR block HTTP 502: the route's payload binder threw reading the body, so the gateway cannot show the signature covers it (fail closed). Remedy: fix the route's bind function or the body it is given. —
GOVERNANCE_ERROR block HTTP 502: verifying the request threw inside the gateway — the guard failed, or the route's trustedContext deriver threw or returned nothing usable (fail closed). The error says which. Remedy: fix the deriver or the guard's configuration; it is not a policy verdict. —
CREDENTIAL_UNAVAILABLE block HTTP 502: the gateway's credential vault released no credential for this route, so the call was not forwarded and the hold it claimed was released. Remedy: configure the credential, or mark a route that needs none credential: false. —
UPSTREAM_UNREACHABLE block HTTP 502: the permitted call could not be forwarded because no connection to the upstream was made, so nothing executed. The claimed hold was released and the body says executed: false. A retry is safe once the upstream is reachable. —
ACCESS_PRINCIPAL_UNAVAILABLE block HTTP 502: the route checks what its upstream returns against the tenant and user the request is served for (route.access), and the operator's resolver named neither, so the call was not forwarded and the claimed hold was released (executed: false). Remedy: fix the resolver, or the fixed principal the route names. —
ACCESS_CONTEXT_MISMATCH block HTTP 502: the upstream ran, but a tenant or user field in its answer (path) names someone other than the principal the request is served for, so the whole answer was withheld (executed: true). The value is never returned. Investigate the upstream query or filter that returned another principal's record. MANDATE_SCOPE_VIOLATION
ACCESS_CONTEXT_UNCHECKABLE block HTTP 502: the upstream ran, but its answer could not be checked against the request's principal: not strict JSON, too many fields, or a tenant or user field the route names is missing or not a string or number (path, detail). It was withheld rather than released unchecked (executed: true). Remedy: correct route.access to the upstream's response shape. —
OUTPUT_FORBIDDEN_WORD block HTTP 403: what the call would send (its body after any argument rewrite, its query string, its path, or a header the route names) contains one of the operator's forbidden words (route.output, or the MCP guard's output), so nothing was sent and the claimed hold was released (executed: false). path says where (? for the query string, @path for the path, @headers/<name> for a header); the word is never returned. POLICY_VIOLATION
OUTPUT_UNCHECKABLE block HTTP 403: what the call would send could not be checked for forbidden words (a body that is not UTF-8, JSON that is not strict, a form or query string that does not decode, or a value that is not plain data), so it was refused rather than sent unchecked (executed: false). detail says why. —
GATEWAY_ERROR block HTTP 502: the gateway's HTTP server caught an unexpected error handling the call (fail closed). Check the gateway's log; the request may be retried. —
BLOCKED block HTTP 403: the guard refused the call without naming a code (an older or custom guard). Treated as a block (§13.3). —
EXECUTED allow An outcome report's code: the gateway forwarded the permitted call and the upstream answered (the report's httpStatus says how). —
UPSTREAM_ERROR allow An outcome report's code, and the reason a claimed hold is marked unknown: the permitted call was forwarded and the upstream failed without an HTTP answer, so whether it took effect is unknown until reconciled. —
RESPONSE_LOST allow The reason recorded when a claimed hold's effect is marked unknown (effect/unknown), as RESPONSE_LOST or RESPONSE_LOST:<executor's reason>: the call was forwarded and its outcome could not be established. The hold stays committed until it is reconciled (§8.7.12). —
SETTLEMENT_THREW block The gateway's settlement call to the issuer (capture, void or mark-unknown) threw. It is retried as transient; after the last attempt it is logged and the claimed hold stays committed to the cap until it is reconciled. —

Guard and SDK fail-closed refusals

Code Decision Meaning SAFR §30 alias
CONTEXT_SIGNATURE_REQUIRED block A service-side guard run with requireContextSignature received a request whose context carries no envelope signature (or had it stripped). Remedy: have the agent sign its context (the SDKs do by default; do not pass signContext: false). ENVELOPE_INVALID
CONTEXT_SIGNING_UNSUPPORTED block Raised by the SDK: its key provider (or the agentsafe-signer daemon) cannot sign the request's context, so the request is not sent unsigned. Remedy: upgrade the signer, or pass signContext: false. —
JURISDICTION_SIGNING_UNSUPPORTED block Raised by the SDK: the agentsafe-signer daemon predates signed jurisdictions (signer 0.18.0), so the request is not sent. Remedy: upgrade the signer, or omit jurisdiction. —
SIGNER_UNREACHABLE block Raised by the SDK: the agentsafe-signer daemon holding the agent's key could not be reached or failed, so nothing was signed or sent (not a gate outage). Remedy: start the daemon or fix its socket path. —
CAPABILITY_INVALID block The MCP guard's capability verifier rejected the capability credential the call presented, without naming a more specific code. DECISION_TOKEN_INVALID
NOT_APPLIED block A service-side guard's settlement call (capture, void, refund or mark-unknown) was refused by the issuer without a reasonCode; nothing changed on the hold. The HTTP status and the issuer's message ride along. —
REFUSED block Raised by the Python client: a settlement or late payload-binding call was refused and the issuer's answer carried no reasonCode or message. —
UNKNOWN block Raised by the Python client: the gate's answer carried no reasonCode (an older or non-MAGP endpoint). Read per §13.3: a non-allow decision without a known code is a block. —

Claim payload and x402 settlement binding

Code Decision Meaning SAFR §30 alias
PAYLOAD_DIGEST_REQUIRED block A claim of a hold whose authorization bound a payload digest presented none: an executor cannot skip the binding by omission (§8.3.9). Nothing is claimed. Remedy: the executor states the digest of what it is about to run (guard payloadDigest). ENVELOPE_INVALID
PAYLOAD_NOT_BOUND_AT_AUTHORIZE block A claim presented a payload digest for a hold whose authorization bound none: the executor believes the payload is bound and it is not (§8.3.9). Nothing is claimed. Remedy: the agent binds its payload when it asks (or binds it late, §8.3.11). ENVELOPE_INVALID
MISSING_BINDING block x402: the payment requirements carry no MAGP authorization (extra.magpAuthorizationId), so the payment cannot be tied to a hold (§7a.2). DECISION_TOKEN_INVALID
AUTHORIZATION_MISMATCH block x402: the payment is bound to another authorization than the one being settled (§7a.2). DECISION_TOKEN_INVALID
AMOUNT_INVALID block An amount that is not a usable number: an x402 paid amount that is not a non-negative integer of minor units (§7a.2), a capture whose amountCharged is negative or not a number, or a refund that is not positive. —
BINDING_OK allow x402: the payment is bound to this authorization and pays no more than it authorized (§7a.2). —

Capture, release and refund of a hold

Code Decision Meaning SAFR §30 alias
AMOUNT_EXCEEDS_AUTHORIZED block A capture whose amountCharged is over the authorized hold. Nothing is captured; an overspend goes through reconciliation, not capture. EXPOSURE_THRESHOLD_EXCEEDED
AGENT_SETTLE_SIGNATURE_INVALID block The agent's signature on a settlement of its own unclaimed hold does not verify over MAGP-SETTLE-v1 for this call. ENVELOPE_INTEGRITY_FAILURE
PAYEE_NOT_REGISTERED block A settlement below the hold named an account paid that is not one the owner registered for this merchant (payee directory), so the lower amount is not accepted. COUNTERPARTY_NOT_ALLOWED
PAYEE_NOT_VERIFIED block The payee directory could not be read to confirm the account paid. Remedy: settle at the full authorized amount, or retry. —
SETTLEMENT_NOT_CONFIRMED block The remote system could not confirm a lower settled amount, or (on a release after a claim) that nothing was settled. Remedy: settle at the authorized amount, mark the effect unknown for reconciliation, or retry once it can confirm. —
SETTLEMENT_AMOUNT_MISMATCH block The remote system reports a different settled amount than the settlement states. —
SETTLEMENT_NOT_FOUND block The remote system reports no settlement for this authorization, so a lower settled amount cannot be confirmed. —
EFFECT_NOT_CAPTURABLE block The hold's effect is in a state that cannot be captured (unknown, reconciling, ...): reconcile it first. —
EFFECT_NOT_VOIDABLE block The hold's effect is in a state that cannot be released (unknown, reconciling, ...): reconcile it first. —
INVALID_EFFECT_TRANSITION block The requested move of the hold's effect (e.g. to reconciling or compensating) is not allowed from its current state; read its state and act on that. —
HOLD_STATE_CHANGED block The hold was claimed, settled or voided by another caller while this capture or refund ran; nothing was applied. Read its current state and retry. —
NOT_CAPTURED block Only a captured (settled) authorization can be refunded; this one is not. —
ALREADY_REFUNDED block Nothing remains to refund: the capture was already refunded in full. —
AMOUNT_EXCEEDS_REFUNDABLE block The refund is more than what remains to refund of the capture. —
CLAIM_EXPECTATION_INVALID block HTTP 400: a claim's expectation (the action, amount, currency or merchant the executor expects the hold to authorize) is malformed. ENVELOPE_INVALID
IDEMPOTENCY_KEY_INVALID block HTTP 400: the Idempotency-Key on a hold operation is malformed. —
MANDATE_BLOCK block An approved escalation, re-judged against the mandate and rules as they stand at reservation, no longer passes and no more specific code was produced. Nothing is reserved; ask again. POLICY_VIOLATION
MODIFIED_APPROVED allow A reviewer-modified action, re-judged against the rules as they stand, passed every check (the hold is then minted as ESCALATION_MODIFIED_APPROVED). —
COMPENSATION allow The separately authorized $0 effect opened to compensate (reverse) an earlier one; its booking reference names the effect it compensates. —

Counterparty authentication (a claim, settlement or outcome report signed by an executor)

Code Decision Meaning SAFR §30 alias
COUNTERPARTY_AUTH_MALFORMED block The executor's counterparty signature headers are incomplete or malformed (nonce, issuedAt or signature missing or out of range). ENVELOPE_INVALID
COUNTERPARTY_DID_UNSUPPORTED block The executor's service DID is not one whose key the issuer can resolve (use did:key). IDENTITY_UNVERIFIED
COUNTERPARTY_REQUEST_EXPIRED block The executor's signed request is outside the freshness window (too old, or issued too far in the future). Check the executor's clock. ENVELOPE_INVALID
COUNTERPARTY_SIGNATURE_INVALID block The executor's counterparty signature does not verify against its service DID. ENVELOPE_INTEGRITY_FAILURE
COUNTERPARTY_REPLAY_DETECTED block The executor's signed request reuses a nonce already seen: a replay is refused. ENVELOPE_INTEGRITY_FAILURE
COUNTERPARTY_NOT_REGISTERED block The executor's service DID is not an active counterparty the agent's owner registered (or not a trusted entry where a proof of control is required). COUNTERPARTY_NOT_ALLOWED
COUNTERPARTY_NOT_ALLOWED_FOR_MERCHANT block The registered counterparty is scoped to other merchants than this hold's. COUNTERPARTY_NOT_ALLOWED
COUNTERPARTY_PROOF_REQUIRED block Registering the counterparty needs a proof of control of its DID (a signed challenge) and none was given. IDENTITY_UNVERIFIED
COUNTERPARTY_PROOF_INVALID block The proof of control of the counterparty DID is malformed or does not verify. IDENTITY_UNVERIFIED
COUNTERPARTY_PROOF_EXPIRED block The challenge the proof of control signs has expired; fetch a new one. IDENTITY_UNVERIFIED
REPORT_CALLER_UNVERIFIED block HTTP 400: an 'executed' outcome report must carry a caller request that verifies; this one does not. ENVELOPE_INTEGRITY_FAILURE
REPORT_OCCURRENCES_INVALID block HTTP 400: an 'executed' outcome report counts exactly one occurrence (only refusals are aggregated). ENVELOPE_INVALID
REPORT_RATE_LIMITED block HTTP 429: the executor sent outcome reports faster than the issuer accepts them; retry later (the guard keeps and re-sends them). RATE_LIMIT_EXCEEDED

Effect reconciliation and compensation

Code Decision Meaning SAFR §30 alias
NOT_RECONCILABLE block HTTP 409: the effect is neither unknown nor reconciling, so there is nothing to reconcile; its current effectState is returned. —
NOT_RECONCILING block An operator resolution needs the effect to be reconciling; it is not. —
NOT_COMPENSABLE block Compensation can begin only for an effect in compensation_required; this one is not. —
NOT_COMPENSATING block Compensation can be completed only for an effect that is compensating; this one is not. —
RECONCILE_COOLDOWN block HTTP 429: this effect was probed moments ago; retry after the time in detail. RATE_LIMIT_EXCEEDED
RECONCILE_NOT_PERMITTED block HTTP 403: the caller is not one that may resolve this hold (its claimer, or a signed-in user who may manage it). —
NEEDS_HUMAN escalate Reconciliation could not decide what happened (no reconciler for this effect, or the remote was inconclusive): the effect stays reconciling for an operator to resolve. HUMAN_REVIEW_REQUIRED
RECONCILED_SUCCEEDED allow Reconciliation found the effect happened: the hold is captured at the amount the remote system (or the operator) says was settled. —
RECONCILED_FAILED block Reconciliation found the effect did not happen: the hold is released and the effect is failed_final. —
COMPENSATION_REQUIRED escalate The effect happened but must be reversed (it violates a postcondition or policy): it waits for a compensating action. HUMAN_REVIEW_REQUIRED
SETTLED_OVER_AUTHORIZED escalate Reconciliation found more settled than was authorized: the cap counts the real amount and the effect waits for compensation. EXPOSURE_THRESHOLD_EXCEEDED
COMPENSATION_STARTED escalate A separately authorized compensating effect was opened for this one, which is now compensating. —
COMPENSATED allow The compensating action succeeded: the original effect is compensated. —
COMPENSATION_FAILED block The compensating action failed: the original effect is compensation_failed and needs a person. —
EFFECT_RECONCILE_NO_HANDLER escalate Evidence row: no reconciler can probe this effect, so it waits for an operator (the answer is NEEDS_HUMAN). HUMAN_REVIEW_REQUIRED
EFFECT_RECONCILE_UNRESOLVED escalate Evidence row: the reconciler could not decide what happened, so it waits for an operator (the answer is NEEDS_HUMAN). HUMAN_REVIEW_REQUIRED
EFFECT_RECONCILED_SUCCEEDED allow Evidence row of RECONCILED_SUCCEEDED. —
EFFECT_RECONCILED_FAILED block Evidence row of RECONCILED_FAILED. —
EFFECT_COMPENSATION_REQUIRED escalate Evidence row of COMPENSATION_REQUIRED. HUMAN_REVIEW_REQUIRED
EFFECT_SETTLED_OVER_AUTHORIZED escalate Evidence row of SETTLED_OVER_AUTHORIZED. EXPOSURE_THRESHOLD_EXCEEDED

Owner actions on the evidence trail

Code Decision Meaning SAFR §30 alias
MODE_AUTONOMOUS allow The owner pinned the agent's operating mode to autonomous. —
MODE_SUPERVISED allow The owner pinned the agent's operating mode to supervised. —
MODE_RESTRICTED allow The owner pinned the agent's operating mode to restricted. —
MODE_PIN_CLEARED allow The owner cleared the agent's operating-mode pin; the derived mode applies again. —
REASSESSMENT_REQUIRED escalate The owner recorded a change to the agent that needs its assurance reassessed before it is trusted as before. HUMAN_REVIEW_REQUIRED
REASSESSED allow The owner cleared a pending reassessment of the agent. —
REGRESSION_CHECK_PASSED allow The agent version's current policy passed every Scenario Bank set that applies to it (the sets checked are on the record). —
TRANSCRIPT allow An agent-chat session transcript was anchored as evidence (hashed on-chain, encrypted off-chain). —

Appendix B — Canonical examples

Canonical message

did:hedera:testnet:zEEZ…|flight-purchase|150|USD|skyward-air|9f2c…|2026-07-10T05:12:00Z

A narrowing delegation — parent caps at 500 across two merchants; the child caps at 50 against one of them, and repeats the parent's cumulative constraint because omitting it would widen:

{ "target": "flight-purchase",
  "permission": [{ "target": "flight-purchase", "constraint": [
    { "leftOperand": "mm:payAmount",       "operator": "lteq",    "rightOperand": 50 },
    { "leftOperand": "mm:cumulativeSpend", "operator": "lteq",    "rightOperand": 500 },
    { "leftOperand": "mm:merchant",        "operator": "isAnyOf", "rightOperand": ["skyward-air"] }
  ]}]}

A refused widening — the same child with mm:payAmount raised to 5000 is rejected at issuance with CONSTRAINT_WIDENED; omitting mm:payAmount entirely is rejected with CONSTRAINT_DROPPED.


Appendix C — Atom catalog

Atoms are pure predicates over request context. Generated from the reference implementation; GET /standards/atoms returns the live catalog and is authoritative. New governance requirements are added as new atoms, which does not require a protocol version bump (§18.1).

Predicate Required context Fires when
amount-over amount Fires when a single action amount exceeds a configured limit (per-transaction cap).
amount-unknown amount Fires when the action carries no usable amount, or a NEGATIVE one — the gate cannot trust either for capping. A deny-by-default control for value-moving actions: author it with BLOCK ahead of a spend cap, otherwise an amount that is missing, unparseable, or negative passes the cap untested (amount-over only ever fires above the limit, so a negative amount clears every positive cap). A genuine $0 amount does NOT fire this — only attach it to actions that must always carry a real, non-negative amount.
cumulative-over amount, cumulativeSpend Fires when cumulative spend (already-spent + this transaction) exceeds a configured total budget.
risk-at-or-above riskLevel Fires when the assessed risk level is at or above the configured threshold.
data-source-not-approved dataSourceId Fires when the action uses a data source not on the approved list.
consent-missing consent Fires when explicit consent is absent for the action.
text-matches prompt, output Fires when the prompt or output contains any of the configured terms.
jurisdiction-not-allowed jurisdiction Fires when the action's jurisdiction is not on the allow-list.
data-residency-violation dataResidency Fires when data would be processed in a region not on the allow-list.
model-not-allowed model Fires when the agent uses an LLM model not on the approved list.
tool-not-allowed tool Fires when the agent invokes a tool/function not on the approved list.
pii-present piiPresent Fires when the action is flagged as involving personal data (PII).
rate-limit-exceeded callCount Fires when the rolling call count exceeds a configured maximum.
hol-trust-below-review holTrustScore Routes to human review when the counterparty's MetaMynd Trust Index (HCS-28) score is below a soft review line. Guidance, not a hard block — author it with an ESCALATE decision. The score is resolved server-side; no counterparty score → the atom does not fire.
evidence-requirement evidenceTypes Fires when the action is not backed by every REQUIRED evidence type the agent attests to in evidenceTypes (missing evidence — including none supplied). A REQUIRE control (SAFR §24): author it with ESCALATE or BLOCK so an under-evidenced action is stopped or reviewed.
evidence-confidence-below evidenceConfidence Fires when the attested evidence confidence is below a required minimum — or absent (SAFR §24). A min of 0 / unset is no requirement. Author with ESCALATE to route low-confidence actions to review.
context-value-in — Fires when a field of the request context (e.g. op, or params.op) has one of the listed values — key a rule on WHAT the agent is doing, not on the risk level it declares about itself. Author with ESCALATE ("deleting records needs approval") or BLOCK. Case, width, whitespace, hyphens/underscores, invisible characters and common Cyrillic/Greek look-alike letters are ignored when comparing. A deny-list cannot foresee every spelling: when the safe values are known, prefer context-value-not-in. When it fires, the verdict and the escalation carry a context-value risk signal naming the field and value.
context-value-not-in — Fires when a field of the request context has a value that is NOT on the owner's allow-list ("anything other than read or list needs approval"). The sound form of an operation rule: a spelling or look-alike the owner did not foresee fires instead of passing. Same comparison rules, missing and actions options as context-value-in, and the same context-value risk signal when it fires.

Appendix D — Future directions

Not implemented at 1.0. Recorded so implementers know the intended direction, and kept out of the normative text so nothing in the body is aspirational.

  • Content-addressed rule packs. Each published rule-pack version addressed by content hash, so a bundle can name an exact immutable pack.
  • HCS-10 channels. An on-chain, auditable channel binding for peers that require message-level durability rather than TLS request/response.
  • WebSocket transport. A duplex binding for streaming interactions.
  • HCS-2 registry discovery. Trustless discovery of DIDs and service endpoints via a mirror node, with the issuer offline.
  • Signed bundle staleness enforcement at the edge, beyond the risk-tiered rules of §6.2.

Changes from 0.4

  • The order of checks (§8.5) has 16 steps, adding containment, operating modes, rate limits, circuit breakers, OBSERVE folding and the reassessment floor.
  • Reason codes are generated from the registry (Appendix A).
  • New: resource scope (§5.3), delegation and the narrowing invariant (§5.4), agent posture (§7), secondary controls (§9), effects and reconciliation (§12), decision records and the governance envelope (§14), privacy-preserving verification (§15).
  • Platform governance surfaces moved to MetaMynd Platform Governance.

1.0 editorial revision (2026-10-20). Implementation detail that an interoperating party does not need was removed. No requirement on the wire, the order of checks or the reason codes changed.