Skip to content

feat(python): native Python server — parity with the Rust/C# server#59

Merged
brentrager merged 1 commit into
mainfrom
server-py
Jun 24, 2026
Merged

feat(python): native Python server — parity with the Rust/C# server#59
brentrager merged 1 commit into
mainfrom
server-py

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

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 in python/server/.

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 the citations field).

Ported from the C# server (verified against the Rust reference)

Module Ported from
protocol.py ProtocolEvents.cs / protocol.rs — byte-for-byte event shapes incl. the triple-nested data.data and the duplicated error descriptor
session_store.py SessionStore.cs / the Rust storage adapter session+message surface
turn_runner.py TurnRunner.cs / runner.rs — streaming turn + prior-history replay for memory
dispatcher.py FrameDispatcher.cs / handler.rsping / create_conversation_session / get_session / send_message routing
auth.py Auth.cs / the Rust verifier seam — permissive default + local HS256-JWT verifier (LocalTokenVerifier), both fail-closed to anonymous
server.py SmoothOperatorWebSocketExtensions.cs / server.rs + local.rs — per-connection read loop, single outbound writer (asyncio.Queue), graceful drain, serve_local()
backplane.py the Rust backplane attach/detach seam (in-memory stub)

Graceful SIGTERM drain (per the spec)

A shared asyncio.Event cancel switch on ServerState is 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/SIGINT handlers stop accepting + set the cancel. A backplane detach always runs after the loop (detach-after-loop).

Tests — pytest, 17 passing

  • Protocol conformance — round-trips the spec/conformance/fixtures.json golden messages through the protocol builders + a JSON encode/decode.
  • Bootserve() starts on an ephemeral port and accepts a connection (ping→pong).
  • Turn round-trip — real websockets client → create_conversation_sessionsend_message (engine on MockLlmProvider) → stream_token(s) + terminal eventual_response with the expected text (mirrors WebSocketProtocolIntegrationTests.cs).
  • Graceful-drain — set the cancel mid-turn → in-flight turn finishes → loop exits → detach runs.
  • Auth verifier seam — valid/expired/forged JWT + the ?token= resolution path.

Run it

cd python/server && uv sync && uv run pytest -q
python -m smooth_operator_server   # local flavor: in-memory, auth off, ws://127.0.0.1:8787/ws

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

…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
@changeset-bot

changeset-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 1935b85

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@brentrager
brentrager merged commit 9c3e8f8 into main Jun 24, 2026
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>
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