Skip to content

feat: asyncio client proof of concept (at_client.aio)#531

Draft
cconstab wants to merge 2 commits into
trunkfrom
rfc/asyncio-client-poc
Draft

feat: asyncio client proof of concept (at_client.aio)#531
cconstab wants to merge 2 commits into
trunkfrom
rfc/asyncio-client-poc

Conversation

@cconstab

@cconstab cconstab commented Jul 7, 2026

Copy link
Copy Markdown
Member

Draft / RFC — a working artifact for the "redo notifications/monitor with asyncio and streams" discussion, not a merge request as-is.

What this is

A parallel at_client.aio package (the httpx/redis-py pattern — the synchronous client is untouched, zero risk to existing consumers):

client = await AsyncAtClient.create("@alice")
await client.notify("@bob", "demo", "hello", namespace="myapp")
async for notification in client.monitor(regex="myapp", last_received_time=epoch):
    print(notification.value)   # decrypted

Only the connection layer is new (~330 lines: connection.py + atclient.py); crypto, key handling and verb builders are reused from the existing package — they do no I/O.

Why asyncio pays here

The sync monitor's recent bug classes become unrepresentable by construction:

Verified

  • Unit (test/aio_client_test.py, network-free, in CI): monitor line parsing (incl. prompt remnants), decrypt round-trip with ivNonce and legacy zero-IV, response framing parity with the sync rules.
  • Live on the ephemeral environment, interoperating with the released 0.2.70 sync client — 5/5:
Check Result
sync notify → async monitor receives + decrypts
async notify → sync monitor receives (first contact: async side created the shared key)
resume: notification sent while offline is replayed from seeded epoch
resume: no backlog replay of already-seen notifications
cancellation leaves no stray tasks

Runnable example: examples/aio_demo.py.

Deliberately NOT in the PoC

get/put/delete/scan async twins, reconnect backoff ladder (Dart-style connectDelays), first-contact decrypt retry on the receive side, persisted resume position, docs. All straightforward follow-ups once the shape is agreed.

Open design questions for maintainers

  1. Package name/shape: at_client.aio vs at_client.asyncio vs a separate distribution?
  2. Long-term: maintain sync + async in parallel, or async-first with a sync shim in a future major version?
  3. AtNotification dataclass as the public stream type — happy with the field set?
  4. Should the monitor auto-persist its resume position (Dart persists lastreceivednotification in the local keystore) or leave persistence to callers, as here?

Colin Constable and others added 2 commits July 7, 2026 14:11
RFC/proof-of-concept for an asyncio-native client, as a parallel package so the
synchronous client is untouched: AsyncAtClient with PKAM auth, encrypted notify,
and a monitor exposed as an async iterator with a cancellable heartbeat task,
bounded reads, automatic reconnect and a seedable resume position.

Only the connection layer is new (~330 lines); crypto, key handling and verb
builders are reused from the existing package (they do no I/O). The structure
makes the thread-lifecycle and wedged-read failure modes of the sync monitor
unrepresentable: the heartbeat is a cancellable Task, reads are bounded by
asyncio.wait_for, and cancelling the consumer tears everything down.

Network-free tests in test/aio_client_test.py (monitor line parsing, decrypt
round-trip with ivNonce and legacy zero IV, response framing). Verified live
against the ephemeral environment interoperating with the released 0.2.70 sync
client: sync->async decrypt, async->sync notify with first-contact shared-key
creation, resume-from-epoch with no backlog replay, and clean cancellation.
Runnable example in examples/aio_demo.py.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant