Specification · draft 0.1

VXP specification

Thirteen sections that define VXP's two public components, VXP Proof and VXP Relationship, the operation requirements every implementation meets, the native profile and the adapters for Poppy and PACT. Requirements carry stable identifiers you can link to.

Draft 0.1 · 21 October 2026, revised 22 October 2026Tested against Poppy draft 0.2 (Operations v1), PACT 1.0 and A2A 1.0.1License: Apache 2.0

1Introduction#

VXP is an open standard for work that personal agents do for people inside companies. Any company or developer can implement it without hiring SOVRAS and without using Poppy or PACT. It has its own profile (§8, experimental) and interoperates with Poppy and PACT through adapters.

VXP has two public components, each a contract with a format on the wire:

ComponentWhat it defines
VXP ProofSigned evidence a third party can verify. By digest, it binds the request, the exact terms of each revision, the approval given on them, the execution, the later read of the company's system, the declared outcome and any correction, and it states the level of evidence available.
VXP RelationshipThe authorized agreement between a personal agent and a company: the identity of both parties, the actions allowed in each direction, limits, validity and revocation. It includes requests that the company starts.

VXP also states operation requirements (§9): the behaviour every conforming implementation shows where an external request becomes an operation, such as executing only the exact revision that was approved, and only once. They are requirements, not a third component: VXP defines no interface for them, VXP Proof is how anyone outside can check they were met, and how an implementation enforces them inside is its own decision.

What each part is not. VXP Proof is not an execution engine: it records what was asked, approved, executed and observed. VXP Relationship does not replace the company's authorization: inside an agreement, each request still passes the company's own rules.

What a proof proves. An intact chain does not prove that the operation was fulfilled. VXP Proof proves who asserted what, over which exact terms, in which order, and what was observed when reading the company's system, with its root of trust named.

Prior art#

These techniques have precedents in Poppy operations, in payments and in 2026 IETF drafts: Schrock outcome-binding, Dogru Cedulon, and the AUDIT group being formed. VXP combines them in its contracts, with its own profile and adapters to Poppy and PACT.

Conventions#

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as described in BCP 14 (RFC 2119 and RFC 8174) when, and only when, they appear in capitals.

  • Normative text is the contract. Each requirement carries an identifier: I1 to I5, ID1 to ID3, E1 to E3, R1 to R17, P1 to P8 and O1 to O7.
  • Informative blocks describe the reference implementation by SOVRAS: its paths, storage and internal controls. They show one way to meet the contract and add no requirement.
  • Experimental marks parts that pass their tests and that no third-party agent uses yet.
  • Pending marks parts that are specified or announced and not yet done.

VXP Proof inherits its statement format, canonicalization, chain, levels, log and anchoring from §9 of the normative annex, the VXP design draft 0.1. That annex passed five rounds of critical review, each with its corrections. Identifiers the annex names with “sovras” are used as listed in Renamed identifiers.

2Why VXP exists#

Every day, companies sell, book, change deliveries and handle incidents inside their own systems. VXP comes from that work at SOVRAS, which builds agents that change orders, deliveries and reservations inside companies' systems. After each of those operations, the same questions come back:

  1. Who asked, and on whose behalf?
  2. Which exact terms did the person approve, and through which channel?
  3. What ran, and how many times?
  4. What did the company's system record afterwards?

When the request comes from a person's own agent, the company did not build that agent and cannot see its conversation. The answers have to travel with the operation. VXP writes them into contracts that any company and any agent can implement.

3Principles and scope#

The table says what kind of part each one is. Only the first four bind an implementation; adapters are optional, and implementation decisions bind no one.

PartKindSection
VXP ProofPublic component, format on the wire§6
VXP RelationshipPublic component, format on the wire§7
Operation requirements O1 to O7Behaviour every implementation MUST show; no interface§9
Native profile: vxp.json and /v1VXP's own discovery and endpoints Experimental§8
Poppy and PACT adaptersOptional. The external contracts, used literally§8
Implementation decisionsInformative. Paths, storage, key custody and internal controls of one implementation§8, §9

Principles#

  • An external protocol is spoken as it is written (I1).
  • Whatever the entry, one enterprise execution per resource (I2).
  • A channel identifier, a customer account and a delegation stay separate (§5).
  • What is not known is signed as unknown (P3).
  • The verifier derives the level of evidence; the issuer never declares it (P7).

Out of scope#

  • Payments: referenced (AP2, x402, Pix), not defined.
  • Agent identity and user sign-in: they belong to Poppy, PACT and OAuth.
  • Disputes and their resolution: each company's policy.
  • Proof that the person read something: the most that is proven is that they answered from the linked channel.
Informative · SOVRAS capability

Access through traditional channels

Access by agents through WhatsApp, web chat or voice is a capability of SOVRAS, not part of VXP. VXP works with any company interface, channel or system of record. SOVRAS detects a probable agent without treating that as identity (its output says prueba_identidad: false), offers the move to a structured, authenticated connection on the company's domain (vxp.json, poppy.json or the PACT Agent Card), and limits what a suspected agent can do in the channel. A write never runs in the channel. For the company to contact the agent, VXP Relationship is used, never the traditional channel.

4Architecture#

Several entries end in one normalized request, the operation requirements and one enterprise execution. VXP Proof and VXP Relationship come out of it. Diagrams of each part are in Architecture.

Personal agent (VXP native, experimental · Poppy · PACT · traditional channel)
        │
                ├── VXP native (EXPERIMENTAL): /.well-known/vxp.json + /v1
        ├── Poppy ── optional adapter (Poppy draft 0.2 contract, literal)
        ├── PACT ─── optional A2A adapter (PACT 1.0 contract, literal)
        └── WhatsApp · web chat · voice ── channels of one implementation, outside VXP
        │
   Normalized operational request
   (identity · authority · company · operation · correlation)
        │
      Operation requirements (§9), enforced by the implementation's own controls
        │
   one enterprise execution → read of the system
        │
   VXP Proof (signed chain)     VXP Relationship (agreements, signed outbound)

Interoperability rules#

I1

An adapter MUST respect the external contract literally: its objects, states, errors and discovery. Inside an adapter, VXP MUST NOT alter, extend or duplicate the discovery, authorization, operations or states of Poppy or PACT. The native profile (§8) is VXP's own form and coexists with the adapters on the same issuer, without replacing them.

I2

Whatever adapters exist, the enterprise execution MUST be one per resource. The entries produce separate operations, one per identity, and only one can take effect: the first that passes the precondition. The others end failed, without effect and with their evidence.

I3

The company does not need a conversational agent of its own: a deterministic intermediary serves the PACT A2A service and the Poppy API.

I4

What VXP adds travels in each protocol's extension points: a domain-named member in Poppy (Poppy §3.3) and metadata or extensions in A2A. Never as new values of their enums.

I5

Items specific to VXP MUST NOT leak into the catalogs of Poppy or PACT. For example, the relationship checkbox is not a scope of the Agent Card.

States: one internal model, each protocol's own enum outside#

E1

The ten states of design draft 0.1, plus expired, are the internal model: received, authorization_required, proposed, accepted, waiting_human, executing, pending_verification, succeeded, failed, cancelled and expired. The last four are final.

E2

Outside, each protocol sees its own enum: Poppy with proposed, in_progress, succeeded, failed, cancelled and expired; A2A with TaskState. In A2A, the internal state can travel in metadata.

E3

waiting_human covers two cases: the company's review before executing, and a contradiction after executing (P5).

Legal transitions

