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:
| Component | What it defines |
|---|---|
| VXP Proof | Signed 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 Relationship | The 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:
- Who asked, and on whose behalf?
- Which exact terms did the person approve, and through which channel?
- What ran, and how many times?
- 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.
| Part | Kind | Section |
|---|---|---|
| VXP Proof | Public component, format on the wire | §6 |
| VXP Relationship | Public component, format on the wire | §7 |
| Operation requirements O1 to O7 | Behaviour every implementation MUST show; no interface | §9 |
Native profile: vxp.json and /v1 | VXP's own discovery and endpoints Experimental | §8 |
| Poppy and PACT adapters | Optional. The external contracts, used literally | §8 |
| Implementation decisions | Informative. 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.
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#
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.
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.
The company does not need a conversational agent of its own: a deterministic intermediary serves the PACT A2A service and the Poppy API.
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.
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#
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.
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.
Legal transitions
Any other transition MUST be rejected.
| From | To |
|---|---|
| (start) | received |
received | authorization_required, proposed, failed |
authorization_required | proposed, cancelled, expired, failed |
proposed | proposed (new revision), accepted, cancelled, expired, failed |
accepted | waiting_human, executing, cancelled (before executing), failed (permissions or precondition no longer hold) |
waiting_human | executing, pending_verification, succeeded, failed, cancelled |
executing | pending_verification, succeeded, failed |
pending_verification | succeeded, 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.
| Internal | Poppy operations | A2A 1.0.1 TaskState |
|---|---|---|
received | no operation yet | TASK_STATE_SUBMITTED |
authorization_required | no operation yet | TASK_STATE_AUTH_REQUIRED |
proposed | proposed | TASK_STATE_INPUT_REQUIRED |
accepted, waiting_human, executing, pending_verification | in_progress | TASK_STATE_WORKING |
succeeded | succeeded | TASK_STATE_COMPLETED |
failed | failed | TASK_STATE_FAILED |
cancelled | cancelled | TASK_STATE_CANCELED |
expired | expired | TASK_STATE_CANCELED, with the internal state in metadata |
- The state MUST NOT move to
failedwhile it is not known whether the effect happened. That ispending_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:
| What | Example | What it establishes |
|---|---|---|
| Channel identifier | A WhatsApp number | That someone controls that channel. It identifies no single person when two customers share it. |
| Customer's identity at the company | The canonical customer, linked with evidence to the customer in the ERP | Which account, and which resources, are theirs in the company's system. |
| Authorization delegated to the agent | A grant with client_id, sub, scopes and authorization_details | What that agent may ask, for that person, over which resources. |
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.
An identifier that corresponds to more than one account MUST NOT be used on its own to link or to give out data.
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'sclient_id),user(a per-company pseudonym) andbody. - 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}.termsMUST carry a freshvxp_saltof at least 128 bits in each revision. - Personal data travels as selective disclosures, never in clear.
Statement kinds#
kind | Signed by | What it records |
|---|---|---|
request | Operator | At seq 0: channel, client_id, digest of what was asked, optional idempotency key digest |
terms | Company | Each revision: terms_digest, capability, commitment, expires_at, company_review_required, read_back_terms |
counter | Operator | A counter-proposal from the agent, by digest |
user_approval | Operator | Approval of the latest revision and its method; basis: "standing_permission" when the person authorized it beforehand, within a limit |
company_review | Company | A decision by a person at the company, by role, after the customer's approval and before execution |
execution | Operator | The single execution, on the approved revision and digest |
read_back | Operator | What the company's system showed afterwards, named by term |
outcome | Company | The result: succeeded, failed, cancelled, expired or unknown |
correction, revocation | Company | What 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.
- SD-JWT format, canonical base64url, header, known
kidfor theiss, valid signature. - Canonical payload.
kindallowed for the signer's role.- SD-JWT processing as in RFC 9901 §7.1.
v,company,op,audanduserequal across the chain.seqwithout gaps andprevequal to the digest of the previous statement.- After a final statement, only
read_back,correctionandrevocation. - Semantic order by
kind: one approval of the latest revision, one execution after it,succeededonly after a matching system read-back. - There is an
outcome; a package without one is treated asunknown.
Rules P1 to P8#
Binding by digest:
terms: the digest of the exact revision, including itscommitment;user_approval: the same digest and the approval method;execution: the same digest;read_back: what was observed, with the name of each term;outcomeorcorrection: the digest of the summary.
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.
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.
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.
Contradiction: if it ran and the later read does not match what was approved (another value, or the resource is gone):
- the
read_backMUST be signed withmatches_terms: falseand 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.
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.
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.
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:
method | Meaning | What it proves |
|---|---|---|
agent_asserted | The agent said the user approved | Only that the agent asserted it, with its DPoP key and in its session |
company_channel | The user answered from the channel the company already has linked to them | That 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
summaryand itsdisplay_code. - The channel is whichever one the company has linked to the user: a messaging line, email, its own app.
display_codeis the first four bytes of SHA-256 over the UTF-8terms_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 beforeexpires_at. The operator MUST limit attempts. - A message that could not be delivered approves nothing: the operation MUST stay
proposedand 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?}.correctsalways points to the latest final statement, and itsstatebecomes the current state for the verifier.revocation:{revokes, reason}, when the referred statement was wrong. The verifier reportsfinal_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:
{ "type": "https://vxpprotocol.org/rar/relationship",
"actions": ["despacho.proponer_cambio", "incidencia.avisar"],
"identifier": "relationship",
"limits": { "max_per_day": 2, "duration_seconds": 2592000 } }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).
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
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.
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.
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#
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.
| Situation | Error |
|---|---|
| The relationship does not exist | relationship_not_found |
| The relationship or its delegation was revoked | relationship_revoked |
| The relationship expired | relationship_expired |
| The action is not in the relationship | action_not_authorized |
| The daily limit was exceeded | relationship_limit_exceeded |
| The resource is not the customer's | target_not_found |
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
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.
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#
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
The agent MAY read its relationship with GET /v1/relationships/{id}; another sub of the same agent gets 404. Experimental route
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_idandsub(otherwise404 relationship_not_found); - that the relationship and the delegation are still in force (
403with 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
decisionis one of theoptions(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.
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).
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.
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.
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.
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).
- 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.jsonand 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.domainMUST be the host that served the document, and that domain MUST be listed invxp_proof.domainsof the issuer's RFC 8414 metadata.- If
poppy.jsonexists, itsauth.issuerMUST be the same. vxp.jsonMUST NOT carry the proof's trust members; the verifier MUST take them only from the issuer metadata.capabilitieslists only what the company decided to offer to external agents.accessispublicordelegated;admissionisstatic(the grant is enough) ordynamic(the company decides per request: owner, precondition, policy).
{
"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_jwtover 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 typehttps://vxpprotocol.org/rar/capability. The consent screen MUST show eachidentifierin business language; the authorization server MUST check that eachidentifierbelongs to the linked account, or answerinvalid_authorization_details; the token response MUST return the details granted. - The type
vxpprotocol.org/operationis the grant of one revision. The two are complementary: the first authorizes asking, the second approving one exact revision.
{ "type": "https://vxpprotocol.org/rar/capability",
"actions": ["pedido.reprogramar_despacho"],
"identifier": "P-1042" }Endpoints#
All with Authorization: DPoP and a DPoP proof.
| Method and path | What it does | Responses |
|---|---|---|
POST /v1/requests | Creates 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 proof | 200 {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_url | 200; 404 request_not_found |
GET /v1/requests/{id}/events | Transitions {seq, from, to, at, reason, poppy, a2a} | 200 |
POST /v1/proposals/{id}/accept | Accepts 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}/counter | Counter-proposal {proposed_terms, note?} | 200 with the new revision; 409 counter_rejected, negotiation_limit_exceeded |
POST /v1/requests/{id}/cancel | Asks to stop (Cancellation) | 200, 202 |
GET /v1/requests/{id}/proof | The proof package | 200 |
GET, DELETE /v1/relationships/{id} | Reads or revokes the agent's relationship (R10, R11) | 200; 404 relationship_not_found |
POST /v1/relationships/{id}/replies | Answers 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 inauthorization_requiredMUST 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. acceptandcancelare idempotent by revision and by state, without a key.- Before executing, the operator MUST re-evaluate, with the token of the
acceptcall, 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 emptyvxp_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 getsinvalid_target(RFC 8707). - Agent authentication uses OAuth building blocks only: a Client ID Metadata Document,
private_key_jwt, DPoP and RFC 8628.
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.requiresMUST be empty.conditional: the result depends on each condition inrequires, 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#
| Limit | Meaning | When exceeded |
|---|---|---|
max_revisions | Highest revision number | No further revision; the request ends failed without effect (negotiation_limit_exceeded) |
proposal_ttl_seconds | Validity of each revision | The revision expires; a later accept gets 409 proposal_expired |
max_accept_attempts | accept calls on a revision that is not current | 409 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_requiredorproposed:cancelled, without effect. - In
acceptedorwaiting_human, before execution starts:cancelled, without effect. - In
executingorpending_verification: a request to stop. The state MUST NOT change because of it. The response MUST be 202 withcancellation: {requested_at, status: "requested", reversal_promised: false}and the knowneffects. - 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#
| Adapter | Discovery and identity | Where VXP travels |
|---|---|---|
| Poppy draft 0.2 | poppy.json, Poppy sign-in, sessions, conversations and Operations, as written | The 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.1 | Agent Card, PACT agent identity and delegated authority, as written | capabilities.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.1 | VXP 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-jwt | typ: vxp-stmt+sd-jwt |
typ: sovras-checkpoint+jwt | typ: vxp-checkpoint+jwt |
sovras_salt (in the terms) | vxp_salt |
binding: sovras.request, sovras.intent | vxp.request, vxp.intent |
sovras.io/outcome-proof, https://sovras.io/ext/outcome-proof/v0.1 | vxpprotocol.org/proof, https://vxpprotocol.org/ext/proof/v0.1 |
https://sovras.io/rar/capability, https://sovras.io/vxp/rar/relationship | https://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.
| What | Path 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#
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.
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.
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.
Company review. When its rule requires it, a person at the company decides on the same digest, after the person. E3.
Requirement, evidence and control#
Three different things that MUST NOT be confused:
| Layer | What it is | Who defines it |
|---|---|---|
| Required behaviour | Rules O1 to O7: what an interoperable implementation does where a request becomes an operation | This specification (normative) |
| Evidence | What 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 controls | The catalog, policies, checks and locks that enforce the rules inside one company's systems | Each 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.
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:
| Step | What it does |
|---|---|
| Catalog | The capabilities the company released, and the actions it lets itself start |
| Prior read | Whether the resource belongs to the linked customer in the ERP |
| Policy | Personal-agent authority: scope ∩ released capabilities ∩ owner; company review by its rule |
| Commitment | firm or conditional, from what the capability can execute and verify |
| Execution | One, with a precondition |
| Read-back | Checks the effect in the system |
| Projection | From the internal state to Poppy and A2A |
Security#
Threats covered by the proof and how, with what remains open.
| Threat | How it is covered |
|---|---|
| Forgery by an outsider | Signature, RFC 7638 kid and anchoring of keys to the company's issuer (§10) |
| Forgery by the operator on the company's behalf | single_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 ERP | No signature solves it. Read-back, third-party evidence and an honest level label |
| Replay | op, seq, prev, aud and user equal across the chain and verified |
| Terms changed after approval | terms_digest covers summary and terms; approval and execution must match it |
| Approval diverted to another revision | display_code bound to the digest, single use, attempt limit |
Two contradictory succeeded on one resource | Precondition before executing and one operation in progress per resource. Residual: across separate packages, only a log with witnesses |
Another agent or sub rewrites an operation | Full owner on reuse (ID3) and a counter for each revision the agent originates |
| A digest that confirms a suspected personal value | vxp_salt in the terms. Residual: request_digest has no salt |
| “The user approved” said by the agent | agent_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
usermember 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#
| Where | Codes |
|---|---|
| Native requests | idempotency_key_required, invalid_request, target_not_found, idempotency_key_reused, request_exists, resource_busy, request_not_found, authorization_required |
| Proposals | terms_changed, proposal_expired, proposal_not_acceptable, attempt_limit_exceeded, user_approval_required, counter_rejected, negotiation_limit_exceeded |
| Authorization | invalid_authorization_details |
| Relationship | relationship_not_found, relationship_revoked, relationship_expired, action_not_authorized, relationship_limit_exceeded, target_not_found |
| Closed-question answers | alert_not_found, already_answered, alert_expired, option_not_allowed |
| Proof endpoints | operation_not_found: someone else's and a missing operation answer the same way |
| Poppy adapter | extension_required (HTTP 403), the Poppy §3.3 mechanism |
| Verifier | The 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#
- 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.
- Fetch the issuer's RFC 8414 metadata and require its
issuerto be identical, character by character. - Take the trust members from that metadata (
vxp_proof), with the keys under the issuer. - Require any copy in
poppy.jsonor the Agent Card to be identical in those members. - On any failure, including a missing or malformed document, answer
issuer_not_anchoredand keep running.
This anchors keys to whoever controls the issuer, not to the company as a legal person.
Algorithm and output#
- Anchor the issuer.
- Apply the chain rules to the whole chain; reject on the first failure with its code.
- Derive the levels from the chain, not from
claimed_assurance. - 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). - With a log: verify inclusion and consistency, and at least one cosignature by a witness that is not the operator. Only then grant
logged. - 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.
| Level | Granted when | Root of trust |
|---|---|---|
issuer_key_attested | outcome validly signed by the key anchored to the company's issuer | Whoever controls that key |
operator_observed | There is an execution signed by the operator | The operator |
operator_reported_read_back | A read_back with system_read_back and matches_terms: true | The operator, who asserts it read the company's system |
operator_reported_channel_approval | user_approval.method = company_channel | The operator, who asserts the answer arrived from the linked number |
operator_reported_agent_approval | user_approval.method = agent_asserted | The operator, who asserts the agent said the user approved |
logged Pending | Inclusion in a checkpoint cosigned by at least one witness outside the operator | That a witness is honest |
time_stamped Pending | An RFC 3161 stamp from an external authority | The time-stamping authority |
third_party_signed Pending | A third-party artifact verified against its digest | The third party |
third_party_reference Pending | A third-party identifier that can be queried again | The third party, when someone queries |
user_confirmed Pending | Later confirmation by the user through the company's channel | The user |
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:
| Question | Where the verdict answers it | What it does not say |
|---|---|---|
| Integrity: has the chain changed, is it complete and ordered? | valid, and on failure error with its seq | That the operation was fulfilled |
| Provenance: who signed each link, anchored to whom? | company, anchor, key_source; each statement's role by kind | Who 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 happened | final_state, from the latest outcome or correction | That the assertion is true |
| Corroboration: what supports it beyond the signature | derived_assurance[], single_root, custody_declared, logged | Independence, 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.
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.
{
"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"
}
}{
"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#
{
"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.
[
{
"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": []
}
}
]{
"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": {
"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 emitssucceededwithout 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
seqof 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:
| Kind | What it is | What it does not show |
|---|---|---|
| Official tools of other protocols | The Poppy validator (draft 0.2 kit) and the PACT Identity suite, unchanged | The 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 tests | Tests written by SOVRAS for the adapters, including the delegated PACT flow with the official PACT client | That a third party agrees with them |
| VXP tests | The reference suite, network tests, mutation tests, the native-only test and the vectors | Interoperability 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.
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#
| Part | State in 0.1 |
|---|---|
| VXP Proof | Specified, implemented and tested. An operator attestation until there are witnesses and a second issuer |
| VXP Relationship over a Poppy conversation | Implemented 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 inbox | Experimental Tested only against a server of SOVRAS's own |
Native profile, vxp.json and /v1 | Experimental Declared so in the document itself. Runs as a native-only gateway, with no adapter |
| Poppy draft 0.2 adapter | Official 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 adapter | Official Identity suite and a delegated test written by SOVRAS with the official client; no third-party PACT agent yet |
| Relationship over PACT | Pending |
Open items#
- 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