Templates
Templates declare types in Aqua Protocol v4: identity by content hash, derivation and narrowing, bounds, template resolution, compute declarations, and the shipped catalog
A template declares a type. It is itself a revision: a JSON Schema for the payloads of object revisions of that type, plus optional derivation metadata, optional shape bounds, and an optional compute declaration. Templates are identified by content hash — there are no template names on the wire; names are a resolution convenience only.
The normative source is the protocol specification in the aqua-rs-sdk-core repository; this page is the readable reference. Where they disagree, 03 — Templates wins.
Template identity
The identity of a template — its template hash — is the hash of its own canonical form (Hashing and Canonicalization), computed always with SHA3-256, regardless of any tree's algorithm and regardless of the template's own method field. The identity is used in two renderings:
- the bare digest (32 bytes) in catalogs and ledgers;
- the full multihash (
0x1620…) everywhere on the wire: as the naming value (revision_type) of object revisions, inderives_fromandancestry, in anchor structural links, and as the template's key in its own tree.
Because the identity is a content hash, any change to a template — schema, bounds, description strings, nonce, local_timestamp — mints a new type. Template authors MUST therefore pin nonce and local_timestamp to fixed literals; a freshly generated value would create a new type identity on every build.
Template trees
A template is distributed as a single-revision tree: one template revision keyed by its full SHA3-256 multihash, with no anchor and no previous_revision. Derivation hierarchy is expressed inside the template, never through chain structure — chaining a template to any parent revision would change its content hash and destroy its identity.
A template revision keyed under a BLAKE3-256 multihash is invalid: identity is SHA3-256, so hash verification fails.
template_meta and the bootstrap
template_meta is the template-of-templates. Every template revision declares as its naming value (revision_type) the full multihash of template_meta:
10x1620f3040850a8836717dd73e87d046723e11f9e9870e3b2e246803ad842fbf01155with exactly one exception: template_meta itself cannot name its own content hash, so it declares the genesis bootstrap hash — the SHA3-256 multihash of the ASCII string aqua:genesis:template_meta:
10x162087ea911a93f2698563b68b860f33fd7a568ca2391d4a227b532812d496039e74Both values classify a revision as a template. template_meta's schema constrains the body of templates (their schema, verification, derives_from, ancestry members).
Derivation
A template MAY derive from another template:
derives_from= the parent's full multihash. A template MAY instead nametemplate_metaitself as itsderives_from; that marks it as a family root, not a derived template, and contributes nothing to ancestry.ancestry= the derivation chain within the template's family,[root, …, parent], root first, where the family root is the nearest ancestor with no family parent.template_metanever appears in an ancestry. The last element MUST equalderives_from.ancestryMUST NOT exceed 3 entries — the maximum derivation depth is 4 (a family root plus three levels of children). Derivation depth is the length ofancestry; family roots have depth 0.
Narrowing
Narrowing is a derivation-time discipline for refinement templates: a child derived under it admits only payloads its parent also admits. The rules, checked property-wise on the two schemas:
- No new properties. Every key in the child's
propertiesMUST exist in the parent'sproperties. - No dropped requirements. Every entry of the parent's
requiredMUST remain in the child'srequired, or — if relaxed to optional — MUST still appear in the child'sproperties. - Constraint tightening. For each property present in both schemas, upper bounds (
maxLength,maximum,exclusiveMaximum) may only decrease, lower bounds (minLength,minimum,exclusiveMinimum) may only increase, a child'senumMUST be a subset of the parent's (or be replaced by aconstdrawn from it), a parent'sconstMUST be repeated identically, and a parent'spatternor bound keyword may be dropped only in favor of aconst. A keyword only the child declares is always a valid tightening.
Narrowing is checked when the child template is produced, and it is not re-checked at verification. On the wire, derives_from and ancestry assert family membership, not schema subsumption — consumers MUST NOT infer payload compatibility from lineage. In particular, the shipped signature and audit families are extension families: their concrete members add properties to a minimal abstract parent and were not produced under the narrowing discipline.
Closed payload schemas are what keep types analyzable: template schemas SHOULD declare additionalProperties: false at every object level whose member set is fixed. An open level defeats narrowing analysis for everything beneath it.
Bounds
A template MAY declare bounds — shape limits for trees of its type. Bounds are part of the template body and therefore part of its identity hash. When bounds is present, all six members MUST be present:
| Member | Type | Limits |
|---|---|---|
max_chain_depth | u16 | maximum chain depth |
structural_links | object | required (u8) and max (u8) structural links |
max_signature_branches | u8 | signature branches per node |
max_timestamp_branches | u8 | timestamp branches per node |
max_anchor_branches | u8 | anchor branches per node |
max_total_revisions | u16 | revisions per tree |
Resolution is nearest-declaration-wins: a template's effective bounds are its own declaration if present; otherwise the nearest ancestor's declaration, walking ancestry from the direct parent toward the root; otherwise the permissive default:
1{2 "max_chain_depth": 64,3 "structural_links": { "required": 0, "max": 4 },4 "max_signature_branches": 8,5 "max_timestamp_branches": 4,6 "max_anchor_branches": 4,7 "max_total_revisions": 10248}Protocol ceilings cap every declaration; no template can raise them:
| Ceiling | Value |
|---|---|
| structural links per anchor | 64 |
| compositional links per anchor | 64 |
compositional links with role reference per anchor | 64 |
| chain depth | 256 |
| branches per node | 256 |
| revisions per object tree | 4096 |
Enforcement status. In the core profile, declared bounds and the ceilings above are normative limits on producers, but their enforcement is advisory: the core verification procedure does not count revisions, branches, or links against them. Runtime environments that admit revisions incrementally SHOULD enforce effective bounds at admission time; verifiers MAY enforce bounds and ceilings and reject trees that exceed them.
Typing object revisions
An object revision claims its type through its naming value: revision_type = the template's full multihash. Verification of an object revision requires both:
- the naming value equals the resolved template's identity (recomputed, not trusted); and
- the object's
payloadsconforms to the template'sschema(JSON Schema draft 2020-12).
Template resolution
Templates are resolved in this order, identical at creation and verification:
- the tree's own revisions (an embedded template revision);
- the verifier's catalog of built-in templates (keyed by bare SHA3-256 digest);
- supplied linked trees / template sources.
At creation, typed trees reference their template via the genesis anchor's structural links and do not embed it; embedding happens only in export (Self-descriptive artifacts).
Three failure conditions arise from resolution and validation:
| Condition | Meaning | strict | offline | debug |
|---|---|---|---|---|
TEMPLATE_NOT_FOUND | the object's template is unresolvable | Fail | Warn | Warn |
ANCESTOR_TEMPLATE_NOT_FOUND | the template resolves but an ancestor in its derives_from chain does not | Fail | Fail | Warn |
SCHEMA_VALIDATION_FAILED | the template resolves and the payload does not conform | Fail | Fail | Fail — never policy-relaxable |
A verifier MAY recognize an unresolvable hash as a known template of a richer profile it does not implement and say so in its diagnostics, but the machine-readable outcome MUST remain TEMPLATE_NOT_FOUND — a verifier is never more permissive because the missing template is known-missing. ANCESTOR_TEMPLATE_NOT_FOUND exists because a child MUST NOT silently bypass invariants declared by a parent it cannot see.
Compute declarations
A template MAY carry a verification member declaring computations to be executed by a WASM runtime (state machines over tree content). Its wire shape:
| Member | Required | Content |
|---|---|---|
computations | yes | array of computation objects (below) |
host_dependencies | yes (MAY be empty) | capability names |
states | yes | state names |
terminal_states | omitted when empty | terminal state names |
Each computation object carries wasm (hex of the compiled WASM module bytes; 0x prefix optional) and wasm_hash (SHA3-256 digest of the WASM bytes), plus OPTIONAL source (inline code, its SHA3-256 hash, and a language identifier), build (a build recipe), and description. Unknown members MUST be rejected at every level of the declaration.
The core profile does not execute compute. Its obligations are:
- Static validation. Whenever a template carrying a
verificationmember is itself verified as a revision, each computation's hex-decodedwasmMUST NOT exceed 2 MiB (the size gate applies to the hex length before decoding) and its SHA3-256 digest MUST equal the declaredwasm_hash; whensourceis present, itscodeMUST NOT exceed 512 KiB and the SHA3-256 digest of its UTF-8 bytes MUST equal the declaredsource.hash. Digest comparisons tolerate an optional0xprefix and are case-insensitive. - Fail-closed refusal. When verifying an object revision whose resolved template chain carries any
verificationmember, a core-profile verifier MUST fail that revision withCOMPUTE_UNSUPPORTED— it has no runtime and refuses to guess. Verification of such trees requires a full-profile verifier.
No template in the shipped catalog carries a compute declaration, so this rule fires only for third-party templates.
The shipped catalog
The core profile ships 19 templates. Their identities (bare SHA3-256 digests) are normative:
| Template | Identity (bare digest) | Role |
|---|---|---|
template_meta | 0xf3040850a8836717dd73e87d046723e11f9e9870e3b2e246803ad842fbf01155 | template-of-templates |
anchor_template | 0x479a304927c47f4308d027a858060ce287a9bdb45f2203f8130574a73511e899 | naming value of anchor revisions |
file | 0x00f3abb3d74fc9dfc2b961cea906b3211716188f4de6f588180fdfcbbfa3fe53 | file-content genesis payloads |
signature_base | 0xbdc93b4152c0163e40d3bf8cb956e235f0150731c1e8c8b50d9c3b7abe50e4a7 | abstract parent of the signature suites |
signature_ed25519 | 0xbaf1d5d47eef50dcde3931956879bb30c5580064a92ce43ed4c6bd8b878b659a | Ed25519 signature revisions |
signature_eip191 | 0x57090c9095a2e9af36e9b6cb4574196fa973c44a210e703bd15dab2623dbd370 | EIP-191 signature revisions |
signature_p256 | 0x23a2cdd4618224a67235321e2dfeffac9ab1809175549d8b5402dd5c5376d81c | ECDSA P-256 signature revisions |
signature_webauthn | 0x2cdea1604c08b4e23f5415d8fcf885cf86d0a3e017cf4b7989c41aa99f3f2188 | WebAuthn signature revisions |
audit_artifact | 0x431668e53b2181311ec43db30ff4d4cf738059051829a5a4f3398c22440a16f3 | abstract root of the audit family |
audit_user_turn_marker (T1) | 0x9bf38992cb2cc1230edb6539a98e6d3c889c69aa09b1764a4b91b30cb6a36990 | opens a user turn; its revision hash is the turn id |
audit_user_prompt (T2) | 0x80143d8018fa7a0fa959c115ff7bf7da0d3363192db5202f2904b4626559ecc7 | the user's prompt |
audit_agent_thinking (T3) | 0xfe1d5fd50335d5d21f3f1976baadc9eab68a0506241ed5303bb8ad0a25a407f1 | agent reasoning |
audit_agent_tool_call (T4) | 0x13d08a7ea2a0dc5f4d5a380d9dd9456ec10392bc752f152517dd227bb272592f | a tool invocation |
audit_api_response (T5) | 0x6d1e300bbb0145cedc1a2ff19fd1ad0e0dcfcfde6a4a9b0d6ea67c7d31c4a129 | an attested API response |
audit_tool_result (T6) | 0xad46f51cfce3fd961acb2a3d9047b4c217c3da43c32addc41e8e11b84b5c4732 | a tool result |
audit_hitl_approval (T7) | 0xcd588f6994409f3088eef84d231027ba69a1d9d7193fcb1bf0d07a1716ba6560 | a human-in-the-loop decision |
audit_agent_response (T8) | 0xe36f3af11b2c6d5c94e2862e4a66db016ff8e8f5486fe72594daa7df480c5413 | the agent's response |
audit_round_anchor | 0xf174f2f669d102d3d74efb80248a08485fe2c40d18b2df5ccc7d225e794b63e7 | Merkle commitment closing a turn |
audit_session_close | 0x9069525103b9408f039a41454d25ed5ffc59aed9e46224cb19864f2e71e0c23b | closes an audit session |
Of the 19, 14 are resolvable by naming value in a core-profile verifier's catalog: file, the four concrete signature templates, audit_artifact, and T1–T8. The remaining five (template_meta, anchor_template, signature_base, audit_round_anchor, audit_session_close) are shipped and hash-pinned but deliberately outside the resolution catalog: nothing resolves an object's type through them by default. Trees using audit_round_anchor or audit_session_close carry or link their templates like any registry template.
The eleven audit templates form one family rooted at audit_artifact (each concrete template derives from it with an ancestry of exactly one entry). They give AI-agent workflows verifiable, per-turn audit trails; see Audit Trails for AI Agents. This family is a deliberate, bounded fork of the audit family published by richer Aqua profiles: the constraint content is identical, but the ancestry is re-rooted at audit_artifact, so all eleven identities differ from their full-profile counterparts. A verifier presented with a full-profile audit hash resolves nothing and reports TEMPLATE_NOT_FOUND.
Distribution. The sanctioned distribution channel for the audit family is the template registry (aqua-template-registry); the catalog resolvability of audit_artifact and T1–T8 is a transitional convenience, not a protocol guarantee. Producers MUST NOT rely on any verifier's catalog beyond the machinery templates; the interoperable way to ship a tree is self-descriptive export or explicit template sources.
Authoring your own template
The condensed flow, from the SDK's template authoring guide (data-only templates; WASM-carrying templates require the full aqua-rs-sdk):
Write the payload schema
A standard JSON Schema (draft 2020-12) for your payload. Set "additionalProperties": false on every object level, and constrain everything you can (pattern for DIDs, minimum for counters, maxLength for strings).
Write the template JSON
revision_type is always the template_meta multihash; method is the default hashing method for objects of this type (tree enables field-level selective disclosure, scalar is cheaper). Pin nonce and local_timestamp to fixed literals — pick the values once, commit the file, never touch it again. A derived template adds derives_from and ancestry.
Compute and pin the hash
Compute the template's canonical SHA3-256 hash once and pin it in your code, guarded by a self-check test that recomputes the hash from the template JSON and fails when it drifts. Any edit to the file mints a new type and orphans every artifact of the old type.
Create objects with the validated path
Use create_object_validated with your template tree as an explicit source. Plain create_object validates payloads only against the built-in catalog, so for a custom template it would silently build a revision from a payload that violates your schema. The validated path fails closed: TemplateNotFound, AncestorTemplateNotFound, or SchemaViolation with per-field errors.
Ship with self-descriptive export
export_tree embeds every referenced template and its full ancestry into the exported tree, so it resolves its own types anywhere. It fails closed on unresolvable templates; run the missing_templates lint first (or in CI).
Publish through the registry
For consumers beyond receivers of your exported trees, publish the template tree through the template registry under your publisher DID. Consumers retrieve it hash-pinned through the registry's fail-closed trust layer and pass it to the SDK as an explicit template source.
Self-descriptive artifacts
A tree can be exported as a self-descriptive artifact: a tree into which every referenced template — and every ancestor of every referenced template — is embedded as an ordinary template revision, keyed by its full multihash. Five normative rules:
- Closure. The embedded set is the transitive closure over each typed revision's naming value plus each embedded template's
derives_from/ancestry; signature, anchor, and template revisions contribute no naming values (they dispatch on machinery templates). - Fail-closed. If any template in the closure cannot be resolved, the export MUST fail with the unresolved identities; partial embedding is not permitted.
- Idempotence. Exporting an already self-descriptive artifact MUST be a no-op; already-embedded templates are not duplicated.
- Receiver-relative built-ins. "Built-in" is relative to the receiver, not the sender: a self-descriptive export embeds catalog templates too, so the artifact verifies on receivers with a different (or empty) catalog; an exporter MAY offer an opt-out at the cost of that portability.
- The missing-templates lint. The same closure walk without embedding yields the set of unresolvable template identities; publishers SHOULD run it before shipping a tree — a non-empty result means receivers will hit
TEMPLATE_NOT_FOUND.
Embedding changes no revision: templates enter the artifact under their own identities, and every existing hash and signature remains valid.
See also
- Hashing and Canonicalization — how the template hash is computed
- Data Model — revision kinds and how
revision_typeclassifies them - Verification — policies, stages, and the error vocabulary
- Template Registry — publishing and retrieving templates by hash
- Audit Trails for AI Agents — the audit template family in practice