Any other transition MUST be rejected.

FromTo
(start)received
receivedauthorization_required, proposed, failed
authorization_requiredproposed, cancelled, expired, failed
proposedproposed (new revision), accepted, cancelled, expired, failed
acceptedwaiting_human, executing, cancelled (before executing), failed (permissions or precondition no longer hold)
waiting_humanexecuting, pending_verification, succeeded, failed, cancelled
executingpending_verification, succeeded, failed
pending_verificationsucceeded, failed, cancelled (stop confirmed), waiting_human (escalation)

Projection

In each profile the visible state MUST be the projection of the internal one, and a value outside that profile's enum MUST NOT be used. Under A2A and PACT, the internal state MUST travel in metadata["https://vxpprotocol.org/ext/native/v0.1"] as {"state", "a2a_state"}; under Poppy it MUST NOT go in the operation's state.

InternalPoppy operationsA2A 1.0.1 TaskState
receivedno operation yetTASK_STATE_SUBMITTED
authorization_requiredno operation yetTASK_STATE_AUTH_REQUIRED
proposedproposedTASK_STATE_INPUT_REQUIRED
accepted, waiting_human, executing, pending_verificationin_progressTASK_STATE_WORKING
succeededsucceededTASK_STATE_COMPLETED
failedfailedTASK_STATE_FAILED
cancelledcancelledTASK_STATE_CANCELED
expiredexpiredTASK_STATE_CANCELED, with the internal state in metadata
  • The state MUST NOT move to failed while it is not known whether the effect happened. That is pending_verification.
  • A read by the agent MUST NOT change the state by itself. It MAY trigger reconciliation against the system of record, and reconciliation MUST NOT repeat the action.

5Identity model#

Three different things that MUST NOT be merged:

WhatExampleWhat it establishes
Channel identifierA WhatsApp numberThat someone controls that channel. It identifies no single person when two customers share it.
Customer's identity at the companyThe canonical customer, linked with evidence to the customer in the ERPWhich account, and which resources, are theirs in the company's system.
Authorization delegated to the agentA grant with client_id, sub, scopes and authorization_detailsWhat that agent may ask, for that person, over which resources.
ID1

An account is associated with an agent only through an authentication the company controls (for example, a one-time code over a channel the company already has linked to the customer; the reference implementation uses a WhatsApp line), and evidence of it is kept. It MUST NOT be associated by a matching name, email or phone number.

ID2

An identifier that corresponds to more than one account MUST NOT be used on its own to link or to give out data.

ID3

The owner of an operation is the tuple (profile, client_id, sub, account). Another DPoP key, another sub or another client_id neither sees it nor is accepted on it. An operation of one profile MUST NOT be read, confirmed, cancelled or reused from another profile, even when client_id and subject match.

6VXP Proof#

Each operation produces a chain of signed statements. Each statement names the previous one by its digest, and is signed by the role that observed it: the company signs what it offers and what it asserts as the result; the operator signs what it received, what it executed and what it read back. A verifier therefore knows who said what at each link.

