Insights

What VAID is, and why delegation can only narrow

For engineers: what a VAID actually is, the exact bytes that get signed, and the mechanism that makes delegation a one-way narrowing rather than a trust assumption. Apache-2.0, with the repo linked.

VAID is an open standard for verifiable agent identity, released under Apache-2.0: a canonicalisation spec, three conforming reference SDKs, and a reference mint. The source is public at github.com/solara-associates/vaid. This article is the engineer’s version of what it is and, specifically, how delegation works, because “delegation can only narrow” is a claim worth being precise about rather than taking on trust.

The primitive: a proof bound to one exact action

A VAID is not a session token and not a bearer credential for an API in the usual sense. It is a document that states an acting identity, the authority it carries, and its lineage, and every action signed under it carries a proof-of-possession that a verifier checks against the signer’s public key. The signing path is fixed and frozen as a conformance vector: a request payload of seven fields (the VAID identifier, method, path, body hash, tenant, timestamp and client nonce) is canonicalised with RFC 8785 JSON Canonicalisation Scheme, hashed with SHA-256, and signed with Ed25519 over that digest. Four headers carry the result on the wire: the base64-encoded VAID, a timestamp, a nonce, and the base64-encoded signature.

The important property is that the signature covers the exact request, not a general-purpose credential that happens to accompany it. A signature produced for one method, path and body is worthless against any other. That is what “capability-scoped” means concretely: not a label on the VAID document, but the fact that the thing being verified is the specific call, not a token that would authorise any call made while it is valid.

Three independent implementations, in Rust, Python and TypeScript, reproduce the identical SHA-256 digest and Ed25519 signature from the same fixed inputs, with no shared runtime between them. That is the interoperability guarantee, and it is proven against a frozen vector rather than asserted in prose: install any of the three SDKs and the package ships the harness to reproduce the frozen digest from what you actually installed, not from what a web page claims you installed.

cargo add vaid-pop vaid-client vaid-mint     # Rust
pip install vaid-pop vaid-mint vaid-langchain # Python
npm install vaid-pop vaid-client vaid-mint    # TypeScript

Minting and verifying, without a server

There is no server or database required to use any of this. A reference mint issues a VAID locally: it binds an agent class, a tenant, a scope boundary and a capability set to a freshly generated or supplied public key, and signs the resulting document with the mint’s own kernel key. A holder then signs individual requests with the key the VAID binds. A verifier checks two independent things: that the VAID document itself is authentic, by checking the issuer’s signature against the issuer’s published kernel public key, and that a given request was actually signed by the holder of that VAID’s key. Neither check calls back to the mint. Both are pure functions of public keys and bytes already in hand.

from vaid_mint import ReferenceIssuer, verify_vaid_authenticity
from vaid_pop import RequestSigner, build_request_auth_payload, verify_signed_payload

issuer = ReferenceIssuer.ephemeral(24, "vaid.example")
vaid = issuer.issue_vaid_with_key(
    agent_class="orchestrator", version="1.0.0", tenant_id="acme",
    parent_vaid=None, scope_boundary=["data.acme"], capability_set=["read"],
    public_key_der=agent_key.public_key().public_bytes_raw())

# later, anywhere, with no call back to the mint:
verify_vaid_authenticity(issuer.kernel_public_key(), vaid)
verify_signed_payload(payload, vaid["public_key_der"], signature)

This matters for engineering reasons beyond convenience. A verifier that needs no live service to check a credential can be embedded anywhere: in a browser, in a CI job, in a policy engine with no outbound network access. The cost of that property is that the mint’s own revocation state does not travel with the document either, which is a real limitation and the subject of a separate article in this series.

Delegation: minting a child, not forging a sibling

The claim worth examining closely is that delegation can only narrow. Here is the mechanism, stated precisely rather than asserted. When a VAID mints a child VAID, rather than a fresh, independent one, the mint’s attenuation check requires the child’s scope boundary to sit inside the parent’s and the child’s capability set to be a subset of the parent’s. A child is not permitted to request a scope its parent does not already hold, and the mint enforces this at issuance, not as a convention the caller is trusted to follow.

This is why “delegation can only narrow” is a mechanical property rather than a design intention. An orchestrator holding ["read", "write"] over data.acme can mint a worker scoped to ["read"] over the same prefix, or to ["read", "write"] over a narrower sub-path, but it cannot mint a worker holding ["admin"], or a worker scoped outside data.acme, because the attenuation check that gates issuance rejects exactly that. The lineage is recorded in the child document: identity and tenant are bound from the authenticated parent at mint time, never from a field the caller simply asserts, so a caller can only ever mint as itself, not as whoever it would prefer to be.

Third parties can now verify the whole chain, not just trust the mint

Until recently, a delegation chain could only be trusted by trusting the mint that issued it, because nothing let an outside party re-check the containment claim independently. That has changed: a third party holding an issuer’s kernel public key, plus the ancestor documents a presenter supplies, can now walk a full delegation chain and confirm that every child’s scope, capabilities, expiry and tenant genuinely sit inside its parent’s, at every hop, using the same matchers the mint applied at issuance time. Verification cannot drift from the check that gated issuance, because it is the same check, run again by someone who was not there when it first ran.

This has real limits worth stating because they are load-bearing, not footnotes. Full chain verification requires the presenter to retain and disclose every ancestor document; a chain whose earlier links were discarded fails closed as unverifiable, not as satisfied. The walk does not consult revocation anywhere along the chain. And a chain that crosses a kernel key, delegation that spans more than one issuing authority, additionally requires a detached consent attestation from the issuer that minted the parent, because nothing inside a VAID document otherwise proves that issuer agreed to the hop.

Getting started

The whole loop, mint, sign, verify, runs with nothing beyond the public packages above. The spec, the conformance vectors and the three reference SDKs are open source under Apache-2.0 at github.com/solara-associates/vaid, and there is an Agent Skill, vaid-skill, that wraps the same SDKs into four verbs, mint, present, verify and revoke, for Claude Code, Codex, Cursor, Gemini CLI and GitHub Copilot: npx vaid-skill to install it, or npx -p vaid-skill vaid --help to use the CLI directly with no skill installed at all.

VAID is still early, and the spec is shaped in large part by people actually building against it. If you are integrating agent identity into your own stack, the standard benefits from that pressure before it hardens further.

← All insights

Join the first cohort shaping the VAID spec.

VAID is an interoperability contract. If you are building on agent identity, help shape the standard as it evolves.

Join the first cohort