Skip to content

python-server: Rich Interactions runtime + choices kind (AskUserQuestion) - #479

Merged
brentrager merged 2 commits into
mainfrom
choices-interaction-py
Aug 18, 2026
Merged

python-server: Rich Interactions runtime + choices kind (AskUserQuestion)#479
brentrager merged 2 commits into
mainfrom
choices-interaction-py

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

Wave 2 of the full-polyglot Rich Interactions effort. The Rust reference merged in #475 (framework runtime + the choices kind / AskUserQuestion). This ports that runtime and the choices kind to the Python server, mirroring the reference.

Solution

Framework (interaction.py) — the kind-agnostic runtime, generalizing the existing write-confirmation park/resume:

  • InteractionKind protocol (kind / capability / tool_schema / parse_request / validate / fallback_directive), InteractionRegistry catalog (default: choices), InteractionRequest / InteractionOutcome / InteractionFieldError.
  • PendingInteractions — a session-keyed park/resume registry that is the kind-agnostic generalization of confirmation.py's ConfirmationRegistry (asyncio future instead of a bool verdict).

Per-turn tools (interaction_tools.py) — mirrors the Rust RequestInteractionTool + SubmitInteractionTool, with the bridge folded into the tool coroutine:

  • one request_<kind> raise tool per hosted kind. Rich channel (capability in supports): park — emit interaction_required, await submit_interaction, resume with the canonical payload. Fallback channel: return the kind's conversational directive (no park).
  • the generic submit_interaction tool, registered only when ≥1 kind is on the fallback path.

Wiring: submit_interaction dispatcher action (peek → match interactionId → validate → invalid emits retryable interaction_invalid and keeps the turn parked, valid resolves the park); capability gating from the session's declared supports (captured connection-local at create_conversation_session); interaction_required / interaction_invalid protocol builders (double-nested data.data, matching the shared event schemas).

The choices kind (choices.py) mirrors choices.rs: request_choices { questions (1–4), reason }, each question with a ≤12-char unique header and 2–4 options + optional multiSelect; validate_choices (every question answered; labels ∈ options; single-select one pick XOR other, multi-select ≥1; blank other dropped; all errors in one pass); enumerated fallback directive; capability id choice_chips.

Verification

  • cd python/server && uv run pytest364 passed (12 new: validator unit tests + the park/resume WS integration test — raise → interaction_requiredsubmit_interaction → resume; invalid-then-resubmit; fallback without capability).
  • The validator is checked against the shared spec/conformance/fixtures.json choices fixtures (spec + values → canonical payload).
  • ruff check + compileall clean.
  • One pre-existing test (test_tool_config_filters_tools_per_agent) updated: the framework interaction tools are always registered (not subject to the agent allow-list), mirroring the Rust runner.
  • Changeset added (@smooai/smooth-operator-server, minor).

🤖 Generated with Claude Code

…ion)

Port the Rich Interactions framework runtime and the `choices` kind to the
Python server (Wave 2 of the polyglot effort), mirroring the Rust reference
(PR #475).

Framework (interaction.py): kind-agnostic InteractionKind protocol,
InteractionRegistry catalog (default: choices), and a session-keyed
PendingInteractions park/resume registry generalizing the write-confirmation
ConfirmationRegistry. Per turn, the runner registers per-kind request_<kind>
raise tools (interaction_tools.py): rich channels park on an asyncio future
(emit interaction_required, await submit_interaction, resume with the canonical
payload); text-only channels degrade to the kind's conversational directive and
submit through the generic submit_interaction tool. A submit_interaction
dispatcher action routes values to the kind validator — invalid -> retryable
interaction_invalid (turn stays parked), valid -> resume. Capability gating from
the session's declared `supports` (choice_chips), captured connection-local.

The choices kind (choices.py) mirrors choices.rs: request_choices { questions
(1-4), reason } with 2-4 options + optional multiSelect; validate_choices (all
answered, labels in options, single=one pick XOR other, multi>=1, blank other
dropped, one-pass errors); enumerated fallback directive; capability choice_chips.

Tests: validator unit tests + the park/resume WS integration test (raise ->
interaction_required -> submit_interaction -> resume; invalid-then-resubmit;
fallback without capability), validating the shared choices conformance
fixtures. Full python/server suite green (364 passed); ruff + compileall clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YbN45JeWDbcjvFqGJvmVD3
@changeset-bot

changeset-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3c8b410

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@smooai/smooth-operator-server Minor

Not sure what this means? Click here to learn what changesets are.

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

CI runs `ruff format --check`; apply it (line-length reflow only, no logic
change).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YbN45JeWDbcjvFqGJvmVD3
@brentrager
brentrager enabled auto-merge (squash) August 18, 2026 22:47
@brentrager
brentrager merged commit c4911c8 into main Aug 18, 2026
3 checks passed
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