Status: release candidate (unpublished). This document is normative for
the shipped reference implementation and its conformance corpus (digest
sha-256:k0ltF0KWcORTzkyr6dP7TPU9M2VdpewAvf_O8w2D7Wo, 88 cases). Where
this document and the package's compiled data disagree, the package and
its corpus are the truth and this document is a defect.
The protocol is non-authorizing. It validates structure, canonical bytes, bounds, compatibility, and evidence. Protocol validity never grants authority: a consuming host remains responsible for identity, tenancy, live policy, effect ownership, execution, and evidence retention. Every verification result — including a fully green one — is evidence, never a decision.
1. Scope and non-goals
Two language-neutral artifacts:
- Blueprint Core — stable identity, typed ports, logical capability requirements, bounds, evidence commitments, registered extensions.
- Deployment Manifest — environment-local tool, principal, data, authority, effect, evaluation, and exact-build bindings for one Blueprint release digest.
Deliberately out of scope (host-owned): runtime kernel, live authority and grants, effect admission/replay/recovery, product schemas, transport clients, billing, evaluation truth. The package compiles to an inert library: no supervision tree, no application callback, zero third-party production dependencies.
2. Artifacts and the closed world
Both artifacts are JSON objects decoded under bounded ceilings (depth, members, items, nodes, string bytes, key bytes, number lexemes — the ceiling family). Decoding is fail-closed with a pinned failure precedence: unknown member → missing required → type → constraint → cardinality → nested → cross-field hooks.
The registries are closed worlds — the exact member sets the shipped tables define:
- Blueprint Core (18 members). Required:
blueprint_id,protocol_revision,producer,release_number,capability_requirements,input_ports,output_ports,output_contract,triggers,ceilings,classification_ceiling,effect_intents,evaluation_assertions,extensions,required_core_fields,content_digest. Optional:signatures,attestations. - Deployment Manifest (19 members). Required:
protocol_revision,blueprint_release,scope_projection,tool_bindings,data_bindings,authority_requirement,effect_owner,signer_custody,eligibility,model_policy,host_bounds,lifecycle,build_identities,evaluation_binding,extensions,required_core_fields,deployment_digest. Optional:signatures,attestations.
Every core member addition requires a protocol revision increment: an older consumer rejects an unknown member, so the vocabulary may only evolve at a revision boundary. No identifier in the package — module, function, path, corpus, or config — carries a version token; the Hex package version is the sole version number.
3. Bytes
- Encoding: UTF-8 JSON, bounded by the decoder ceilings. Duplicate
members deny
:duplicate_member(I-JSON). Trailing bytes deny:trailing_bytes. - Integer window: a pure-digit lexeme above ±(2^53−1) decodes as a
float iff that double's canonical ECMAScript serialization reproduces
the lexeme byte-exactly; every other above-bound lexeme denies
:number_not_double_expressible. Core fields are integer-typed by schema: a float-tagged window value in a core field denies:invalid_type. - base64url: unpadded, exact alphabet; padded input denies
:base64url_padded, non-alphabet input denies:base64url_invalid. - Canonicality: interchange bytes must already be canonical —
decode → re-encode → byte-compare; non-canonical bytes deny
:non_canonical_bytesbefore any semantic read, and digests are computed only over the exact received bytes.
4. Canonicalization
RFC 8785 JSON Canonicalization Scheme:
- No whitespace between tokens (§3.2.1).
- Control characters U+0000–U+001F escaped as lowercase
\uXXXXexcept the five short escapes;"and\escaped; a lone surrogate denies (§3.2.2.2). - Numbers per ECMA-262 7.1.12.1 including the Note 2 enhancement:
1.0 → "1",1.0e22 → "1e+22",-0.0 → "0",5.0e-324 → "5e-324",1.7976931348623157e308 → "1.7976931348623157e+308"; NaN and Infinity deny (§3.2.2.3). - Object members sorted as UTF-16 code units compared as unsigned
integers (§3.2.3) — NOT by UTF-8 byte order: for
U+FF3AvsU+10000, UTF-16 order putsU+10000first.
5. Digest
digest_bytes = SHA-256( domain_separator || <<0>> || JCS(digest_input) )digest_input is the artifact object minus content_digest (or
deployment_digest), signatures, and attestations — everything
else is covered, including extensions and unknown-but-optional
extension payloads retained verbatim.
| Purpose | Separator |
|---|---|
| Blueprint content | agent-blueprint-protocol/blueprint-content |
| Deployment content | agent-blueprint-protocol/deployment-content |
| Federation envelope | agent-blueprint-protocol/federation-envelope |
| Signature input | agent-blueprint-protocol/signature |
| Conformance report | agent-blueprint-protocol/conformance-report |
| Corpus index | agent-blueprint-protocol/corpus-index |
Separators carry no version token: protocol_revision is itself a
covered member. The wire form is the self-identifying tagged string
sha-256:<43-char unpadded base64url>, never a bare hex blob; an
unknown algorithm tag denies :digest_algorithm_unsupported, a
malformed body denies :digest_encoding_invalid. Algorithm succession
is therefore a data change, not a format break.
6. Signature envelope
Signatures are detached and evidence-only. The package VERIFIES; it never signs, never accepts a private key on any public function, and never embeds public keys in artifacts (the host supplies trusted keys).
- Format: RFC 7515 compact + RFC 7797
b64=falseunencoded detached payload. Protected header:{alg: EdDSA, kid, crit: ["b64"], b64: false}. Ed25519 only; an unknownalgdenies. signed_attributes = {algorithm, content_digest, created_at, key_id, purpose}withpurposeone ofblueprint|deployment|federation-envelope; the signing input is the signature domain separator, a zero byte, and JCS(signed_attributes).- Because the digest covers
protocol_revision, identity members, extensions, andrequired_core_fields, a signature cannot be lifted onto a different artifact, revision, or purpose;key_idprevents key substitution. Akey_idmatching no supplied key is averified: falsecheck entry with:signature_key_unsupported— the package performs no key discovery, fetch, or trust selection. - Attestations use the identical envelope with a registered
kindand astatement_digest. The attestation kind registry is empty by design in this release. - Small-order Ed25519 public keys (identity and order-2 encodings)
reject
:signature_key_unsupported.
7. Evolution and negotiation
protocol_revision is a digest-covered body member. This release is
revision 1. A consumer's support declares an explicit revision SET
(never a range); a revision outside the set denies
:protocol_revision_unsupported — above-max and below-min alike, both
fail-closed.
required_core_fields is the producer's declaration of which covered
core members a consumer must honor. Three checks, all fail-closed:
every entry names a known core member (:required_core_field_unsupported),
is digest-covered (:required_core_field_not_digest_covered — an
evidence-only member laundered into a requirement is a tamper blind
spot), and is in the consumer's supported set.
Extension criticality
For each namespace → {criticality, payload} in extensions:
| Registry state | Declared critical | Declared optional |
|---|---|---|
| unregistered | deny :extension_unknown_critical | retained verbatim, quarantined, never executed |
| reserved | deny :extension_unknown_critical | retained verbatim; typed notice |
| active, criticality matches | supported | retained and executable |
| active, criticality mismatch | deny :extension_criticality_conflict | deny :extension_criticality_conflict |
| deprecated | supported + typed notice | supported + typed notice |
| retired | deny :extension_retired | retained verbatim; typed notice |
An unknown optional extension round-trips byte-exactly (decode → to_value → encode is a fixed point, property-tested). Lifecycle
asymmetry: optional→critical promotion requires a revision increment;
demotion does not. A retired namespace is never reused.
8. Extension registry
Namespace form: reverse-DNS-plus-path (com.example.commerce/graph),
lowercase, one /, length-ceilinged, and not parseable as an absolute
URI with a network authority (a namespace can never double as an
endpoint). The registry is COMPILED-IN data in the package — registry
content is a code release; there is no registry directory or file, and
drift between shipped code and shipped registry is unrepresentable.
Registered at this release:
| Namespace | Owner | Criticality | State |
|---|---|---|---|
com.example.commerce/graph | ExampleCommerce | critical | active |
com.example.commerce/classification-labels | ExampleCommerce | optional | active |
com.example.commerce/rubric-assertion | ExampleCommerce | optional | active |
com.example.platform/estate | ExamplePlatform | optional | active |
com.example/federation | Agent Blueprint Protocol | critical | active |
Critical-extension bodies validate only against a host-supplied schema
whose digest matches the registry's schema_digest; no schema denies
:extension_schema_unavailable, a mismatched digest denies
:extension_schema_digest_mismatch. Entry changes are owner-made,
changelog-recorded, and ADR-recorded. The registry's own digest is
bound into the corpus index but not into artifact digests: an artifact
must remain verifiable against a registry that legitimately gained
entries after it was produced; hosts wanting pinning use
build_identities.
9. Bounds algebra
Two bound families, never conflated with parse ceilings (those are tighten-only decoder limits):
- Operational (8 members, all REQUIRED — absent is
:missing_ceiling, never infinity):max_attempts,max_concurrency,max_cost,max_depth,max_descendants,max_elapsed_ms,max_fan_out,max_tokens. Pointwise narrowest meet; every narrowing emits clamp evidence. - Protected (5 members:
classification_ceiling,disclosure_ceiling,effect_impact_ceiling,authority_trait,approval_trait): ordinal lattices (plus set-monotone markers{pci, phi}on classification). Narrowing a protected bound denies:protected_bound_clamp_deniedby default; a host may opt into an acknowledge posture which always records evidence. Obligation families (authority, approval, effect impact) meet at the STRICTEST value; markers are retained regulatory obligations whose effective set is the UNION of sources — dropping one is a widening.
Laws (property-tested): meet is idempotent, commutative, associative;
not widens?(effective, host) universally; a clamp is emitted iff
effective ≠ requested on an operational field. model_policy and
data_bindings are not intersection inputs.
10. Compatibility and binding
A manifest's blueprint_release binds to exactly one Blueprint content
digest (digest equality only — no fuzzy match). build_identities
carry exact identities; compatibility verification is identity-exact
or error: a range expression, missing entry, or duplicate denies
(:compatibility_identity_inexact, :compatibility_entry_missing,
:compatibility_duplicate_entry). Binding verification is a pinned
deny-ordered check including attestation freshness
(:binding_attestation_stale) and descriptor-digest equality
(:binding_descriptor_mismatch — the tool rug-pull case).
11. Federation profile
A 23-member task envelope carried in A2A Task.metadata / MCP _meta
under the registered com.example/federation extension; the full
field-by-field A2A/MCP mapping (3 native, 5 partial, 15 extension
members) ships as docs/federation-mapping.md. Zero native wire
fields; no native transport. State codecs are lossy-aware: A2A
REJECTED/AUTH_REQUIRED deny crossing into MCP, UNSPECIFIED is
unmapped (:federation_state_unmappable); cancellation is a request,
never a terminal receipt. A Terminal Commitment digests task identity,
terminal state, result digest, result classification, compatibility
ref, authority-proof refs, and checkpoint-history commitment; ANY
divergence between receipts for one task identity denies
:federation_terminal_conflict — not just state divergence.
Federation.verify_commitment compares issuer/subject/audience
against the receiving context (:audience_mismatch). AgentCard
signing carries a protobuf field-presence pre-normalization on top of
JCS (documented in the mapping so adapters do not inherit silent
verification failure). Correlation grants nothing.
12. Error vocabulary
A closed typed set — %Error{code, subject, detail} — enforced
two-directionally by a build gate (an unreachable code or an undeclared
emission is a build failure). The 74 codes of this release:
base64url_invalid · base64url_padded · invalid_syntax · invalid_encoding · invalid_number · number_not_double_expressible · duplicate_member · trailing_bytes · unknown_bound · non_canonical_bytes · integer_magnitude · unknown_member · missing_required_field · invalid_type · invalid_constraint · invalid_cardinality · digest_algorithm_unsupported · digest_encoding_invalid · digest_mismatch · signature_algorithm_unsupported · signature_key_unsupported · signature_malformed · signature_not_verified · attestation_malformed · schema_dialect_unknown · schema_keyword_not_allowed · schema_keyword_value_invalid · schema_complexity_exceeded · schema_ref_unresolvable · schema_ref_cycle · schema_invalid_shape · predicate_op_unknown · predicate_path_unresolved · predicate_nodes_exceeded · extension_duplicate · extension_namespace_invalid · protocol_revision_unsupported · required_core_field_unsupported · required_core_field_not_digest_covered · extension_unknown_critical · extension_criticality_conflict · extension_retired · extension_schema_unavailable · extension_schema_digest_mismatch · extension_payload_forbidden · bound_unknown · missing_ceiling · bound_source_missing · bound_unit_mismatch · bound_value_invalid · protected_bound_clamp_denied · deployment_digest_mismatch · binding_incomplete · no_authoritative_recovery · binding_attestation_stale · binding_descriptor_mismatch · lifecycle_state_invalid · compatibility_identity_inexact · compatibility_entry_missing · compatibility_duplicate_entry · forbidden_portable_value · federation_state_unmappable · federation_mapping_conflict · nonportable_content · audience_mismatch · federation_terminal_conflict · corpus_index_invalid · corpus_hash_mismatch · corpus_file_set_mismatch · corpus_case_id_duplicate · corpus_count_mismatch · corpus_applicability_incomplete · corpus_empty · corpus_case_invalid
Plus the parameterized ceiling family {:ceiling, key} over the eight
decoder limit names. No authorization vocabulary exists in any
identifier — no :unauthorized in either polarity (source-scanned
gate).
13. Evidence and reconcile
reconcile(blueprint, deployment, inputs) is the one call per import:
canonical → digest → negotiation → structure → portability →
signatures → bind → bounds, in that pinned order. Its result is an
%Evidence{} of per-surface checks, effective bounds, clamp evidence,
and not_verified — which is non-empty BY CONSTRUCTION, always naming
at least the seven host-owned surfaces this protocol structurally
cannot establish: tenancy, live_policy, authority,
effect_ownership, execution, billing, evaluation_truth. A
caller cannot read an Evidence record and conclude "everything is
fine": the record itself names what it did not check.
14. Portability
Portable artifacts never contain secrets, private keys, live grants, tenant identifiers, raw endpoints, database primary keys, or backend engine identifiers — enforced structurally (member-name and value-shape denylists over the open regions, at any depth, name-spelling-normalized) and red-cased per class in the corpus. Honest limit, stated: the guard is necessary, not sufficient — opaque quarantined extension bodies are typed as unscanned, and portability claims attach only to schema-validated content. Concrete provider/model names, credentials, engine ids, and endpoints resolve host-side and are unrepresentable in self-fulfilling artifacts.
15. Conformance
The package ships a portable conformance corpus (priv/conformance/):
88 cases covering every required cell of the 16-surface × 29-class
applicability floor, full-registry golden artifacts, RFC 8785 number
vectors, and deterministic Ed25519 fixtures, at corpus digest
sha-256:k0ltF0KWcORTzkyr6dP7TPU9M2VdpewAvf_O8w2D7Wo. Corpus identity
is the digest of the domain-separated index — versioned by digest, not
by name. The loader is pure over %{path => binary} and verifies
per-file hashes, exact file set (both directions), counts, id-uniqueness,
and applicability totality; the report refuses a vacuous green.
A repo-side second-language verifier (conformance/verifier/,
TypeScript on Node ≥ 24, node: builtins only, never in the Hex
archive) independently recomputes every corpus verdict and integrity
check with its own scanner, canonicalizer, and node:crypto Ed25519
path; the release gate requires its report to be byte-identical to the
Elixir escript's over both the repo corpus and the built archive's
corpus. A mutation gate breaks the implementation at seven named
points and requires the corpus to go red on each — a vacuous case set
is a build failure.
16. The shipped surface
The facade (AgentBlueprintProtocol) delegates and never implements;
every public function carries a @spec (build-gated), every module a
boundary-stating moduledoc:
| Function | Contract |
|---|---|
decode_blueprint/1,2 | bounded, canonical-first Blueprint decode |
decode_deployment/1,2 | bounded, canonical-first Deployment decode |
decode_federation_envelope/1,2 | bounded TaskEnvelope decode |
canonical_bytes/1 | the artifact's canonical JCS bytes |
negotiate/2 | revision/required-field/extension evolution gate |
intersect/1 | bounds meet over blueprint/deployment/host sources |
verify_compatibility/2 | identity-exact build compatibility |
reconcile/3 | the one composed non-authorizing pass per import |
federation_mapping/0 | the 23-row A2A/MCP mapping as data |
Layered under it: the bytes layer (Json, Canonicalization,
Base64Url, Digest, Bounds), the artifacts (Blueprint,
Deployment, Extension, Registry as the one generic table-driven
engine, Schema as the bounded 2020-12 dialect + instance validator,
Predicate, Portability), the algebra (BoundsAlgebra,
Negotiation, Compatibility, Signature, Federation, Error,
Evidence, Reconcile, ExtensionRegistry), and the conformance
tooling (Conformance.Corpus/Runner/Report/Cli + the escript entry).
The module tree is strictly downward (bytes → algebra → artifacts →
conformance); the engine knows tables, never domains.