Back to Python Sdk

parallel-calls

examples/stories/parallel_calls/README.md

2.0.0b12.6 KB
Original Source

parallel-calls

Two Clients connected to the same server, each with a call_tool in flight at once. The meet tool is a rendezvous: a handler signals its own arrival, then blocks until every named peer has arrived too — so neither call can return unless the server runs both handlers concurrently. Each caller's progress_callback= sees only the notifications for its request — each Client is a separate connection, so there's no shared wire for them to cross on.

Run it

The tested legs run in-memory (Client(server)); the identical main body works unchanged over HTTP — both clients just reach the same server. Under --http the client self-hosts that server on a free port, runs, then tears it down:

bash
# --legacy because handler-emitted progress is dropped on the modern
# streamable-HTTP path today (see Caveats).
uv run python -m stories.parallel_calls.client --http --legacy
# same, against the lowlevel-API server variant
uv run python -m stories.parallel_calls.client --http --legacy --server server_lowlevel

There is no stdio run for this story: the stdio default spawns a fresh server subprocess per connection, so two clients there could never rendezvous.

What to look at

  • client.py — the two visible Client(targets(), mode=...) blocks. Each connection is constructed inside attend(...); targets() yields a fresh target on every call and both land on the same server instance. The two blocks run in one anyio task group.
  • server.py — the arrivals barrier. Each handler sets its own anyio.Event then waits for every peer's. A server that processed requests sequentially would never set the second event, so the client would time out — the timeout is the concurrency assertion. No sleeps.
  • client.pyprogress_callback= per call. Each call passes its own callback; received == {"a": ["a"], "b": ["b"]} shows each connection delivered its own progress, and — combined with the rendezvous — that both calls were genuinely in flight at once.
  • server_lowlevel.py — same wire contract on the lowlevel Server, reporting via ctx.session.report_progress(...).

Caveats

  • Over Streamable HTTP in the modern (2026-07-28) era, handler-emitted progress is currently dropped (the single-exchange dispatch context no-ops notify()). In-memory (both eras) and legacy-era HTTP deliver progress correctly — hence the --legacy above.

Spec

Progress flow

See also

streaming/ (progress + cancellation on one call), reconnect/ (the other multi-connection client), tools/ (basics).