Skip to main content

counterpart

CI PyPI Python License


A2A lets one AI agent hand work to another. counterpart mocks the agent on the other end, including the ways it can go wrong:

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

If your agent hands work to an agent somebody else runs, you cannot test it without them. counterpart stands in for that agent, and it also checks what comes back, because a task reaching completed only means the peer stopped working. Every one of these arrives as a completed task and the protocol is satisfied by all of them:

{"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 works in both directions too: wrap() turns your own agent into an A2A server so the mock can call it and hold your answers to the same contract.

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.9, 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.9

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.9
File Size Uploaded
counterpart-0.1.9.tar.gz 143.3 kB Details

Built distribution (wheel)

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

Total release size: 199.6 kB

Release files / counterpart-0.1.9.tar.gz

Download URL counterpart-0.1.9.tar.gz
Size 143.3 kB
Tags Source
SHA-256 checksum
How to use checksums
22c0c94cef0a2d60d68ef247087e313bb3ad8ea184e02094a6afab0ccfa2cd5f
BLAKE2b-256 checksum
How to use checksums
9c5abe7af775f3eab2c3ff33ecf730f978bd413532d57819461d358dbdd2e5ea
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.9-py3-none-any.whl

Download URL counterpart-0.1.9-py3-none-any.whl
Size 56.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
243213084871ab657ec0c0cf4029c8be6f9b4272407a8971b22dba7c8fe0d32c
BLAKE2b-256 checksum
How to use checksums
1607457bc0e61d9b20d84aa6592816472118a0ebfdd55aeadb8636740dae0618
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

This release

0.1.9 This release

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