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

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.10
File Size Uploaded
counterpart-0.1.10.tar.gz 143.4 kB Details

Built distribution (wheel)

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

Total release size: 199.8 kB

Release files / counterpart-0.1.10.tar.gz

Download URL counterpart-0.1.10.tar.gz
Size 143.4 kB
Tags Source
SHA-256 checksum
How to use checksums
1a7b02cdc33233367fb94cd3d6a91294f6de371da1eeaa1a1980359b9a35f3f9
BLAKE2b-256 checksum
How to use checksums
5596363a7c0991f820a6172b30b1b3a421c71298ca54df251ef4375eea9ad2db
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.10-py3-none-any.whl

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

This release

0.1.10 This release

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