Skip to main content

counterpart

Test your A2A agent against simulated counterparties — cooperative, broken, or hostile — before you connect it to a real one.

Multi-agent systems don't usually fail by crashing. They fail when one agent reports completed and hands incomplete or corrupt work to the next one — no error, no stack trace, just wrong output three hops downstream. counterpart lets you reproduce that in a test: spin up a counterparty that misbehaves on purpose, point your agent at it, and assert on the result, not just the protocol status code.

Status: v0, spec v1.0, API will change. This is pre-deployment testing — it catches classes of failure before you ship. It is not production monitoring, and it is not an identity, trust, or runtime-governance layer. See What it does and doesn't do.

Quickstart

pip install counterpart        # or: uv add counterpart

That's the whole setup. No conftest.py, no ini options — installing the package registers the pytest fixture and configures async tests for you.

The scary one first — a peer that lies about success:

from pydantic import BaseModel
from counterpart import Contract


class Quote(BaseModel):
    price: float
    currency: str


async def test_my_agent_rejects_a_lying_peer(mock_agent):
    # Declare what a *correct* delegated result must look like.
    contract = (
        Contract("freight quote")
        .returns(Quote)                                        # must parse into this shape
        .require("price_is_number", lambda q: isinstance(q.price, (int, float)))
        .expect_status("completed")
    )

    # A counterparty that reports "completed" but returns garbage.
    peer = mock_agent(persona="false_success")
    async with peer.client() as client:
        task = await client.send_message("Quote 2 pallets LA->Dallas", contract=contract)

    assert task.status == "completed"     # the peer *claimed* success at the protocol level...
    assert task.contract_violated         # ...but the returned work does not hold up — caught.

mock_agent is a pytest fixture that ships with the package (no config). Wire that same contract into your agent's delegation path and it rejects the garbage instead of forwarding it downstream.

One thing to know before you trust a contract

By default .returns(Model) uses pydantic's normal lax mode, so a peer sending {"price": "1420.00"} — a string where your model says float — is coerced and passes. Predicates then see a real float, so isinstance(x, float) cannot save you.

That is standard pydantic behaviour, but it is a trap here: you may think you are getting type validation that you are not. If you need real type fidelity, ask for it:

Contract("fare").returns(Fare, strict=True)   # a stringified number is now a failure

Lax stays the default on purpose — plenty of real services legitimately send numbers as strings, and a contract that flags valid traffic gets switched off entirely, which is worse than no contract. Use strict=True when you own both ends or the wire format is pinned.

Every persona is deterministic (no LLM), toggle-configurable, and drives a real A2A task lifecycle. The reliability tier is where today's pain lives; the security tier is a smaller, forward-looking set for teams integrating untrusted or third-party agents.

Persona What it simulates What a correct agent does
cooperative Accepts, works, completes with a well-formed result Uses the result
clarifier Flips to input-required once with a question, then completes Answers, continues the task
false_success ⭐ Reports completed but returns incomplete/corrupt output Detects it via a contract; does not forward garbage
resource_abuse Stalls or streams forever, never completing Bounds its own time/spend and gives up
flaky Drops the connection mid-exchange, then recovers Retries transient failures
over_sharing Asks for more context than the task needs Refuses to leak unrelated context

Security-tier personas (prompt_injection, capability_lying) are on the roadmap as A2A's cross-boundary use grows.

Write your own by returning directives from a plain class — no DSL:

from counterpart.core import Complete, Progress
from counterpart.personas import register

class HalfAnswer:
    def respond(self, turn, ctx):
        return [Progress("working"), Complete(result={"partial": True})]  # omits the real answer

register("half_answer", HalfAnswer)

wrap() — expose your agent for testing in one line

from counterpart import wrap

app = wrap(my_agent, name="quoting-agent", skills=["freight-quote"])  # an ASGI A2A server
# run it: uvicorn.run(app, ...)   — or drive it in-process with A2AClient(app=app)

Worked examples

Four runnable scenarios in examples/, each in a domain where A2A is genuinely warranted (the counterparty is operated by someone else) and each with a different failure shape — because the shape of the failure determines the shape of the check:

