examples/stories/parallel_calls/README.md
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.
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:
# --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.
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.py — progress_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(...).notify()).
In-memory (both eras) and legacy-era HTTP deliver progress correctly — hence
the --legacy above.streaming/ (progress + cancellation on one call), reconnect/ (the other
multi-connection client), tools/ (basics).