Black-box contract tests for A2A agents
Project description
a2a-proof
Black-box contract tests for A2A agents.
a2a-proof discovers an agent, sends real A2A requests, and checks the observable result.
It does not need the agent's source code, framework, prompts, or model provider.
Scope
a2a-proof complements the official A2A testing tools:
- A2A TCK checks protocol conformance.
- A2A Inspector supports interactive inspection and debugging.
a2a-proofruns repeatable, user-defined behavior checks locally or in CI.
Use the TCK to verify that an implementation follows the A2A specification. Use a2a-proof to
verify that a deployed agent still behaves as your application expects. The current release targets
A2A 1.0 and supports JSON-RPC, HTTP+JSON, and gRPC.
Quick start
uvx a2a-proof init https://agent.example.com
uvx a2a-proof run
init reads the Agent Card and creates a2a-proof.yaml. If a skill contains examples,
the first example becomes a scenario; otherwise the file contains one smoke test. The generated
scenarios verify protocol success. Add the assertions you care about before using them in CI.
Configuration
# yaml-language-server: $schema=https://raw.githubusercontent.com/aspix2k/a2a-proof/main/schema/a2a-proof.schema.json
version: 1
agent:
url: https://agent.example.com
timeout: 30
transport: auto
extensions:
- https://agent.example.com/extensions/structured-input/v1
headers:
Authorization: ${A2A_AUTHORIZATION}
scenarios:
- name: capital of France
message: What is the capital of France?
expect:
state: completed
max_seconds: 10
max_first_event_seconds: 2
text:
contains: Paris
not_contains: error
matches: "(?i)capital"
- name: clarification
turns:
- message: Book a table
expect:
state: input_required
text:
contains: city
- message: Paris
expect:
state: completed
- name: nondeterministic answer
message: Name a primary color
trials: 5
pass_rate: 0.8
expect:
text:
matches: "(?i)red|blue|yellow"
- name: structured forecast
message: Return a structured forecast
data:
action: forecast
city: Paris
expect:
data:
- source: artifact
artifact_name: forecast
media_type: application/json
path: /city
equals: Paris
- path: /temperature
gte: 18
lt: 30
- path: /summary
matches: "(?i)sunny|cloudy"
- path: /alerts
exists: true
- path: /forecast
json_schema:
type: object
required: [date, conditions]
properties:
date: {type: string}
conditions: {type: string}
Each scenario uses a single turn or turns. A turn may contain message, data, or both. A mapping
under data creates one A2A data part; use a list to send several parts. Multi-turn scenarios
preserve the A2A context and continue the task after input_required and auth_required responses.
Text assertions support contains, not_contains, equals, and Python regular expressions in
matches. Strings are case-sensitive unless case_sensitive: false is set. Failed, rejected,
and canceled tasks fail by default unless that state is explicitly expected.
max_seconds bounds the complete turn. max_first_event_seconds bounds the time until the first
A2A response event observed by the client, which is useful for streaming responsiveness checks.
trials repeats a scenario. pass_rate is the minimum successful fraction and defaults to 1.
Structured assertions inspect A2A data parts from messages or artifacts. path is an
RFC 6901 JSON Pointer; an empty path checks the complete
JSON value. Each assertion must match at least one data part after the optional source, artifact
name, and media type filters are applied. Use exactly one assertion type per entry:
equalscompares JSON values without treating booleans as numbers.existschecks whether a non-root pointer is present or absent.matchesapplies a bounded regular expression to string values.gt,gte,lt, andltedefine one or more numeric bounds.json_schemavalidates a value against an inline JSON Schema Draft 2020-12 document.
Embedded schemas may use local references such as #/$defs/item; external references are rejected
and never fetched.
Editor support
The published configuration schema provides completion and inline
validation in editors that support YAML language-server schema comments. init writes the comment
automatically. For an existing file, add the first line shown in the configuration example above.
Output
A passing scenario produces a compact summary:
$ a2a-proof run
┏━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━┳━━━━━━┓
┃ Result ┃ Scenario ┃ Trials ┃ Time ┃
┡━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━╇━━━━━━┩
│ PASS │ Echo │ 1/1 │ 2ms │
└────────┴──────────┴────────┴──────┘
1 scenario passed in 2ms
A failed assertion identifies the scenario and returns exit code 1:
$ a2a-proof run --verbose
┏━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━┳━━━━━━┓
┃ Result ┃ Scenario ┃ Trials ┃ Time ┃
┡━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━╇━━━━━━┩
│ FAIL │ Echo │ 0/1 │ 1ms │
└────────┴──────────┴────────┴──────┘
Echo
trial 1, turn 1: response text is not equal to the expected value
response: echo: Hello
1 scenario failed in 1ms
Official sample
The repository includes a contract for the A2A project's
Hello World agent.
Start that agent on its default port, then run from an a2a-proof checkout:
uv run a2a-proof run examples/official-helloworld.yaml
Authentication
Keep secrets in environment variables. Configuration values in the form ${NAME} are expanded
after YAML parsing.
export A2A_AUTHORIZATION='Bearer ...'
a2a-proof init https://agent.example.com \
--header-env Authorization=A2A_AUTHORIZATION
The generated file stores the reference, not the secret.
Transports
The default auto mode lets the Agent Card select JSON-RPC, HTTP+JSON, or gRPC. Set transport
to JSONRPC, HTTP+JSON, or GRPC to require one binding. gRPC uses TLS by default; set
grpc_tls: false only for a trusted plaintext endpoint such as a local test server.
Agent interfaces must share the discovery URL's origin by default. If a trusted deployment
intentionally separates them, set allow_cross_origin_interfaces: true or pass
--allow-cross-origin to init. Request headers will then be sent to that interface.
Discovery uses /.well-known/agent-card.json and falls back to the legacy
/.well-known/agent.json path after a 404. Use card_path to require a custom path.
Protocol extensions
List extension URIs under agent.extensions. a2a-proof checks them against the Agent Card and
activates them on every JSON-RPC, HTTP+JSON, or gRPC request. Execution stops before the first agent
request if a configured extension is not advertised or a required extension is not configured.
init adds required extensions automatically.
Existing configurations that set A2A-Extensions under headers remain valid. The dedicated
extensions field is preferred because it is explicit and lets init populate required
capabilities.
Extension activation does not implement the extension's semantics. In particular, this release
does not add AP2 mandate assertions. The current official AP2 Python samples pin
a2a-sdk==0.3.24,
while a2a-proof targets A2A 1.0, so those sample agents are not compatible wire-level test targets
yet.
Commands and output
a2a-proof check [CONFIG]
a2a-proof run [CONFIG]
a2a-proof run --format json
a2a-proof run --format junit --output a2a-proof.xml
a2a-proof run --verbose
a2a-proof run --scenario "capital of France"
a2a-proof run --scenario smoke --scenario regression
--scenario is repeatable and runs exact, case-sensitive scenario names in configuration order.
Unknown names fail before connecting to the agent.
Exit code 0 means all scenarios passed, 1 means a contract failed, and 2 means the command
or configuration could not be executed. JUnit output is suitable for CI test reports.
Safety limits
Per turn, outgoing structured input is limited to 100 parts and 1 MB. Responses are limited to
1,000 stream events, 1,000 structured data parts, and 1 MB each of text, structured data, and inline
raw data. At most 20 extension URIs and 8,000 extension-header characters may be configured.
Embedded JSON Schemas are limited to 100 KB and 50 levels. Requests have a configurable timeout,
text and data matches checks have a 100 ms evaluation limit, HTTP redirects are disabled,
external schema references are rejected, and artifact URLs are never fetched. Treat the tested
agent and all returned content as untrusted input.
Development
Requires Python 3.11+ and uv.
uv sync --all-groups
uv run ruff format --check .
uv run ruff check .
uv run ty check
uv run python scripts/generate_schema.py --check
uv run zizmor --persona=pedantic --offline --strict-collection .
uv run pytest --cov=a2a_proof
uv run mutmut run --max-children 1
uv build
Mutation testing targets the deterministic contract core. Network transports remain covered by real JSON-RPC, HTTP+JSON, and gRPC end-to-end tests.
See CONTRIBUTING.md for the contribution workflow and SECURITY.md for private vulnerability reports.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file a2a_proof-0.4.0.tar.gz.
File metadata
- Download URL: a2a_proof-0.4.0.tar.gz
- Upload date:
- Size: 23.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
477b8112aeb6c4ce2ff74458f81fab10078b8d79fad58562823ec7183f1dacf0
|
|
| MD5 |
a80725ede4b8b225ee1075411c853cf0
|
|
| BLAKE2b-256 |
b066b538b47fd49b71a9a3e5675c9f8e984422674abd7d7efb0b01cab7536499
|
Provenance
The following attestation bundles were made for a2a_proof-0.4.0.tar.gz:
Publisher:
release.yml on aspix2k/a2a-proof
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
a2a_proof-0.4.0.tar.gz -
Subject digest:
477b8112aeb6c4ce2ff74458f81fab10078b8d79fad58562823ec7183f1dacf0 - Sigstore transparency entry: 2200847015
- Sigstore integration time:
-
Permalink:
aspix2k/a2a-proof@69fb7198878cbc6220bf2975b47f48b5b4922978 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/aspix2k
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@69fb7198878cbc6220bf2975b47f48b5b4922978 -
Trigger Event:
push
-
Statement type:
File details
Details for the file a2a_proof-0.4.0-py3-none-any.whl.
File metadata
- Download URL: a2a_proof-0.4.0-py3-none-any.whl
- Upload date:
- Size: 27.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
30bcb9911c39511d4c1f87b2d39e2d880b0354670e405fce91962a84f1729867
|
|
| MD5 |
2d204cf0381eb4ded3969e21aee532c6
|
|
| BLAKE2b-256 |
ef68845bd30335b215b4e7f9963793bd347c47cf81946a997dcd0cdff664606d
|
Provenance
The following attestation bundles were made for a2a_proof-0.4.0-py3-none-any.whl:
Publisher:
release.yml on aspix2k/a2a-proof
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
a2a_proof-0.4.0-py3-none-any.whl -
Subject digest:
30bcb9911c39511d4c1f87b2d39e2d880b0354670e405fce91962a84f1729867 - Sigstore transparency entry: 2200847039
- Sigstore integration time:
-
Permalink:
aspix2k/a2a-proof@69fb7198878cbc6220bf2975b47f48b5b4922978 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/aspix2k
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@69fb7198878cbc6220bf2975b47f48b5b4922978 -
Trigger Event:
push
-
Statement type: