Repository navigation
feat(inputlayer-py): routed connection core with request ids, deadlines and reconnect - #236
Merged
Merged
Conversation
…econnect Replace the Python SDK's lock-around-send-and-receive connection with one reader task per connection that routes every frame: replies by request id (streamed results assembled and checked against their end counts), standing-query pushes by subscription and generation (stale ones dropped and counted), notifications to the dispatcher, notices logged. - Every request carries an id; requests are pipelined with at most 15 unanswered, leaving one server slot free for a cancel. - Each call has a deadline sent as timeout_ms, with a local backstop that cancels the request on the server; cancelling the awaiting task cancels it too. New typed errors: DeadlineExceeded, Cancelled, ConnectionLost (with may_have_committed), RateLimited (retried once), ProtocolError. - Notifications and deltas arrive while idle; a keepalive ping keeps a listening client under the server's idle timeout. - A dropped connection fails pending calls and reconnects with jittered exponential backoff to the same knowledge graph, resuming notifications from the last seq and epoch; il.events reports disconnected, session_reset, reconnected, notification_gap and closed. - One connection per knowledge graph handle, bound with ?kg= (a missing graph is created on first use); the SDK no longer sends .kg use. Tests: shared frame fixtures in packages/conformance/connection replayed through a real-socket mock server, timing and pool tests, live tests in make python-test-live, and a fast python-sdk CI job for the unit suite.
…d pool lifecycle bugs
This was referenced Oct 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
So fix the SDKs by improving them. (Context: the captain asked to rework the SDKs so agents and agentic loops use InputLayer without writing Datalog, and to fix the SDK release blockers by improving the SDKs rather than patching. The agreed design, signed off by an independent challenger after three rounds, is ~/firstmate/data/sdk-design/report.md; this item is its phase-1 PR P1-D P1-D (Py).)
The referenced item, P1-D of the agreed design (Python): the connection core. Replace the Python SDK connection (a lock around send-and-receive, with the receive loop never started, so no notifications or subscription pushes arrive while idle and subscriptions cannot be hosted) with: one reader task that reads every frame and routes it - frames with an id to the call registered under that id, with result_start/chunk/end assembled there and checked against the announced counts; subscription pushes routed by subscription and generation, a stale generation dropped and counted; notifications to the notification dispatcher; notices logged, and closing ones fail every pending call with ConnectionLost(code). One connection per knowledge graph handle, bound at connect time with ?kg=, never sending .kg use on a connection that could hold subscriptions. Ids on every request (r per connection), at most ws_max_in_flight_requests - 1 in flight (default 15) so a cancel can always be sent, queries pipelined and replies matched by id so the execute lock disappears. Deadlines and cancel: timeout= sets timeout_ms; a local timer at timeout + grace sends cancel and raises DeadlineExceeded if no reply arrives; a cancel_ack of too_late means the write committed and the reply is awaited. Idle delivery: the reader always runs, and an application ping is sent every keepalive seconds (default 20) when nothing else was sent so a subscription-only client never hits idle_timeout. Pending calls fail on close with ConnectionLost, flagging writes that may have committed (may_have_committed) so the caller re-reads before retrying. Reconnect with exponential backoff 1 s to 30 s with jitter, re-authenticating to the same knowledge graph, resuming the notification cursor with last_seq/epoch, reporting a notification gap on replay_gap and a session_reset event (plus reconnected) on il.events; max_reconnect_attempts exhausted ends iterators with ConnectionLost. Errors per the design: DeadlineExceeded, Cancelled, ConnectionLost(code, may_have_committed), RateLimited (retried once after the window), ProtocolError for invalid_request (an SDK bug, and it says so). Tests: mock-server unit tests replaying shared conformance frame fixtures (chunked results, missing chunk, stale generation, replay_gap, close mid-stream, duplicate id rejection), plus live tests against a real engine that run in CI. Millisecond latency must not regress. Other phase-1 items (compile path, subscribe/watch/on, claim, README) are separate PRs and out of scope.
What Changed
r<n>), and chunked results are assembled and checked against the announced counts. Subscription pushes are routed by subscription and generation, with stale generations dropped. Notifications go to a shared dispatcher, and a closing notice fails every pending call. Queries are now pipelined, with at mostws_max_in_flight_requests - 1in flight. Each knowledge graph handle gets its own connection, bound with?kg=<name>. The client exposesil.events(disconnected,reconnected,session_reset,notification_gap,closed) plus newdefault_timeout,keepaliveandepochoptions.canceland raisesDeadlineExceededwhen no reply arrives in time; atoo_lateack means the call keeps waiting for its reply. An application ping is sent when the connection has been idle for the keepalive interval. Reconnect uses exponential backoff with jitter, re-authenticates to the same graph and resumes the notification cursor (last_seq/epoch). New typed errors:DeadlineExceeded,Cancelled,ConnectionLost(code, may_have_committed),RateLimited(retried once after the window) andProtocolError. All are exported, and the Python SDK guide documents them.packages/conformance/connection/. They cover chunked results, missing chunks, count mismatch, stale generation,replay_gap, close mid-query/mid-stream, duplicate id rejection, rate limiting and typed error codes. Mock-server unit tests replay them, and the connection tests were rewritten against the mock server. Also added live engine tests (test_connection_live.py), wired intomake python-test-live. A new path-filteredPython SDKCI job runsmake python-test.Risk Assessment
Testing
I ran the targeted mock-server and conformance tests: 118 passed. The one failure is test_sync.py::test_loop_thread_custom_timeout, which this change does not touch and which fails on Python 3.10. Then I built the engine from this branch and ran the 7 live connection tests plus a custom live script against isolated local servers. All of these passed: pipelining past the 15-request in-flight limit, idle notifications, subscriptions surviving queries on another handle, deadline, cancel, reconnect with replay and session_reset/reconnected, notification_gap after a reconnect with no cursor, keepalive under a 3 s idle timeout (the control run without keepalive was closed with idle_timeout), and ConnectionLost when the server was killed (may_have_committed=True for the write, False for the query, iterators ended). Dropping a graph left no connection on it and a stale handle could not recreate it; close then reopen worked again. Latency was compared with the base SDK. One latency run on the rate-limited server ended in RateLimited and then ConnectionLost when 500 requests were fired at once; that is the server's 100 msg/s limit, so the comparison was re-run with the limit off. The conformance-fixture scenarios ran only through the mock server, not live. This is an SDK change with no UI, so there are CLI transcripts instead of screenshots. All servers, temp data and build output were removed afterwards.
Evidence: Live connection tests against the real engine
Evidence: Custom live scenario driver script
Evidence: Drop graph, close and reopen, no-cursor gap transcripts (plus the rate-limited latency run)
Evidence: Keepalive vs no-keepalive under a 3 s server idle timeout
no keepalive: events=[('disconnected','idle_timeout'),('closed','idle_timeout'),...] state=closed keepalive=1s: after 10s idle events=[] push inserted=[[1]] state=openEvidence: Server SIGKILL with calls in flight
query ConnectionLost code= None may_have_committed= False write ConnectionLost code= None may_have_committed= True notifications iterator: ["ConnectionLost: Connection to 'default' closed and not reconnected"]Evidence: Latency: head SDK vs base SDK
head: sequential p50 0.59-0.72ms p99 0.84-1.02ms; 500 concurrent 73-81ms base: sequential p50 0.55-0.57ms p99 0.73-0.81ms; 500 concurrent 290-313msPipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 8 issues found → auto-fixed (2) ✅
packages/inputlayer-py/src/inputlayer/client.py:155-_create_kgruns.kg create <name>on the client's own connection. The engine's KgCreate switches the session to the new graph (src/protocol/handler.rs:2614-2627 setsswitched_kg_result = Some(name)), andConnection._accept(connection.py:845-846) records that as the main connection's_current_kg. Failing sequence:InputLayer(url, initial_kg="a"), connect,a = il.knowledge_graph("a")(bound to the main connection via_connection_for),b = il.knowledge_graph("b"), thenawait b.execute(...)on a missing "b" calls_create_kg("b")and moves the main session to "b". After that,a.execute(...)/a.query(...)silently read and write graph "b", reconnects of the main connection re-bind "b", and the switch drops the main session's state and subscriptions. This breaks the intent's invariant: "never sending .kg use on a connection that could hold subscriptions". The live testtest_connection_live.py:72passes only because it never touches a main-connection handle after a create. Fix: run the create on a short-lived connection that no handle uses (for example a temporaryConnectionbound to the server default, closed after the create), not onself._conn.drop_knowledge_graph's.kg use defaultonself._conn(client.py:208-209) is another switch of a handle-bearing connection; route it through the same throwaway admin connection.packages/inputlayer-py/src/inputlayer/connection.py:470- Whenauthenticatedreports a newstream_epoch(the engine restarted),_authenticatereplaces_epochbut keeps_last_seqfrom the old run._deliver_notificationthen computesmax(self._last_seq, frame.seq), so the cursor stays at the old high value. Failing sequence: the client has seen seq 100, the engine restarts, the reconnect getsreplay_gap, and new-run notifications 1..60 arrive while_last_seqstays 100. On the next drop at new seq 60, the reconnect sendslast_seq=100&epoch=<new>, so the server skips everything up to 100 and notifications 61..100 of the outage are lost with no gap reported.NotificationDispatcher._last_seq(notifications.py,dispatch) andInputLayer.last_seqreport the same stale max. Fix: reset_last_seqto 0 (and the dispatcher's last_seq) when the epoch changes, so the cursor counts in the new run.packages/inputlayer-py/src/inputlayer/connection.py:512-timeout_msis sent as the fulltimeout, but the local deadline (and its backstop,_await_replyline 611) started before_ensure_openand the slot wait. Failing sequence: 15 calls are in flight and a 16th waits 10 s for a slot (default timeout 30 s). The server deadline then falls at ~40 s, while the local backstop fires at ~31 s and sendscancel. The engine answerserror code=cancelled, so the caller getsCancelledalthough it never cancelled and only its deadline passed. The same happens when_ensure_openwaits for a reconnect. Fix: sendtimeout_msfrom the remaining time (self._remaining(deadline)), so the server'sdeadline_exceededarrives before the local backstop. Related:execute's RateLimited retry (line 494-495) restarts a full timeout, so one call can take about 2×timeout+1 s.packages/inputlayer-py/src/inputlayer/connection.py:944- Every pooled per-KG connection shares the client's singleNotificationDispatcher(client.py_options['dispatcher']), and_give_upcallsself._dispatcher.fail(...)on it. When one graph's connection gives up, everyil.notifications()iterator of the whole client ends with ConnectionLost, even though the other connections still deliver notifications. Concrete triggers: a pooled graph is dropped by another client (the reconnect gets auth_error,_reconnectbreaks and calls_give_up), orauto_reconnect=Falseand a single pooled socket drops (_on_lostcalls_give_up). Fix: fail the shared dispatcher only when the client's own connection (or every connection) has given up, and report a single graph's loss via itsclosedevent.packages/inputlayer-py/src/inputlayer/connection.py:888- The keepalive pings whenever nothing was sent forkeepaliveseconds, even with requests in flight. The server's idle timer cannot fire while requests are pending (ws.rs:if ... requests.is_idle()), so these pings are unnecessary. They also hurt: the server admits pings into the same in-flight pipeline and releases replies in order (ws/pipeline.rs). With 15 slow executes outstanding for more than 20 s, the ping takes the 16th server slot until all 15 are answered, so the deadline-backstopcancel(or a task-cancellation_fire_cancel) is not read by the server. This breaks the intent's "at most ws_max_in_flight_requests - 1 in flight so a cancel can always be sent"._fire_cancel(line 657) has the same gap when several callers cancel at once: each cancel uses a server slot the client never accounts for. Fix: skip the keepalive ping while_pendingis non-empty. Also, when_sendfails, the loop never updates_loop_last_send(line 889 recomputes wait <= 0), so if the socket is already closed but the reader has not torn down yet, the loop re-registers and re-sends pings back to back. Advance_loop_last_send(or break) after a failed send.packages/inputlayer-py/src/inputlayer/connection.py:652-_cancel_overdueraisesDeadlineExceededwhen neither the reply nor thecancel_ackarrives within the grace period.DeadlineExceededdocuments "nothing it would have changed is applied" (exceptions.py, docs python-sdk.mdx). For a write, that claim is unproven here: nocancelledreply or ack arrived, and the server may still commit the write (or acktoo_lateafter the client stopped waiting). The caller then retries without reading back first. The intent asks for DeadlineExceeded in this case, so changing the error type or adding amay_have_committedflag to this path is a product/API decision: should a write that hits the local backstop surface as uncertain (as ConnectionLost does)?packages/inputlayer-py/src/inputlayer/client.py:94-InputLayer.close()closes every pooled connection but keeps_pooland the cached_kgshandles. A lazyConnectionin stateclosednever reopens:_ensure_openraises ConnectionLost (connection.py:543-546). Before this change, afterawait il.close(); await il.connect()(or two consecutiveasync with il:blocks) the same client and its handles worked again. Now every graph handle obtained earlier, and every newil.knowledge_graph(name)for a name already in_pool, fails with "the connection is closed". Fix: clear_pool/_kgson close, or let a lazy connection reopen fromclosedon the next call.packages/inputlayer-py/src/inputlayer/connection.py:393- The reconnect sends a resume cursor only when_last_seq is not None, i.e. only after at least one notification was received. Sequence: a connection that has seen no notification drops, the graph changes during the outage, and the reconnect sends nolast_seq, so the change is never replayed and nonotification_gapis emitted.authenticatedcarries no current seq, so the fix needs a decision: sendlast_seq=0&epoch=...(which replays older events) or emitnotification_gapafter any cursor-less reconnect.🔧 Fix applied.
10 issues (2 errors, 6 warnings, 2 infos) still open:
packages/inputlayer-py/src/inputlayer/client.py:155-_create_kgruns.kg create <name>on the client's own connection. The engine's KgCreate switches the session to the new graph (src/protocol/handler.rs:2614-2627 setsswitched_kg_result = Some(name)), andConnection._accept(connection.py:845-846) records that as the main connection's_current_kg. Failing sequence:InputLayer(url, initial_kg="a"), connect,a = il.knowledge_graph("a")(bound to the main connection via_connection_for),b = il.knowledge_graph("b"), thenawait b.execute(...)on a missing "b" calls_create_kg("b")and moves the main session to "b". After that,a.execute(...)/a.query(...)silently read and write graph "b", reconnects of the main connection re-bind "b", and the switch drops the main session's state and subscriptions. This breaks the intent's invariant: "never sending .kg use on a connection that could hold subscriptions". The live testtest_connection_live.py:72passes only because it never touches a main-connection handle after a create. Fix: run the create on a short-lived connection that no handle uses (for example a temporaryConnectionbound to the server default, closed after the create), not onself._conn.drop_knowledge_graph's.kg use defaultonself._conn(client.py:208-209) is another switch of a handle-bearing connection; route it through the same throwaway admin connection.packages/inputlayer-py/src/inputlayer/connection.py:470- Whenauthenticatedreports a newstream_epoch(the engine restarted),_authenticatereplaces_epochbut keeps_last_seqfrom the old run._deliver_notificationthen computesmax(self._last_seq, frame.seq), so the cursor stays at the old high value. Failing sequence: the client has seen seq 100, the engine restarts, the reconnect getsreplay_gap, and new-run notifications 1..60 arrive while_last_seqstays 100. On the next drop at new seq 60, the reconnect sendslast_seq=100&epoch=<new>, so the server skips everything up to 100 and notifications 61..100 of the outage are lost with no gap reported.NotificationDispatcher._last_seq(notifications.py,dispatch) andInputLayer.last_seqreport the same stale max. Fix: reset_last_seqto 0 (and the dispatcher's last_seq) when the epoch changes, so the cursor counts in the new run.packages/inputlayer-py/src/inputlayer/connection.py:512-timeout_msis sent as the fulltimeout, but the local deadline (and its backstop,_await_replyline 611) started before_ensure_openand the slot wait. Failing sequence: 15 calls are in flight and a 16th waits 10 s for a slot (default timeout 30 s). The server deadline then falls at ~40 s, while the local backstop fires at ~31 s and sendscancel. The engine answerserror code=cancelled, so the caller getsCancelledalthough it never cancelled and only its deadline passed. The same happens when_ensure_openwaits for a reconnect. Fix: sendtimeout_msfrom the remaining time (self._remaining(deadline)), so the server'sdeadline_exceededarrives before the local backstop. Related:execute's RateLimited retry (line 494-495) restarts a full timeout, so one call can take about 2×timeout+1 s.packages/inputlayer-py/src/inputlayer/connection.py:944- Every pooled per-KG connection shares the client's singleNotificationDispatcher(client.py_options['dispatcher']), and_give_upcallsself._dispatcher.fail(...)on it. When one graph's connection gives up, everyil.notifications()iterator of the whole client ends with ConnectionLost, even though the other connections still deliver notifications. Concrete triggers: a pooled graph is dropped by another client (the reconnect gets auth_error,_reconnectbreaks and calls_give_up), orauto_reconnect=Falseand a single pooled socket drops (_on_lostcalls_give_up). Fix: fail the shared dispatcher only when the client's own connection (or every connection) has given up, and report a single graph's loss via itsclosedevent.packages/inputlayer-py/src/inputlayer/connection.py:888- The keepalive pings whenever nothing was sent forkeepaliveseconds, even with requests in flight. The server's idle timer cannot fire while requests are pending (ws.rs:if ... requests.is_idle()), so these pings are unnecessary. They also hurt: the server admits pings into the same in-flight pipeline and releases replies in order (ws/pipeline.rs). With 15 slow executes outstanding for more than 20 s, the ping takes the 16th server slot until all 15 are answered, so the deadline-backstopcancel(or a task-cancellation_fire_cancel) is not read by the server. This breaks the intent's "at most ws_max_in_flight_requests - 1 in flight so a cancel can always be sent"._fire_cancel(line 657) has the same gap when several callers cancel at once: each cancel uses a server slot the client never accounts for. Fix: skip the keepalive ping while_pendingis non-empty. Also, when_sendfails, the loop never updates_loop_last_send(line 889 recomputes wait <= 0), so if the socket is already closed but the reader has not torn down yet, the loop re-registers and re-sends pings back to back. Advance_loop_last_send(or break) after a failed send.packages/inputlayer-py/src/inputlayer/connection.py:652-_cancel_overdueraisesDeadlineExceededwhen neither the reply nor thecancel_ackarrives within the grace period.DeadlineExceededdocuments "nothing it would have changed is applied" (exceptions.py, docs python-sdk.mdx). For a write, that claim is unproven here: nocancelledreply or ack arrived, and the server may still commit the write (or acktoo_lateafter the client stopped waiting). The caller then retries without reading back first. The intent asks for DeadlineExceeded in this case, so changing the error type or adding amay_have_committedflag to this path is a product/API decision: should a write that hits the local backstop surface as uncertain (as ConnectionLost does)?packages/inputlayer-py/src/inputlayer/client.py:94-InputLayer.close()closes every pooled connection but keeps_pooland the cached_kgshandles. A lazyConnectionin stateclosednever reopens:_ensure_openraises ConnectionLost (connection.py:543-546). Before this change, afterawait il.close(); await il.connect()(or two consecutiveasync with il:blocks) the same client and its handles worked again. Now every graph handle obtained earlier, and every newil.knowledge_graph(name)for a name already in_pool, fails with "the connection is closed". Fix: clear_pool/_kgson close, or let a lazy connection reopen fromclosedon the next call.packages/inputlayer-py/src/inputlayer/connection.py:393- The reconnect sends a resume cursor only when_last_seq is not None, i.e. only after at least one notification was received. Sequence: a connection that has seen no notification drops, the graph changes during the outage, and the reconnect sends nolast_seq, so the change is never replayed and nonotification_gapis emitted.authenticatedcarries no current seq, so the fix needs a decision: sendlast_seq=0&epoch=...(which replays older events) or emitnotification_gapafter any cursor-less reconnect.packages/inputlayer-py/src/inputlayer/client.py:221- Round 1's fixes (admin connection for.kg drop, and lazy reopen afterclose()inconnection.py:555) leftdrop_knowledge_graphwith two wrong outcomes that don't raise errors. (1) The round removed the.kg use defaultstep instead of replacing it. Whennameis the client's own connection's graph (InputLayer(url, initial_kg="a")followed bydrop_knowledge_graph("a")), the server allows the drop from the admin session, because the KgDrop check in handler.rs:2661 only compares against the dropping session's own graph. The main connection is then still bound to the dropped graph.il.knowledge_graph("a")then returns a handle onself._conn(_connection_forline 140 matchescurrent_kg) that never recreates the graph. The main connection's next reconnect binds?kg=a, gets auth_error and gives up, and because the main connection hasends_notifications=Truethis ends everyil.notifications()iterator. (2) The pooled connection closed at line 223 has_closing=Trueandlazy=True. AnyKnowledgeGraphhandle the user kept from before the drop therefore reopens it on its next call (connection.py:555-558)._open_bound(first=True)then gets auth_error for the missing graph and callscreate_kg, sokg.execute("+x(1)")silently recreates the dropped graph and writes into it. Before this round, that call raised ConnectionLost. Fix: make a connection closed by a drop terminal: close it in a way the lazy reopen does not apply to, e.g. clear_lazy/_create_kgor use a dedicated flag. When the dropped graph isself._conn.current_kg, move the main connection off it: either reconnect it bound to the default graph or run.kg use defaultthere, which is acceptable because that graph's handle and subscriptions are gone. Also make_connection_forstop returningself._connfor that name.packages/inputlayer-py/src/inputlayer/connection.py:555- The lazy reopen added in round 1 can start whileclose()is still running.close()setsstate='closed'and_closing=Truefirst, then awaits the reconnect-task cancel,ws.close()and the reader. A call made in that window reachesconnect()and opens a new socket.close()then runs_teardown(old_ws, ...), which cancels the new socket's_keepalive_taskand fails the new socket's pending calls with ConnectionLost. Concurrent close-and-call is rare, but the fix is small: only allow the reopen afterclose()has finished, for example by setting the reopenable flag at the end ofclose().🔧 Fix applied.
✅ Re-checked - no issues remain.
packages/inputlayer-py/tests/test_sync.py:641- tests/test_sync.py::test_loop_thread_custom_timeout fails on the local Python 3.10. Before 3.11, concurrent.futures.TimeoutError is a different class from the builtin TimeoutError. This change does not modify _LoopThread or that test.uv run --extra dev pytest tests/test_connection.py tests/test_connection_conformance.py tests/test_statement_errors.py tests/test_sync.py -q(mock server replaying the conformance fixtures: 118 passed, 1 failed, test_sync.py::test_loop_thread_custom_timeout, which this change does not touch; it fails on Python 3.10)cargo build --release --bin inputlayer-server, then an isolated server per scenario (temp --data-dir, ports 18089/18090/18091)INPUTLAYER_TEST_SERVER=ws://127.0.0.1:18089/ws uv run --extra dev pytest tests/test_connection_live.py -v(7/7 passed against the live engine)live_scenarios.py drop_graphagainst the live enginelive_scenarios.py close_reopenagainst the live enginelive_scenarios.py cursorless_gapagainst the live enginelive_scenarios.py no_keepalive_idleandkeepalive_idleagainst a server started with INPUTLAYER_HTTP__WS_IDLE_TIMEOUT_MS=3000live_scenarios.py kill_inflight, with the harness SIGKILLing inputlayer-server while a write and a query were in flightlive_scenarios.py latency, 3 runs each for the head SDK and the base SDK (ea9d5d3c, extracted with git archive), against a server with the rate limit off✅ **Document** - passed
✅ No issues found.
packages/inputlayer-py/src/inputlayer/connection.py-ruff format --checkwould reformat 11 of the changed Python SDK files (connection.py, client.py, the tests and others). This is not new to this change: at the base commit, 58 of the package's files were already unformatted, and neither CI nor the Makefile runs ruff format. Reformatting only these files would add a large whitespace-only diff to an unrelated change, so I left them alone. If formatting is wanted, a one-timeruff formatof the whole package in a separate commit, with a CI check added, is the follow-up.ruff checkpasses. mypy reports 54 errors, all of which already existed (56 at the base commit), so this change adds no new ones.✅ **Push** - passed
✅ No issues found.