Format#

  • Serialization: one SD-JWT per statement (RFC 9901 §4), signed with ES256, without a Key Binding JWT in 0.1. Header {"alg":"ES256","kid":"<RFC 7638 thumbprint>","typ":"vxp-stmt+sd-jwt"}.
  • Canonical bytes: the payload MUST be exactly its JCS form (RFC 8785, integers only, amounts as strings), and every base64url segment MUST be canonical.
  • Common members: v, kind, iss, company, op, seq, prev, iat, aud (the agent's client_id), user (a per-company pseudonym) and body.
  • Digest of a statement: SHA-256 over the JWS signing input, header and payload, without the signature and without disclosures.
  • Digest of terms: the digest of {company, operation_id, revision, summary, terms, expires_at, user_approval_required}. terms MUST carry a fresh vxp_salt of at least 128 bits in each revision.
  • Personal data travels as selective disclosures, never in clear.

Statement kinds#

kindSigned byWhat it records
requestOperatorAt seq 0: channel, client_id, digest of what was asked, optional idempotency key digest
termsCompanyEach revision: terms_digest, capability, commitment, expires_at, company_review_required, read_back_terms
counterOperatorA counter-proposal from the agent, by digest
user_approvalOperatorApproval of the latest revision and its method; basis: "standing_permission" when the person authorized it beforehand, within a limit
company_reviewCompanyA decision by a person at the company, by role, after the customer's approval and before execution
executionOperatorThe single execution, on the approved revision and digest
read_backOperatorWhat the company's system showed afterwards, named by term
outcomeCompanyThe result: succeeded, failed, cancelled, expired or unknown
correction, revocationCompanyWhat became known after the final state (Corrections)

Chain rules#

A verifier MUST check these in order, for each seq; the first failure decides the error code and its seq. Two conforming verifiers MUST return the same verdict on any chain.

  1. SD-JWT format, canonical base64url, header, known kid for the iss, valid signature.
  2. Canonical payload.
  3. kind allowed for the signer's role.
  4. SD-JWT processing as in RFC 9901 §7.1.
  5. v, company, op, aud and user equal across the chain.
  6. seq without gaps and prev equal to the digest of the previous statement.
  7. After a final statement, only read_back, correction and revocation.
  8. Semantic order by kind: one approval of the latest revision, one execution after it, succeeded only after a matching system read-back.
  9. There is an outcome; a package without one is treated as unknown.

Rules P1 to P8#

P1

Binding by digest:

  • terms: the digest of the exact revision, including its commitment;
  • user_approval: the same digest and the approval method;
  • execution: the same digest;
  • read_back: what was observed, with the name of each term;
  • outcome or correction: the digest of the summary.
P2

Approval through a channel the company controls, when it applies: method: company_channel, with a display_code derived from the digest (below). In the native profile and in Poppy, approval is agent_asserted, and the evidence says so by the name of its root. Pending company_channel in the native profile and in Poppy.

P3

Unknown: if it is not known whether the effect happened, the company MUST sign outcome.state = "unknown". It MUST NOT stay silent or sign failed. Poppy sees in_progress and the operation stays open.

P4

What becomes known later MUST go as a correction on the latest final result, never as a second outcome. A correction to succeeded MUST cite, by digest, a later matching read_back.

P5

Contradiction: if it ran and the later read does not match what was approved (another value, or the resource is gone):

  • the read_back MUST be signed with matches_terms: false and what was observed;
  • the result stays unknown;
  • the operation moves to waiting_human;
  • it is not retried and no success is declared.

The verifier does not grant the read-back level.

P6

Not checkable: a capability without read-back MUST NOT emit succeeded. It signs read_back.method = "none" and outcome = unknown, once, and reading again adds no assertions.

P7

Level of evidence: the verifier derives it, and the issuer never declares it. As implemented today, without external witnesses and with the company's key held by the operator, the evidence is an attestation signed by the operator: intact and ordered, and not independent of SOVRAS. The verifier says so: single_root: operator, logged: false.

P8

Cross-check with the operation: the verifier MUST compare the visible state of the operation with the latest final statement (outcome or correction), not with the first outcome.

Approval through the company's channel#

A user_approval records one of two methods:

methodMeaningWhat it proves
agent_assertedThe agent said the user approvedOnly that the agent asserted it, with its DPoP key and in its session
company_channelThe user answered from the channel the company already has linked to themThat whoever controls that channel answered that revision. It does not prove civil identity.
  • The outgoing message MUST name the company and the requesting agent, and carry the revision's summary and its display_code.
  • The channel is whichever one the company has linked to the user: a messaging line, email, its own app. display_code is the first four bytes of SHA-256 over the UTF-8 terms_digest, read as an unsigned big-endian integer, modulo 1 000 000, padded to six digits. It is not a secret: it ties an answer to one revision.
  • The answer MUST come from the identifier linked to the user at the company, contain the display_code, be single-use and arrive before expires_at. The operator MUST limit attempts.
  • A message that could not be delivered approves nothing: the operation MUST stay proposed and MUST NOT run.
  • Under PACT every write MUST require company_channel.

Corrections and revocations#

A final state never changes. Later facts live in the chain and never rewrite it. A statement is never edited or deleted.

  • correction: {corrects, state, reason, note_digest?, read_back?}. corrects always points to the latest final statement, and its state becomes the current state for the verifier.
  • revocation: {revokes, reason}, when the referred statement was wrong. The verifier reports final_state: "revoked".

7VXP Relationship#

A relationship lets a company start a request toward the person's agent, within an agreement the person approved first. It always starts from a prior authorization by the user.

Birth#

The agent asks for the relationship in the Device Authorization (RFC 8628), with an authorization_details entry (RFC 9396) of this type:

authorization_details · relationshipJSON
{ "type": "https://vxpprotocol.org/rar/relationship",
  "actions": ["despacho.proponer_cambio", "incidencia.avisar"],
  "identifier": "relationship",
  "limits": { "max_per_day": 2, "duration_seconds": 2592000 } }
R1

actions MUST be a subset of the actions the company publishes in vxp_relationship.company_initiated_actions, inside its RFC 8414 metadata. The company MUST cap the limits at its own maximums (in 0.1: 10 per day and 90 days).

R2

The agent MUST declare its inbox in its Client ID Metadata Document, in extensions["vxpprotocol.org/relationship"].inbox_uri, on the same origin as its client_id. Without an inbox, the company MUST reject with invalid_authorization_details. Experimental

R3

The consent screen MUST show the relationship in business language: the company, the actions, the limit and the validity. The relationship MUST have its own checkbox; if the person unchecks it, the relationship is not created.

R4

On grant, the token response MUST carry relationships[] with relationship_id, state, actions, limits and expires_at, and the signed agreement: a JWS with typ: vxp-relationship+jwt, iss = the company's issuer, aud = client_id, sub, rel, actions, limits, iat, exp and jti.

R5

The relationship key MUST be published in the issuer metadata, in vxp_relationship.jwks_uri, and MUST be different from the proof keys and from the Poppy and PACT keys.

Company-initiated requests#

R6

The company MAY start a request only if all of these hold; otherwise it MUST reject before signing or sending anything:

  • the relationship exists and is in force;
  • the delegation that created it is still in force;
  • the action is in the relationship;
  • the daily limit has not been exceeded;
  • the resource belongs to that relationship's customer.
SituationError
The relationship does not existrelationship_not_found
The relationship or its delegation was revokedrelationship_revoked
The relationship expiredrelationship_expired
The action is not in the relationshipaction_not_authorized
The daily limit was exceededrelationship_limit_exceeded
The resource is not the customer'starget_not_found
R7

The message MUST be a JWS with typ: vxp-company-request+jwt carrying iss, aud (the agent), sub, rel, action, content, iat, exp (in 0.1: one hour) and jti. It is delivered by POST {inbox_uri} with {"message": "<jws>"}, and the company MUST record the outbound message and its HTTP result. The agent MUST verify the signature with the key anchored to the issuer and SHOULD reject a repeated jti. Experimental

R8

A request that proposes a change (despacho.proponer_cambio) MUST create a normal operation: a revision with terms signed by the company, user_approval_required: true and its VXP Proof chain. The message cites request_url, proposal_id and terms_digest. Proposing MUST NOT execute. The agent accepts, with the user's approval, through the accept endpoint.

R9

Accepting a proposal from the relationship MUST check again that the relationship and the delegation are still in force. If the person revoked in the meantime, the answer is 403 relationship_revoked and there is no effect.

Revocation, expiry and HTTP answers#

R10

Revocation MAY come from the user (console or company channel), from the company or from the agent (DELETE /v1/relationships/{id}). Revoking the delegation revokes the relationship. Once expired or revoked, no request goes through. Experimental route

R11

The agent MAY read its relationship with GET /v1/relationships/{id}; another sub of the same agent gets 404. Experimental route

R17

Answering a closed question over HTTP. When the company's request is a closed question, its content carries alert_id, options, reply_url = /v1/relationships/{id}/replies and expires_at. The agent answers with POST {reply_url} and {"alert_id", "decision"}. The company MUST check, in this order:

  • that the relationship belongs to that client_id and sub (otherwise 404 relationship_not_found);
  • that the relationship and the delegation are still in force (403 with the error of R6);
  • that the question exists in that relationship (404 alert_not_found);
  • that it was not answered before (409 already_answered);
  • that it has not expired (410 alert_expired);
  • that decision is one of the options (400 option_not_allowed).

A successful answer is 200 {alert_id, decision, receipt}, where receipt is a JWS vxp-reply-receipt+jwt, and produces only the effect of that option. The path over a Poppy conversation (R16) applies the same checks. Experimental route

Relationship over a Poppy conversation#

A Poppy agent today declares no inbox_uri and asks for no authorization_details, so under R2 it could not have a relationship. For such an agent, the relationship is asked for, received and answered inside its Poppy conversation (Poppy §7), without touching the Poppy contract: these are messages with data, and the agent reads them like any other event. This path was run once, on 21 October 2026, with a live personal agent through the Poppy adapter; no agent outside SOVRAS's own runs has used it yet.

R12

Asking. The agent asks in its own words in the conversation, with the account already linked. The company answers with a message whose data["vxpprotocol.org/relationship"] is {type: "relationship_offer", approval_url, actions, limits}. The actions MUST be only those the agent asked for and the company offers; if it named none, the closed question of its industry. Without a linked account, the answer asks to link it (sign_in_required).

R13

Approving. approval_url MUST be on the company's own domain and not reveal the agreement until the person identifies with a code sent through a channel the company has linked to them. Only the customer of the account linked to that session MAY approve it; another number receives no code. The agreement is shown in the industry's words (actions, limit and validity). On approval, the delegation MUST still be in force.

R14

Creation. The relationship is bound to that session's client_id, sub and delegation, with inbox poppy:conversation. The company writes in the conversation a message with {type: "relationship_agreement", relationship_id, actions, limits, expires_at, agreement}; agreement is the JWS of R4.

R15

Receiving. What the company starts (R6 to R8, unchanged) is delivered as a message event with role: company in the agent's most recent open conversation: the text of the question and {type: "company_request", relationship_id, action, …content, message} with the JWS of R7. Without an open conversation, the delivery is recorded as failed in the outbox.

R16

Answering. The agent answers in the same conversation with one of the options, as text or as data["vxpprotocol.org/relationship"] = {alert_id?, decision}. Once, before it expires, and with the relationship and the delegation in force. If the text matches more than one option, the company asks it to choose one. The company replies with {type: "reply_receipt", alert_id, decision, receipt}, where receipt is a JWS vxp-reply-receipt+jwt. Once the relationship is revoked, a late answer does not count.

Closed-question actions in 0.1, each offered only in its industry: alerta.confirmar_o_bloquear (commerce: “block” holds the dispatch) and alerta.mantener_o_liberar (hotels: “release” lets the booking go).

Pending
  • A third-party HTTP inbox. The inbox of R2 has been tested only with servers of SOVRAS's own; the live agent on the Poppy adapter used the conversation path.
  • Delivery retries with backoff.
  • The relationship over PACT (A2A).
  • Notifying the person when the agent is not reading its conversation.
  • The signed receipt of the 21 October answer was not kept; the answer itself is in the company's event log.

Reading, revoking and answering over HTTP (R10, R11, R17) and the HTTP inbox (R2, R7) use routes of the native profile, so VXP Relationship over HTTP has the same experimental maturity.

8Native profile and adapters Experimental#

/.well-known/vxp.json and the endpoints /v1/requests, /v1/proposals/{id}/accept|counter, /v1/requests/{id}/cancel|events|proof and /v1/relationships/{id} are experimental, and the document says so with "status": "experimental". They are VXP's own form: an agent that speaks neither Poppy nor PACT goes through the whole cycle with them. It discovers the company, identifies with private_key_jwt and DPoP, receives per-request authority with authorization_details, asks, receives a proposal, accepts, follows events, cancels, obtains the proof and keeps the relationship.

  • The native profile needs neither adapter (Native-only gateway).
  • The company MAY publish vxp.json, poppy.json and the PACT Agent Card at the same time.
  • The issuer, the authorization server and the metadata are the same for the three entries.
  • An agent that speaks Poppy or PACT does not need the native profile.

Pending Adoption of the native profile by a third-party agent.

Discovery#

https://<domain>/.well-known/vxp.json on the company's domain.

  • organization.domain MUST be the host that served the document, and that domain MUST be listed in vxp_proof.domains of the issuer's RFC 8414 metadata.
  • If poppy.json exists, its auth.issuer MUST be the same.
  • vxp.json MUST NOT carry the proof's trust members; the verifier MUST take them only from the issuer metadata.
  • capabilities lists only what the company decided to offer to external agents. access is public or delegated; admission is static (the grant is enough) or dynamic (the company decides per request: owner, precondition, policy).
GET /.well-known/vxp.jsoncaptured from the reference implementation
{
  "protocol": "vxp",
  "version": "0.1",
  "organization": {
    "name": "Ferretería Demo",
    "domain": "store.example"
  },
  "issuer": "https://store.example/e/ferreteria-demo",
  "endpoint": "https://store.example/e/ferreteria-demo/v1",
  "capabilities": [
    {
      "id": "pedido.consultar",
      "effect": "read",
      "access": "delegated",
      "admission": "static",
      "authorization_details_type": "https://vxpprotocol.org/rar/capability"
    },
    {
      "id": "pedido.reprogramar_despacho",
      "effect": "write",
      "access": "delegated",
      "admission": "dynamic",
      "authorization_details_type": "https://vxpprotocol.org/rar/capability",
      "commitment_modes": [
        "firm",
        "conditional"
      ],
      "outcome_proof": true
    }
  ],
  "limits": {
    "max_revisions": 3,
    "proposal_ttl_seconds": 900,
    "max_accept_attempts": 3
  },
  "outcome_proof": {
    "version": "0.1",
    "trust": "issuer_metadata"
  },
  "status": "experimental",
  "vxp": {
    "version": "0.1",
    "relationship": {
      "authorization_details_type": "https://vxpprotocol.org/rar/relationship",
      "company_initiated_actions": [
        "despacho.proponer_cambio",
        "incidencia.avisar",
        "alerta.confirmar_o_bloquear"
      ]
    },
    "proof": {
      "trust": "issuer_metadata"
    }
  }
}

Authorization#

  • The authorization server is the issuer's (RFC 8414). When the company also offers the Poppy adapter, it is the same one.
  • Native requests MUST carry resource = endpoint (RFC 8707).
  • The client authenticates with private_key_jwt over its Client ID Metadata Document, and the token MUST be bound with DPoP (RFC 9449, ES256). The agent's User ID for the company travels as an RFC 7523 assertion.
  • To operate on a resource, the grant MUST carry authorization_details (RFC 9396) of type https://vxpprotocol.org/rar/capability. The consent screen MUST show each identifier in business language; the authorization server MUST check that each identifier belongs to the linked account, or answer invalid_authorization_details; the token response MUST return the details granted.
  • The type vxpprotocol.org/operation is the grant of one revision. The two are complementary: the first authorizes asking, the second approving one exact revision.
authorization_details · capabilityJSON
{ "type": "https://vxpprotocol.org/rar/capability",
  "actions": ["pedido.reprogramar_despacho"],
  "identifier": "P-1042" }

Endpoints#

All with Authorization: DPoP and a DPoP proof.

Method and pathWhat it doesResponses
POST /v1/requestsCreates a request {operation, target, parameters}. MUST carry Idempotency-Key, except for a capability with effect: "read", which is answered at once and creates no request, no operation and no proof200 {operation, effect: "read", result} for a read; 403 authorization_required for a delegated read without the grant; 201 proposed; 202 authorization_required with what is missing; 400 idempotency_key_required, invalid_request; 404 target_not_found; 409 idempotency_key_reused, request_exists, resource_busy
GET /v1/requests/{id}Internal state, projections {poppy, a2a}, proposal, effects, cancellation, result, proof_url200; 404 request_not_found
GET /v1/requests/{id}/eventsTransitions {seq, from, to, at, reason, poppy, a2a}200
POST /v1/proposals/{id}/acceptAccepts that revision ({approved_by}: "user", or "standing_permission" only when the revision says user_approval_required: false, which the company sets only when the terms fall within a limit the person approved in advance)202 if not final; 200 if finished; 409 terms_changed, proposal_expired, proposal_not_acceptable, attempt_limit_exceeded; 403 authorization_required, user_approval_required; 400 invalid_request
POST /v1/proposals/{id}/counterCounter-proposal {proposed_terms, note?}200 with the new revision; 409 counter_rejected, negotiation_limit_exceeded
POST /v1/requests/{id}/cancelAsks to stop (Cancellation)200, 202
GET /v1/requests/{id}/proofThe proof package200
GET, DELETE /v1/relationships/{id}Reads or revokes the agent's relationship (R10, R11)200; 404 relationship_not_found
POST /v1/relationships/{id}/repliesAnswers a closed question {alert_id, decision} (R17)200 {alert_id, decision, receipt}; 404 relationship_not_found, alert_not_found; 403 with the error of R6; 409 already_answered; 410 alert_expired; 400 option_not_allowed

Idempotency and re-verification#

  • The key is scoped to (issuer, client_id, User ID, Idempotency-Key). Same key and same body (the JCS digest of {operation, target, parameters}) return the same request. A request waiting in authorization_required MUST be re-evaluated with the current token, without creating another.
  • Same key with another body: 409 idempotency_key_reused.
  • The key's digest MUST go in request.body.idempotency_key_digest.
  • accept and cancel are idempotent by revision and by state, without a key.
  • Before executing, the operator MUST re-evaluate, with the token of the accept call, the resource grant, the policy and the precondition. If anything fails, it does not execute.

Native-only gateway#

A company can implement VXP with the native profile alone. Nothing in VXP Proof, VXP Relationship or the operation requirements depends on Poppy or PACT:

  • Discovery is vxp.json; the issuer and its keys come from the issuer's RFC 8414 metadata, which then carries no Poppy member (poppy_domains) and an empty vxp_proof.pact.
  • A verifier anchors the proof by the issuer it knows (§10), with no adapter document in between.
  • A token request without resource = the native endpoint has no other profile to go to, and gets invalid_target (RFC 8707).
  • Agent authentication uses OAuth building blocks only: a Client ID Metadata Document, private_key_jwt, DPoP and RFC 8628.
Informative · reference implementation

The reference implementation runs this way with adaptadores: {poppy: false, pact: false}. Its test reference/test/nativo-solo.test.ts runs the whole cycle with an agent that signs with node:crypto directly, and checks the proof with the independent verifier anchored by the issuer alone.

Commitment of a revision#

Every revision issued MUST declare its commitment: {"mode": "firm" | "conditional", "requires": [...]}.

  • firm: if accepted and executed, the company expects to fulfil the terms without depending on a third party. requires MUST be empty.
  • conditional: the result depends on each condition in requires, which MUST NOT be empty. Each condition is the name of a term, and the read-back MUST observe it.
  • The mode MUST come from what the capability can execute and verify, never from what the agent asks. A capability whose effect depends on a system the company does not control MUST be proposed conditional.

Negotiation limits#

LimitMeaningWhen exceeded
max_revisionsHighest revision numberNo further revision; the request ends failed without effect (negotiation_limit_exceeded)
proposal_ttl_secondsValidity of each revisionThe revision expires; a later accept gets 409 proposal_expired
max_accept_attemptsaccept calls on a revision that is not current409 terms_changed up to the limit; then the request ends failed without effect (attempt_limit_exceeded)

The limits MUST NOT allow more than one automatic execution attempt per accepted revision.

Cancellation#

Cancelling is asking to stop.

  • In received, authorization_required or proposed: cancelled, without effect.
  • In accepted or waiting_human, before execution starts: cancelled, without effect.
  • In executing or pending_verification: a request to stop. The state MUST NOT change because of it. The response MUST be 202 with cancellation: {requested_at, status: "requested", reversal_promised: false} and the known effects.
  • In a final state: returned as it is.

The company and the operator MUST NOT claim that an effect was reverted unless it was reverted and read back. A request that ends failed or cancelled after execution started MUST carry effects[] with {system, change, applied, reverted?, status?}.

Adapters#

AdapterDiscovery and identityWhere VXP travels
Poppy draft 0.2poppy.json, Poppy sign-in, sessions, conversations and Operations, as writtenThe extension vxpprotocol.org/proof in poppy.json and in the agent's client metadata; a domain-named member in each operation with effect, only for an agent that lists the extension
PACT 1.0 over A2A 1.0.1Agent Card, PACT agent identity and delegated authority, as writtencapabilities.extensions[] with https://vxpprotocol.org/ext/proof/v0.1, required: false; message.metadata next to pact.receipt, which is not touched

In both, the proof's trust members are published in the issuer's RFC 8414 metadata as vxp_proof, and any copy in poppy.json or the Agent Card MUST be identical in those members.

Renamed identifiers#

VXP is independent of SOVRAS. The protocol identifiers that the annex names with “sovras” are used as follows, from 22 October 2026.

Annex 0.1VXP 0.1
/.well-known/sovras.json, "protocol": "sovras"/.well-known/vxp.json, "protocol": "vxp"
sovras_outcome_proof (RFC 8414 metadata)vxp_proof
typ: sovras-stmt+sd-jwttyp: vxp-stmt+sd-jwt
typ: sovras-checkpoint+jwttyp: vxp-checkpoint+jwt
sovras_salt (in the terms)vxp_salt
binding: sovras.request, sovras.intentvxp.request, vxp.intent
sovras.io/outcome-proof, https://sovras.io/ext/outcome-proof/v0.1vxpprotocol.org/proof, https://vxpprotocol.org/ext/proof/v0.1
https://sovras.io/rar/capability, https://sovras.io/vxp/rar/relationshiphttps://vxpprotocol.org/rar/capability, https://vxpprotocol.org/rar/relationship

vxpprotocol.org was registered on 22 October 2026 and is under SOVRAS's control, so these identifiers use a domain the publisher controls, as Poppy requires. Pending Publishing the site and these documents on that domain.

Implementation paths#

VXP fixes the well-known locations (/.well-known/vxp.json and the issuer's RFC 8414 metadata) and the native endpoints under the endpoint that vxp.json declares. Every other location is discovered: the key sets, the log's checkpoint and the proof bundle are read from vxp_proof in the metadata. Those paths are an implementation's own choice and carry no meaning.

Informative · reference implementation
WhatPath in the reference implementation
Company proof keys{issuer}/vxp/proof-jwks.json
Operator issuer and keys{origin}/vxp/operator, …/proof-jwks.json
Log origin, checkpoint, consistency, keys{host}/vxp/log/v0, {origin}/vxp/log/checkpoint, …/consistency, …/jwks.json
Relationship keys{issuer}/vxp/relationship-jwks.json

Until the evening revision of 22 October 2026 these paths were under /sovras/, and the implementation still answers there as an alias. Packages signed before that revision name the log …/sovras/log/v0 inside their signed content; they are kept exactly as signed and verify as they are.

9Operation requirements, security and privacy#

Where an external request becomes an operation on a company's system, every conforming implementation MUST show the behaviour below. A valid identity is a starting point: what passes is the intersection of what the person delegated, what the company allows and what the resource admits. VXP defines no interface for these rules and does not prescribe how they are built. Each one is written elsewhere in this specification and gathered here.

Rules O1 to O7#

O1

Identity and owner. The account is associated only through an authentication the company controls; an ambiguous identifier does not link; the owner of an operation is (profile, client_id, sub, account). ID1 to ID3.

O2

Scope. What is asked fits in the delegation (per-request authorization_details, RFC 9396) and in what the company publishes; without scope, authorization_required. §8.

O3

Own resource. Someone else's resource, a missing one and an unlinked one answer the same way (operation_not_found or target_not_found). R6.

O4

Terms and commitment. The company signs the exact revision with its commitment (firm or conditional); a change of terms is a new revision, approved again. P1, Commitment.

O5

Company review. When its rule requires it, a person at the company decides on the same digest, after the person. E3.

O6

One execution. Per resource, even when several entries arrive; with a precondition and Idempotency-Key. I2.

O7

Read-back. Without a read-back there is no succeeded; a contradiction moves to waiting_human. P5, P6.

Requirement, evidence and control#

Three different things that MUST NOT be confused:

LayerWhat it isWho defines it
Required behaviourRules O1 to O7: what an interoperable implementation does where a request becomes an operationThis specification (normative)
EvidenceWhat VXP Proof records so that anyone can check the behaviour afterwards: the terms digest (O4), the approval on it (O4), the review (O5), the single execution (O6), the read-back (O7)This specification (normative), §6 and §10
Internal controlsThe catalog, policies, checks and locks that enforce the rules inside one company's systemsEach implementation (not part of VXP)

A rule that the evidence cannot show (for example O3, the owner of a resource) is still required; the verifier only reports what the chain carries.

Informative · reference implementation

In the reference implementation by SOVRAS, these internal controls are what SOVRAS calls its Capability Gate. They are SOVRAS's own mechanism, not part of VXP:

StepWhat it does
CatalogThe capabilities the company released, and the actions it lets itself start
Prior readWhether the resource belongs to the linked customer in the ERP
PolicyPersonal-agent authority: scope ∩ released capabilities ∩ owner; company review by its rule
Commitmentfirm or conditional, from what the capability can execute and verify
ExecutionOne, with a precondition
Read-backChecks the effect in the system
ProjectionFrom the internal state to Poppy and A2A

Security#

Threats covered by the proof and how, with what remains open.

ThreatHow it is covered
Forgery by an outsiderSignature, RFC 7638 kid and anchoring of keys to the company's issuer (§10)
Forgery by the operator on the company's behalfsingle_root: "operator" unless there is verifiable evidence otherwise. Residual: with the operator holding the keys and no witnesses, the operator could sign a complete alternative chain
The company lies about its ERPNo signature solves it. Read-back, third-party evidence and an honest level label
Replayop, seq, prev, aud and user equal across the chain and verified
Terms changed after approvalterms_digest covers summary and terms; approval and execution must match it
Approval diverted to another revisiondisplay_code bound to the digest, single use, attempt limit
Two contradictory succeeded on one resourcePrecondition before executing and one operation in progress per resource. Residual: across separate packages, only a log with witnesses
Another agent or sub rewrites an operationFull owner on reuse (ID3) and a counter for each revision the agent originates
A digest that confirms a suspected personal valuevxp_salt in the terms. Residual: request_digest has no salt
“The user approved” said by the agentagent_asserted is labelled as such, with the level operator_reported_agent_approval

Privacy#

  • The log keeps no personal data: only blinded leaves and unlinkable tags.
  • The user member is a pseudonym per company. A pseudonym is still personal data while the extra information exists.
  • Everything personal travels as a selective disclosure; the agent reveals to a third party only what its user decides.
  • The read-back keeps the values that are compared, not the body of the system's response.
  • Keys, issuers, pseudonyms and tags are per company. A statement of one company MUST NOT validate with another company's key, and a statement of another operation MUST NOT be inserted into a chain.

Errors#

WhereCodes
Native requestsidempotency_key_required, invalid_request, target_not_found, idempotency_key_reused, request_exists, resource_busy, request_not_found, authorization_required
Proposalsterms_changed, proposal_expired, proposal_not_acceptable, attempt_limit_exceeded, user_approval_required, counter_rejected, negotiation_limit_exceeded
Authorizationinvalid_authorization_details
Relationshiprelationship_not_found, relationship_revoked, relationship_expired, action_not_authorized, relationship_limit_exceeded, target_not_found
Closed-question answersalert_not_found, already_answered, alert_expired, option_not_allowed
Proof endpointsoperation_not_found: someone else's and a missing operation answer the same way
Poppy adapterextension_required (HTTP 403), the Poppy §3.3 mechanism
VerifierThe codes of the chain rules, plus issuer_not_anchored, pact_receipt_mismatch, pact_receipt_invalid and pact_receipt_not_anchored

10Independent verification#

Anyone can check a proof package: the agent, a bank, an arbitrator, an auditor. The verifier receives the package, an anchor it knows on its own account (the company's domain or issuer, never a document handed over by whoever presents the package), and its own policy.

Anchoring the issuer and its keys#

  1. Start from the anchor, with what each protocol gives the agent: the company's domain under Poppy, the brand's domain or the PACT authorization server under PACT, or the issuer the verifier already knows.
  2. Fetch the issuer's RFC 8414 metadata and require its issuer to be identical, character by character.
  3. Take the trust members from that metadata (vxp_proof), with the keys under the issuer.
  4. Require any copy in poppy.json or the Agent Card to be identical in those members.
  5. On any failure, including a missing or malformed document, answer issuer_not_anchored and keep running.

This anchors keys to whoever controls the issuer, not to the company as a legal person.

Algorithm and output#

  1. Anchor the issuer.
  2. Apply the chain rules to the whole chain; reject on the first failure with its code.
  3. Derive the levels from the chain, not from claimed_assurance.
  4. With the operation the agent saw: recompute terms_digest, require the salt, compare state and summary digest, and check observed values against the terms of the same name (P8).
  5. With a log: verify inclusion and consistency, and at least one cosignature by a witness that is not the operator. Only then grant logged.
  6. Report single_root: "operator" unless there is verifiable evidence of another root.

Output: {valid, company, anchor, key_source, final_state, revision, approval, derived_assurance[], single_root, custody_declared, logged}, plus pact_interface and pact_receipt with a PACT anchor or a receipt citation.

Assurance levels#

Each level is named by its real root: who asserts it with their signature. No field says verified: true; a verifier states its policy in these terms.

LevelGranted whenRoot of trust
issuer_key_attestedoutcome validly signed by the key anchored to the company's issuerWhoever controls that key
operator_observedThere is an execution signed by the operatorThe operator
operator_reported_read_backA read_back with system_read_back and matches_terms: trueThe operator, who asserts it read the company's system
operator_reported_channel_approvaluser_approval.method = company_channelThe operator, who asserts the answer arrived from the linked number
operator_reported_agent_approvaluser_approval.method = agent_assertedThe operator, who asserts the agent said the user approved
logged PendingInclusion in a checkpoint cosigned by at least one witness outside the operatorThat a witness is honest
time_stamped PendingAn RFC 3161 stamp from an external authorityThe time-stamping authority
third_party_signed PendingA third-party artifact verified against its digestThe third party
third_party_reference PendingA third-party identifier that can be queried againThe third party, when someone queries
user_confirmed PendingLater confirmation by the user through the company's channelThe user
The first five are implemented. The last five are specified, and no verifier grants them yet.

What a verifier can conclude today#

Established, as an operator attestation

  • The chain has not changed since the verifier received it.
  • It is coherent and ordered, and each link was signed by the stated role.
  • The execution was on the approved revision.
  • Nobody other than whoever controls the issuer assembled it.

Not established yet

  • Independence from the operator: there are no external witnesses, the operator holds the company's key, and there is no second independent issuer.
  • That what was read in the company's system is true, or that a channel approval happened, without trusting the named root.

The repository includes three verifiers with no code shared with the issuer: two in Python and one in JavaScript that runs in the browser (Verify). The same team wrote them and the issuer, so they show that the rules are implementable and agree on every vector, not that someone outside checked them. Their results are in Conformance.

Reading a verdict#

A valid signature, the operator's testimony and an independent confirmation of a fact are three different things. A verdict keeps them apart:

QuestionWhere the verdict answers itWhat it does not say
Integrity: has the chain changed, is it complete and ordered?valid, and on failure error with its seqThat the operation was fulfilled
Provenance: who signed each link, anchored to whom?company, anchor, key_source; each statement's role by kindWho the company is as a legal person
Authority: what was approved, by which method?revision, approval (agent_asserted or company_channel)That the person read the terms
Declared result: what the company asserts happenedfinal_state, from the latest outcome or correctionThat the assertion is true
Corroboration: what supports it beyond the signaturederived_assurance[], single_root, custody_declared, loggedIndependence, while single_root is operator and logged is false

Key rotation and historical verification#

  • Statements name their key by kid (RFC 7638). An issuer that rotates a proof key SHOULD keep the earlier public key in its JWKS for as long as proofs signed with it are expected to be verified, and MUST NOT reuse a retired key.
  • A key removed from the JWKS no longer verifies against live material: the verifier answers unknown_key. Against material saved while the key was published, the same proof still verifies. A verifier that uses saved material SHOULD say so, with the date the material was obtained.
  • Keys generated in memory and lost when a process stops make every proof they signed unverifiable unless their public keys were saved. Implementations SHOULD keep proof keys persistent.
Informative · reference implementation

With VXP_KEYS_DIR, the reference implementation keeps its operator, log and company keys across restarts, and a rotation keeps the earlier public key published. Its tests reference/test/llaves.test.ts and the key test in prueba-resultado.test.ts cover restart, rotation, retirement and verification against saved material. Before the evening revision of 22 October 2026 its keys lived in memory; that is why most proofs of 21 October can no longer be checked by signature.

11Examples#

Every object in this section was captured from the reference implementation in the evening run of 22 October 2026: a native-only gateway (no Poppy, no PACT) with persistent keys, against a test company. The run is reproducible with reference/scripts/recorrido-nativo.test.ts, and its signed files and public material are in the downloads. Hosts are shown as store.example and agent.example, and long tokens are shortened. Operations run with live agents on 21 October 2026, during SOVRAS's production runs with test companies, are documented in Examples: personal agents through the Poppy adapter, among them SOVRAS's beta agent Nova AI, and a web agent identified as Grok.

Asking without authority#

POST /v1/requests without a grant for the order returns authorization_required with exactly what is missing.

POST …/v1/requests → 202captured from the reference implementation
{
  "request_id": "req_eSTKa7J-MERu",
  "operation": "pedido.reprogramar_despacho",
  "target": {
    "order_ref": "P-1042"
  },
  "state": "authorization_required",
  "projections": {
    "poppy": null,
    "a2a": "TASK_STATE_AUTH_REQUIRED"
  },
  "authorization": {
    "error": "authorization_required",
    "authorization_details": [
      {
        "type": "https://vxpprotocol.org/rar/capability",
        "actions": [
          "pedido.reprogramar_despacho"
        ],
        "identifier": "P-1042"
      }
    ],
    "device_authorization_endpoint": "https://store.example/e/ferreteria-demo/oauth/device",
    "resource": "https://store.example/e/ferreteria-demo/v1"
  }
}
Token response after the person approvescaptured from the reference implementation
{
  "access_token": "nt_pt_q…",
  "token_type": "DPoP",
  "expires_in": 3600,
  "authorization_details": [
    {
      "type": "https://vxpprotocol.org/rar/capability",
      "actions": [
        "pedido.reprogramar_despacho"
      ],
      "identifier": "P-1042"
    }
  ],
  "relationships": [
    {
      "relationship_id": "rel_X9sUhyHhjsLp",
      "state": "active",
      "actions": [
        "despacho.proponer_cambio"
      ],
      "limits": {
        "max_per_day": 2
      },
      "expires_at": "2026-10-22T23:30:29.000Z",
      "agreement": "eyJhbGciO…"
    }
  ]
}

A signed proposal#

Proposal, revision 1captured from the reference implementation
{
  "id": "prop_op_XzdILOmlgiae_r1",
  "revision": 1,
  "state": "proposed",
  "summary": "Cambiar el despacho del pedido P-1042 (Taladro percutor 800 W) del 2026-10-24 al 2026-10-28, a Calle Ejemplo 100, Santiago, sin costo.",
  "terms": {
    "pedido": "P-1042",
    "producto": "Taladro percutor 800 W",
    "despacho_actual": "2026-10-24",
    "despacho_nuevo": "2026-10-28",
    "direccion": "Calle Ejemplo 100, Santiago",
    "costo": {
      "amount": "0",
      "currency": "CLP"
    },
    "vxp_salt": "hRQxNKbVg-MdR_aCsFbHYQ"
  },
  "commitment": {
    "mode": "firm",
    "requires": []
  },
  "expires_at": "2026-10-22T22:45:29.356Z",
  "user_approval_required": true,
  "limits": {
    "max_revisions": 3,
    "revisions_left": 2,
    "max_accept_attempts": 3,
    "accept_attempts_left": 3
  }
}

The chain and the verdict#

Six statements, shown as processed payload bodies. Statements signed by the company and by the operator alternate as in Statement kinds.

Proof chain (processed)captured from the reference implementation
[
  {
    "seq": 0,
    "kind": "request",
    "signer": "operator",
    "prev": null,
    "body": {
      "channel": "vxp",
      "client_id": "https://agent.example/agent.json",
      "request_digest": "sha-256:mfJleoso5oEyt_pqJS3OtbmYam7cyncRBrAcqbKfIrI"
    }
  },
  {
    "seq": 1,
    "kind": "terms",
    "signer": "company",
    "prev": "sha-256:kAO5f-JoCSonqCQnG_1vki1T01wmqh4wDS5YK74U0v4",
    "body": {
      "capability": "pedido.reprogramar_despacho",
      "commitment": {
        "mode": "firm",
        "requires": []
      },
      "company_review_required": false,
      "expires_at": "2026-10-22T22:45:29.356Z",
      "read_back_terms": [
        "pedido",
        "despacho_nuevo"
      ],
      "revision": 1,
      "terms_digest": "sha-256:LexccQ2IrXTwkx-zM3KJ3m2Sf5-YHsqzs-po_QCxAC0",
      "user_approval_required": true
    }
  },
  {
    "seq": 2,
    "kind": "user_approval",
    "signer": "operator",
    "prev": "sha-256:lCNrubnR6MLwUhwhcNUCWaqAKxFGOVWIlx3Rafs8v08",
    "body": {
      "method": "agent_asserted",
      "revision": 1,
      "terms_digest": "sha-256:LexccQ2IrXTwkx-zM3KJ3m2Sf5-YHsqzs-po_QCxAC0"
    }
  },
  {
    "seq": 3,
    "kind": "execution",
    "signer": "operator",
    "prev": "sha-256:wk4J9m_VE1zzlLhc1QEZ7l3m7USokQiF9gtICMNft5U",
    "body": {
      "attempt": 1,
      "capability": "pedido.reprogramar_despacho",
      "release": "sha-256:u1TBBxJXTKta36K5UI_j_myhQaHzbPP2HcF4rBKGxPA",
      "revision": 1,
      "terms_digest": "sha-256:LexccQ2IrXTwkx-zM3KJ3m2Sf5-YHsqzs-po_QCxAC0"
    }
  },
  {
    "seq": 4,
    "kind": "read_back",
    "signer": "operator",
    "prev": "sha-256:Gs2GOu4azZ6aE1VaKjm2Edj91Zya-VP4zP9Fbc71VJw",
    "body": {
      "matches_terms": true,
      "method": "system_read_back",
      "observed": {
        "_sd": [
          "g1MZFKspn_fcTPBy5COpYIVc6rPjeQYv9bF7QBCrv5g"
        ],
        "despacho_nuevo": "2026-10-28",
        "pedido": "P-1042"
      },
      "source": "company_system"
    }
  },
  {
    "seq": 5,
    "kind": "outcome",
    "signer": "company",
    "prev": "sha-256:jfSCFRNR4QjvnD9VjmaNWdhfAg4ftVxMWy5xLrrT6Jo",
    "body": {
      "claimed_assurance": [
        "issuer_key_attested",
        "operator_observed",
        "operator_reported_read_back",
        "operator_reported_agent_approval"
      ],
      "result_summary_digest": "sha-256:aKs3uy77c1osgD7qSgTqNJ0_YITroc5iwoYHqFmSl6s",
      "revision": 1,
      "state": "succeeded",
      "terms_digest": "sha-256:LexccQ2IrXTwkx-zM3KJ3m2Sf5-YHsqzs-po_QCxAC0",
      "third_party": []
    }
  }
]
Verifier verdictcaptured from the reference implementation
{
  "valid": true,
  "final_state": "succeeded",
  "revision": 1,
  "approval": "agent_asserted",
  "derived_assurance": [
    "issuer_key_attested",
    "operator_observed",
    "operator_reported_read_back",
    "operator_reported_agent_approval"
  ],
  "single_root": "operator",
  "custody_declared": "operator_hosted",
  "logged": false,
  "company": "https://store.example/e/ferreteria-demo",
  "anchor": "issuer",
  "key_source": "issuer_metadata_live",
  "profile": "issuer",
  "statements": 6,
  "log_inclusion": "ok",
  "tree_size": 6,
  "live_tree_size": 6,
  "consistency": "ok",
  "witnesses": 0,
  "operation": "matches"
}

A company-initiated request#

The relationship as granted in the token, and the company's request delivered to the agent's HTTP inbox (Experimental). The delivery value is the reference implementation's own outbox label.

Relationship and outbound requestcaptured from the reference implementation
{
  "relationship": {
    "relationship_id": "rel_X9sUhyHhjsLp",
    "state": "active",
    "actions": [
      "despacho.proponer_cambio"
    ],
    "limits": {
      "max_per_day": 2
    },
    "expires_at": "2026-10-22T23:30:29.000Z",
    "agreement": "eyJhbGciO…"
  },
  "delivery": {
    "outbox_id": "out_c_bdRdvaL42s",
    "delivery": "entregado",
    "http": 202,
    "content": {
      "request_id": "req_1tEWlrG-_6eq",
      "request_url": "https://store.example/e/ferreteria-demo/v1/requests/req_1tEWlrG-_6eq",
      "proposal_id": "prop_op_wHbfb8mjcZI7_r1",
      "operation_id": "op_wHbfb8mjcZI7",
      "summary": "Cambiar el despacho del pedido P-1043 (Escalera de aluminio 6 peldaños) del 2026-10-27 al 2026-10-30, a Calle Ejemplo 100, Santiago, sin costo.",
      "terms_digest": "sha-256:u-oBOmfP_-UahdQqZhwaNaeQAq1ro4hzhaDCy69JhG8"
    }
  }
}

12Tests and conformance#

Conformance by role#

  • Conforming issuer (company and operator): meets O1 to O7; emits the chain with the chain rules; publishes the trust members with a truthful custody; never emits succeeded without a matching read-back; puts no personal data in clear; and, if it offers the Poppy or PACT adapter, keeps that protocol's official tools passing with the extension active.
  • Conforming verifier: implements SD-JWT processing (RFC 9901 §7.1), the chain rules and the verification algorithm with anchoring, and obtains exactly the results of the test vectors, including the codes and the seq of the negative cases.
  • Conforming agent (SHOULD): lists the extension, verifies each package, keeps it with its inclusion proofs, and shows the user the derived level, not the claimed one.

Three kinds of tests, never added together:

KindWhat it isWhat it does not show
Official tools of other protocolsThe Poppy validator (draft 0.2 kit) and the PACT Identity suite, unchangedThe Poppy validator covers discovery and conversations; it skips sign-in and Operations, so its result is not full Poppy conformance. The official PACT delegated suite runs only against PACT's demo airline
Own adapter testsTests written by SOVRAS for the adapters, including the delegated PACT flow with the official PACT clientThat a third party agrees with them
VXP testsThe reference suite, network tests, mutation tests, the native-only test and the vectorsInteroperability with an implementation written elsewhere

How to run the checks#

From reference/ in the repository, which also holds the verifiers and the test vectors. Results, with their logs, are in Conformance.

Reference implementation · shellbash
npm test
npm run test:network
node scripts/mutar-vxp.mjs
node scripts/mutar-nativo.mjs
node scripts/mutar-exigencia.mjs
PYTHONIOENCODING=utf-8 python scripts/mutar-prueba.py
python ../verifier/verificar.py ../vectors/vectores-0.1.json
python ../verifier/verificar_paquete.py vectores ../vectors/vectores-0.1.json
python ../verifier/verificar_paquete.py pact ../vectors/vectores-pact-0.1.json
python scripts/cruce-verificadores.py
python scripts/fuzz-diferencial.py 20000

A third party checks its own implementation of the native profile with the conformance validator: it acts as a personal agent against any company endpoint, cites the rule behind each check and reports each one as passed, failed, optional or manual (conformance/validate.ts, conformance/CHECKLIST.md, conformance/MATRIX.md). Against the reference implementation: 51 passed, 0 failed, 3 manual.

JSON Schemas#

Informative Every object of this draft has a JSON Schema (2020-12) at https://vxpprotocol.org/schemas/0.1/: the discovery document, the members of the issuer metadata and of the agent's Client ID Metadata Document, the token response, the native requests, proposals, events and errors, the proof package and each statement by kind, the log checkpoint, and the relationship, its agreement, the company's requests, the answers and their receipts. They restate §6 to §8; where they differ, this text governs. A schema checks shape; signatures, digests and the order of a chain are checked by a verifier (§10). The conformance validator checks every object an implementation returns against them (check S1).

13Specification and versions#

Draft 0.1, 21 October 2026. Revised 22 October 2026, first for names vxp, rule I1 and Poppy §4.4 conformance, and again that evening for two public components and operation requirements O1 to O7; native-only gateway; implementation paths; key rotation and historical verification. Revised 23 October 2026: read capabilities and standing_permission in the native endpoints, and the JSON Schemas (§12).

  • Poppy draft 0.2, official kit at commit 39b35bd. Poppy can break compatibility before its 1.0.
  • PACT 1.0 over A2A 1.0.1, official suite at commit 838c6bd.

Versioning#

The extension has one version, and it MUST be the same in three places: version of the trust block, the A2A extension URI (https://vxpprotocol.org/ext/proof/v0.1) and v of each statement and package. While VXP is a 0.x draft, every new draft is treated as incompatible with the previous one. A verifier MUST reject a v it does not know.

Status of each part#

PartState in 0.1
VXP ProofSpecified, implemented and tested. An operator attestation until there are witnesses and a second issuer
VXP Relationship over a Poppy conversationImplemented and run once with a live personal agent through the Poppy adapter, on 21 October 2026; no agent outside SOVRAS's runs yet
VXP Relationship over an HTTP inboxExperimental Tested only against a server of SOVRAS's own
Native profile, vxp.json and /v1Experimental Declared so in the document itself. Runs as a native-only gateway, with no adapter
Poppy draft 0.2 adapterOfficial validator for discovery and conversations; run with live personal agents on 21 October 2026, among them SOVRAS's beta agent Nova AI
PACT 1.0 adapterOfficial Identity suite and a delegated test written by SOVRAS with the official client; no third-party PACT agent yet
Relationship over PACTPending

Open items#

Pending
  • A third-party agent on the Poppy adapter, the PACT adapter or the native profile, and an agent with a stable identity.
  • A third party that implements the HTTP inbox of the relationship.
  • Independent evidence: external log witnesses, company key custody outside the operator, a second independent issuer.
  • Approval through the company's channel in the native profile and in Poppy, which today use agent_asserted.
  • Independent review of VXP Relationship and of the changes to VXP Proof.
  • Parts of Poppy draft 0.2 not implemented: the person's direct conversation with the company, streaming, DPoP nonce, web browsing with a shared session, OpenAPI and MCP, Direct and Mediated Sign-In, and a screen where the person sees and disconnects their agents.
  • Publishing the site and the repository on vxpprotocol.org.
  • A third party that runs the conformance validator (§12) against its own implementation.

Taking part#

The specification, the reference implementation, the verifiers and the vectors live in one repository under Apache 2.0. Proposals and errata are issues or pull requests there; a change to a normative rule cites the rule's identifier and comes with a test or a vector. See CONTRIBUTING.md in the repository.

See how the parts fit together, with diagrams.

Architecture