Example Shape What it shows
freight_procurement.py N competing offers A shipper agent collects carrier quotes and picks the cheapest valid one. The naive version books the "cheapest" carrier whose completed quote has price: "call for rate" and sends a non-numeric price to invoicing.
satellite_downlink.py units, time systems, reference frames A mission-ops agent schedules a downlink pass with a ground-station network. 12 plans that all report completed while describing a different physical reality — a window in GPS time (18 s of leap seconds early), elevation in radians masquerading as degrees, west-positive longitude, an inertial frame where an earth-fixed one is needed, a month-old TLE, a negative link margin. This is the Mars Climate Orbiter failure class.
chaos_multihop.py 4 hops, real sockets, 20 concurrent users Deliberately excessive: a delegation chain where each hop is a separate real HTTP server, auth passed through, corruption injected at the deepest hop. Corruption three hops away is still caught by the contract at hop 1.
prior_authorization.py pipeline with cross-field consistency A clinic's agent clears a procedure with a payer's eligibility and utilization-management agents. 25 modelled payer failures — all reporting completed — sourced from the X12 278 / FHIR Da Vinci PAS specs and practitioner forums, with each case labelled by how well it is attested.
uv run python examples/prior_authorization.py

Every example measures two things, and the second matters more: every unusable answer is caught, and legitimate counterparty variation is not flagged.

That second property is not theoretical. Researching the real X12 value sets for the prior-auth example found three bugs in those contracts, and every one was a false positive — rejecting a valid A1 certification, rejecting a correctly-echoed member id, and mis-parsing a date so a MM/DD/YYYY value silently passed. A later end-to-end chaos run found a fourth. None was a missed catch; all four were the checker crying wolf. A contract that flags valid traffic gets switched off in week two, and then you have no contract at all.

check and attack a live agent

counterpart check  https://my-agent.example.com
counterpart attack https://my-agent.example.com --json   # for CI

check produces a scored conformance smoke report, every row citing the spec section it verifies:

                   counterpart check — https://my-agent.example.com
┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Check                ┃ Result ┃ Spec §      ┃ Detail                       ┃
┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ agent_card_reachable │ PASS   │ 8.2         │ HTTP 200                     │
│ agent_card_valid     │ PASS   │ 4.4.1       │ name='...', 1 skill(s)       │
│ send_message         │ PASS   │ 9.4.1       │ got task                     │
│ streaming_honesty    │ PASS   │ 3.3.4       │ content-type: text/event-... │
│ method_not_found     │ PASS   │ 9.5         │ got error code -32601        │
│ task_not_found       │ PASS   │ 5.4         │ got error code -32001        │
└──────────────────────┴────────┴─────────────┴──────────────────────────────┘
Score: 9/9 checks passed — conformant
Smoke report only; run a2a-tck for the full matrix.

It's a fast dev-loop tool, not a certification suite — for the full conformance matrix, use the official a2a-tck.

What it does and doesn't do

Does: reproduce cross-boundary failure modes — a peer that lies about success, stalls, drops, or over-asks — as deterministic pytest fixtures; verify the result of delegated work against a contract, not just the protocol; smoke-check and probe a live A2A agent from CI.

Doesn't: monitor production (that's tracing/observability — counterpart is shift-left, complementary); solve agent identity or trust infrastructure; issue certifications; mock LLM APIs, MCP, or vector DBs. It complements ingress-focused tools like agent-security-harness (which attacks your endpoint) by testing the other direction: the counterparty your agent delegates to.

Honest scoping: A2A adoption is still early, and multi-agent architectures are often overkill. counterpart is for the real case where you have genuine cross-boundary agents — and the core engine is protocol-agnostic, so the contract-verification and persona machinery isn't locked to A2A. See docs/prior-art.md for how this compares to what exists.

Design

Architecture: a protocol-agnostic core (counterpart.core — lifecycle, contracts, personas) with A2A as one adapter (counterpart.adapters.a2a). A test enforces that the core never imports protocol code, so a second adapter is possible without touching the engine.

License

Apache-2.0. See LICENSE.

Metadata

Release files for counterpart 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for counterpart 0.1.0
File Size Uploaded
counterpart-0.1.0.tar.gz 141.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for counterpart 0.1.0
File Interpreter ABI Platform
counterpart-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 198.8 kB

Release files / counterpart-0.1.0.tar.gz

Download URL counterpart-0.1.0.tar.gz
Size 141.8 kB
Tags Source
SHA-256 checksum
How to use checksums
9cf4d4186695b68b47e0bf12b471c0d6a0826e2e31311115283b4b8bcffe6d98
BLAKE2b-256 checksum
How to use checksums
c27a461ba979b661d34aefce0724108a7bbbfa8f6141310848e05eecb609368b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.

Transparency log

Release files / counterpart-0.1.0-py3-none-any.whl

Download URL counterpart-0.1.0-py3-none-any.whl
Size 56.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b0153beceb26398b79179efea170ac9abbee13c3c6aa302c39fa2a28c50f17ce
BLAKE2b-256 checksum
How to use checksums
24d94e29d3551462815362f89cd3aeb833be0164a734b14cccd8cf1d9114386b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.16

2 release files

0.1.15

2 release files

0.1.14

2 release files

0.1.13

2 release files

0.1.12

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page