feat(python): native Python server — parity with the Rust/C# server#59
Merged
Conversation
…consuming smooai-smooth-operator-core
Adds python/server/ — a native async WebSocket server for the smooth-operator
protocol, the Python parity of the Rust (rust/smooth-operator-server) and C#
(dotnet/server) reference servers. Until now Python was a client only; this fills
the gap. New code lives entirely in python/server/ (the client at python/src is
untouched).
It consumes the published in-process engine (smooai-smooth-operator-core): each
turn builds a SmoothAgent and maps its run_stream events onto the wire protocol
(text deltas -> stream_token, tool calls/results -> stream_chunk, terminal ->
eventual_response with citations).
Ported, piece by piece, from the C# server (verified against the Rust reference):
- protocol.py <- ProtocolEvents.cs / protocol.rs (byte-for-byte event shapes)
- session_store.py <- SessionStore.cs / the Rust storage adapter surface
- turn_runner.py <- TurnRunner.cs / runner.rs (streaming turn + memory replay)
- dispatcher.py <- FrameDispatcher.cs / handler.rs (action routing)
- auth.py <- Auth.cs / the Rust verifier seam (permissive + local HS256 JWT)
- server.py <- SmoothOperatorWebSocketExtensions.cs / server.rs + local.rs
(per-connection read loop, single outbound writer, graceful
SIGTERM/SIGINT drain with detach-after-loop, serve_local)
- backplane.py <- the Rust backplane attach/detach seam (in-memory stub)
Graceful drain follows the spec: a shared asyncio.Event cancel on ServerState,
checked first each iteration, raced against the next inbound frame, with the turn
dispatch awaited inside the frame branch so an in-flight turn finishes before the
loop exits; a backplane detach always runs after the loop.
Tests (pytest, 17 passing): protocol conformance (round-trips the
spec/conformance/fixtures.json golden messages), boot, full turn round-trip on
MockLlmProvider (create_conversation_session -> send_message -> stream_token(s) +
eventual_response), graceful-drain (cancel mid-turn -> turn finishes -> loop exits
-> detach), and the auth verifier seam.
MVP-stubbed seams (left open, not wired): ACL-filtered retrieval / rerank,
tool-confirm/HITL, and the Redis/NATS cross-pod backplane.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U7Mn93HpqhSgEmX6tRdPAv
|
brentrager
added a commit
that referenced
this pull request
Jun 24, 2026
… shared parity corpus (#69) The TS/Python/Go servers landed (#59/#60/#61) and all five now run the shared spec/conformance/scenarios corpus (#62-#67) — tested protocol parity. Updates the servers row + tests section from 'in progress' to the real state. Claude-Session: https://claude.ai/code/session_01U7Mn93HpqhSgEmX6tRdPAv Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
What
Adds
python/server/— a native async WebSocket server for the smooth-operator protocol, the Python parity of the Rust (rust/smooth-operator-server) and C# (dotnet/server) reference servers. Until now Python was a client only (python/src); this fills the gap. The client is untouched — all new code lives inpython/server/.It consumes the published in-process engine
smooai-smooth-operator-core: each turn builds aSmoothAgentand maps itsrun_streamevents onto the wire protocol — text deltas →stream_token, tool calls/results →stream_chunk, terminal →eventual_response(with the citations field).Ported from the C# server (verified against the Rust reference)
protocol.pyProtocolEvents.cs/protocol.rs— byte-for-byte event shapes incl. the triple-nesteddata.dataand the duplicatederrordescriptorsession_store.pySessionStore.cs/ the Rust storage adapter session+message surfaceturn_runner.pyTurnRunner.cs/runner.rs— streaming turn + prior-history replay for memorydispatcher.pyFrameDispatcher.cs/handler.rs—ping/create_conversation_session/get_session/send_messageroutingauth.pyAuth.cs/ the Rust verifier seam — permissive default + local HS256-JWT verifier (LocalTokenVerifier), both fail-closed to anonymousserver.pySmoothOperatorWebSocketExtensions.cs/server.rs+local.rs— per-connection read loop, single outbound writer (asyncio.Queue), graceful drain,serve_local()backplane.pyGraceful SIGTERM drain (per the spec)
A shared
asyncio.Eventcancel switch onServerStateis the single source of truth for "stop" (default unset). Each connection loop checks the cancel first every iteration (prefer cancel on ties), then races "cancel set" vs "next inbound frame" — with the turn dispatch awaited inside the frame branch so an in-flight turn finishes before the loop exits.SIGTERM/SIGINThandlers stop accepting + set the cancel. A backplanedetachalways runs after the loop (detach-after-loop).Tests — pytest, 17 passing
spec/conformance/fixtures.jsongolden messages through the protocol builders + a JSON encode/decode.serve()starts on an ephemeral port and accepts a connection (ping→pong).websocketsclient →create_conversation_session→send_message(engine onMockLlmProvider) →stream_token(s) + terminaleventual_responsewith the expected text (mirrorsWebSocketProtocolIntegrationTests.cs).?token=resolution path.Run it
MVP-stubbed seams (left open, not wired)
ACL-filtered retrieval / rerank, tool-confirm/HITL, and the Redis/NATS cross-pod backplane — each a documented seam mirroring the Rust server's later phases.
🤖 Generated with Claude Code