Skip to main content

counterpart

CI PyPI Python License

Test your agent against a peer that lies about finishing.

When your agent delegates work over A2A, a task reaching completed tells you the other agent stopped working. It does not tell you the work is any good. counterpart gives you counterparties that misbehave on purpose, and a way to check what they actually returned.

pip install counterpart
from pydantic import BaseModel
from counterpart import Contract


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


async def test_agent_rejects_a_lying_peer(mock_agent):
    contract = (
        Contract("freight quote")
        .returns(Quote)
        .require("price_is_number", lambda q: isinstance(q.price, (int, float)))
        .expect_status("completed")
    )

    peer = mock_agent(persona="false_success")
    async with peer.client() as client:
        task = await client.send_message("Quote 2 pallets LA to Dallas", contract=contract)

    assert task.status == "completed"   # the peer said it was done
    assert task.contract_violated       # what it sent back was useless

Installing the package is the whole setup. The mock_agent fixture and the async configuration come with it, so there is no conftest.py to write.

Why this exists

Multi-agent systems rarely fail by crashing. They fail when one agent finishes successfully and hands the next one something incomplete or wrong. Nothing raises, nothing gets logged, and you find out days later from a bad invoice or a denied claim. A study of multi-agent failures puts false success at 45 to 79 percent of them.

Conformance testing cannot see this, because the protocol behaved correctly. Evaluation platforms cannot either, because they simulate a user talking to your agent rather than a peer agent answering it. So the question nobody asks is the one that matters: the agent I delegated to says it finished, but can I use what it gave me?

Here is a real example. HL7's prior authorization standard publishes a sample response for a request that is still pending review. Its top level outcome field says complete. The approved sample says complete too. The two differ only in an optional code buried four levels deep, while the field that misleads you is the required one. Any client reading the obvious status field will schedule surgery against an authorization that does not exist.

Personas

Every persona is deterministic, needs no LLM, and drives a real A2A task lifecycle.

Persona What it does
cooperative Works and completes with a well formed result
clarifier Asks one question, waits for your answer, then completes
false_success Reports completed and returns garbage
resource_abuse Stalls or streams forever without finishing
flaky Drops the connection, then recovers on retry
over_sharing Asks for more context than the task needs

Writing your own takes one class and one call. There is 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})]

register("half_answer", HalfAnswer)

One thing to know about contracts

By default returns() uses pydantic's normal mode, so a peer sending {"price": "1420.00"} gets coerced to a float and passes. Your predicates then see a real number, so an isinstance check will not save you. That is ordinary pydantic behaviour, but it surprises people here, because you might reasonably think you asked for type validation.

If you want the stricter reading, ask for it:

Contract("fare").returns(Fare, strict=True)

Lax is the default on purpose. Plenty of real services send numbers as strings, and a contract that rejects valid traffic gets switched off, which leaves you with nothing. Use strict=True when you control both ends or the format is pinned.

Exposing your own agent

wrap() turns any callable, sync or async, into an A2A server you can point tests at.

from counterpart import wrap

app = wrap(my_agent, name="quoting-agent", skills=["freight-quote"])

Run it with uvicorn, or skip the socket entirely with A2AClient(app=app).

Checking a live agent

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

check gives you a scored report where every row cites the spec section it verifies. It is a quick smoke test for the development loop, not a certification suite. For the full conformance matrix use the official a2a-tck. attack sends adversarial probes and reports what came back.

Examples

There are four runnable scenarios in examples, each in a domain where the counterparty genuinely belongs to someone else, and each built around a different kind of failure.

Freight procurement collects quotes from competing carriers and picks the cheapest usable one. The naive version books the carrier whose completed quote lists price as "call for rate", and sends that string to invoicing.

Prior authorization clears a procedure with a health insurer's eligibility and utilization management agents. It models 25 payer responses that all report completed, drawn from the X12 278 and FHIR Da Vinci specs and from billing forums. Each one is labelled by how well attested it is, and two are marked unattested because I could not find a real case for them.

Satellite downlink schedules a pass with a ground station network. Twelve plans that all validate and all describe a different physical reality: a window handed over in GPS time, elevation in radians where the field says degrees, an inertial frame where you need an earth fixed one. This is the failure that cost NASA the Mars Climate Orbiter.

Chaos multihop is deliberately excessive. Four hops, each a separate HTTP server, auth passed along the chain, twenty concurrent users, and corruption injected at the deepest hop. Corruption three hops away still gets caught at the top.

Every one of them measures two things, and the second matters more. Every unusable answer has to be caught, and every legitimate variation has to be left alone. That second half is not theoretical. Researching the real X12 value sets turned up three bugs in these contracts, and all three were false positives: rejecting a valid A1 certification, rejecting a member id that came back correct but with different padding, and comparing dates as strings so that 07/31/2026 quietly passed a check it should have failed. A later chaos run found a fourth. None of them was a missed catch. A contract that flags good traffic gets deleted in week two.

What it does not do

This is testing you run before you deploy. It does not watch production, it does not solve agent identity or trust, and it will not bound how deep a peer delegates. A contract only sees the payload, not the clock, so if a correct answer arriving 400ms late is useless to you, that timeout is yours to enforce. limits_probe.py shows exactly where the library goes quiet.

If your agents are three functions in the same process, you do not need any of this.

Design

The core has no protocol code in it. A2A is one adapter, and a test enforces the boundary, so the contract engine also works on a plain HTTP response, an MCP style tool result, or a function return.

  • docs/spec-notes.md maps A2A v1.0 to the code, citing the spec throughout. Wire types are checked against the vendored normative proto.
  • docs/prior-art.md covers what already exists and where this fits.
  • docs/roadmap.md lists what is out of scope and what is known to be missing.

Status

Version 0.1.1, built against A2A spec v1.0. The API will change. Apache-2.0.

If you have hit this failure in something you built, I would like to hear about it. Open an issue with the shape of the response that fooled you.

Metadata

Release files for counterpart 0.1.1

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.1
File Size Uploaded
counterpart-0.1.1.tar.gz 140.4 kB Details

Built distribution (wheel)

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

Total release size: 196.5 kB

Release files / counterpart-0.1.1.tar.gz

Download URL counterpart-0.1.1.tar.gz
Size 140.4 kB
Tags Source
SHA-256 checksum
How to use checksums
0b107bbfb1f2b337534892dcd8bd8af6082ba58e39a62ffb419407994381a08b
BLAKE2b-256 checksum
How to use checksums
c514eb6b7461cdcf5db371a0f0a6cd7a6ebffb2b8c6e4e39b1f9f70ae023e688
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.1-py3-none-any.whl

Download URL counterpart-0.1.1-py3-none-any.whl
Size 56.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
60c820383406be5cc1cdc3a8b4a8b9fe6bb4f27c10bb6829c4fc8b0e8004e0b3
BLAKE2b-256 checksum
How to use checksums
548a3495c273d339102f318b3311a2ed828e2f8b33a6344f41956792b9cea777
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

This release

0.1.1 This release

2 release files

0.1.0

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