Skip to main content

counterpart

CI PyPI Python License


A2A lets one AI agent hand work to another. counterpart gives you misbehaving peers to test yours against, and contracts that catch the answers you cannot use:

async def test_carrier_reports_success_with_no_price(mock_agent):
    peer = mock_agent("false_success")     # reports done, returns garbage

    task = await peer.ask("Quote 2 pallets LA to Dallas", contract={"price": float})

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

Why counterpart

The agents your agent depends on belong to other people. You cannot spin up a carrier's agent or an insurer's agent to test against, so counterpart stands in for them. And it reads what they send back, because completed only means the other agent stopped working. Every one of these arrives as a completed task with the protocol fully satisfied:

{"message": "Your quote is ready!"}          # reads fine, contains no price
{"price": "call for rate"}                   # a sentence where you needed a number
{"price": 1420.0, "account": "AC-99812"}     # right shape, somebody else's account
{}                                           # nothing at all

A contract is you writing down what you were expecting, so the test fails at the point the peer lets you down instead of three functions later or in an invoice. {"price": float} is the shorthand form. For real checks, pass a pydantic model and add named predicates, with strict=True when you do not want "1420.00" silently coerced to a number:

contract = (
    Contract("freight quote")
    .returns(Quote, strict=True)
    .require(price_positive=lambda q: q.price > 0)
)

The mock runs inside your test process. No socket, no port, nothing on the network. mock_agent is the pytest fixture; MockAgent is the same object without pytest, and its .serve() binds a real address when you need to point another process at it.

It also works in the other direction, with the mock as the caller and your agent answering. wrap() turns any function into an A2A server, and the same contract now judges your answers:

from counterpart import MockAgent, serve_asgi, wrap

with serve_asgi(wrap(my_agent, name="quoting-agent")) as url:
    peer = MockAgent("cooperative")
    task = await peer.send_task(url, "Quote 2 pallets", contract={"price": float})

assert not task.contract_violated   # your own answer held up

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

Personas

Six ship with the package. All deterministic, none needing an LLM.

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)

Installation

pip install counterpart

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

There is also a CLI for poking a live agent: counterpart check <url> scores it against the spec, section by section, and counterpart attack <url> sends deliberately malformed requests and reports what came back. For the full rulebook use the official a2a-tck.

Examples

Five runnable scenarios in examples, each in a domain where the counterparty genuinely belongs to somebody else:

  • Freight procurement: pick the cheapest usable quote, not the cheapest string that parses.
  • Freight edge cases: 22 ways a quote is unusable, 14 that look wrong and are fine. Checking them changes which carrier gets booked, at $592.50 more.
  • Prior authorization: 25 insurer replies that all report success, including the standard's own example of a pending decision that does.
  • Satellite downlink: twelve plans that all validate and each describes a different physical reality. Radians where the field says degrees.
  • Chaos multihop: four hops, real sockets, twenty concurrent users, corruption injected at the bottom and caught at the top.

Every example tests both halves: each unusable answer is caught, and each legitimate answer is left alone. The second half is harder. All four bugs found in these contracts were false positives, checks rejecting good answers, and a checker that flags good data gets switched off.

Documentation

Contributing

Version 0.1.12, Apache-2.0, the API will change. If a peer's reply has fooled something you built, open an issue with the shape of the payload. That is the most useful contribution this project can get.

Metadata

Release files for counterpart 0.1.12

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.12
File Size Uploaded
counterpart-0.1.12.tar.gz 143.5 kB Details

Built distribution (wheel)

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

Total release size: 199.9 kB

Release files / counterpart-0.1.12.tar.gz

Download URL counterpart-0.1.12.tar.gz
Size 143.5 kB
Tags Source
SHA-256 checksum
How to use checksums
2b23252135606bef464080d6385087db6e1c2e9f100b66b19d5e8900c1549efa
BLAKE2b-256 checksum
How to use checksums
ae02b4fd3254139f229c205a6d13a0e8def778bb89c1889ceb1452ea1ff2684d
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.12-py3-none-any.whl

Download URL counterpart-0.1.12-py3-none-any.whl
Size 56.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3c3de33b9dceef8bd22761d35cf29bd905a32524d65b79497fb42fb592f736fb
BLAKE2b-256 checksum
How to use checksums
df2feb2ea22776d70ee74e78c4e165322d52f767ee8067181c47ac639d453b15
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

This release

0.1.12 This release

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

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