interactions: add the choices kind (AskUserQuestion) — Rust reference + shared schema - #475
Merged
Merged
Conversation
… + shared schema
Adds `choices`, the second Rich Interaction kind, modeled on Claude Code's
AskUserQuestion: the agent raises 1–4 structured multiple-choice questions and
the turn parks until the visitor picks.
- Rust kind (`rust/smooth-operator/src/choices.rs`): `ChoicesKind` implementing
the `InteractionKind` seam — `request_choices { questions, reason }` raise
tool, `validate_choices` server-side validator (every question answered, each
label offered, single vs multiSelect, always-available free-text `other`
escape hatch), and the enumerated conversational fallback directive. Capability
id `choice_chips`. Registered in the default `InteractionRegistry`.
- Shared contract (`spec/interactions/choices.schema.json`): Spec / Values /
Payload $defs the other four servers + web SDK mirror, plus conformance
fixtures. Envelope stays generic (no per-kind enums).
- Tests: validator unit tests (single/multi/other/invalid) + a park/resume WS
integration test (rich card path + capability-less conversational fallback).
- Docs: Rich Interactions + Protocol Reference updated; `choices` moved from
candidate to implemented (Rust). Changeset added.
Wave 1 of the polyglot effort — this is the reference the ports follow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YbN45JeWDbcjvFqGJvmVD3
🦋 Changeset detectedLatest commit: a751647 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
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 |
This was referenced Aug 18, 2026
brentrager
added a commit
that referenced
this pull request
Aug 18, 2026
…ion) (#479) * python-server: Rich Interactions runtime + choices kind (AskUserQuestion) 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 * python-server: ruff format the new interaction modules + tests 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 --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Aug 18, 2026
Merged
brentrager
added a commit
that referenced
this pull request
Aug 18, 2026
…#483) Port the kind-agnostic Rich Interactions framework and the `choices` kind to the TypeScript server, mirroring the merged Rust reference (PR #475). Framework (interaction.ts): the InteractionKind seam, InteractionRegistry of hosted kinds, a session-keyed InteractionParkRegistry (the interaction analog of the write-confirmation registry), and the per-kind request_<kind> raise tool + generic submit_interaction fallback tool. A raise on a session that declared the kind's render capability parks the turn (the raise tool awaits inside execute), the server emits interaction_required, and a submit_interaction action validates + resumes (invalid -> retryable interaction_invalid, never terminal). A text-only channel degrades to the kind's conversational fallback directive. choices kind (choices.ts): request_choices (1-4 questions, header <=12 unique, 2-4 options, optional multiSelect), validate_choices rules mirrored one-for-one, capability choice_chips. Session `supports` is now persisted (in-memory + Postgres) and gates rich-vs-fallback per kind; the server hosts choices by default. Tests: validator/parse/kind unit tests + a WS park/resume integration test (raise -> interaction_required -> submit_interaction -> resume; invalid -> interaction_invalid -> resubmit; mismatch; no-pending; text-only fallback), all cross-checked against the shared spec/conformance choices fixtures. Claude-Session: https://claude.ai/code/session_01YbN45JeWDbcjvFqGJvmVD3 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Aug 18, 2026
brentrager
added a commit
that referenced
this pull request
Aug 18, 2026
…ion) (#484) Wave 2 of the polyglot Rich Interactions effort — mirror the merged Rust reference (#475) on the .NET (C#) server, Brent's tsx-dev-agent host. Framework (generalizes the write-confirmation park/resume kind-agnostically): - IInteractionKind / InteractionCatalog — the kind catalog (default = choices). - InteractionParkRegistry — session-keyed park store (peek vs resolve, so an invalid submit re-prompts without consuming the park), the interaction analog of ConfirmationRegistry. - RequestInteractionTool / SubmitInteractionTool — the per-kind request_<kind> raise tool (rich = park + interaction_required; fallback = conversational directive) and the generic submit_interaction fallback tool. - ProtocolEvents.InteractionRequired / InteractionInvalid — the double-nested data.data envelopes matching spec/events/*.schema.json. The choices kind (mirrors choices.rs exactly): request_choices (1–4 questions, header ≤12 unique, 2–4 options, optional multiSelect), validate_choices (all-answered; labels ∈ options; single = one label XOR other, multi = ≥1; blank other dropped; one-pass errors), fallback directive, capability id choice_chips. Server wiring: submit_interaction dispatch (validate → resume, invalid → retryable interaction_invalid, decline path, INTERACTION_MISMATCH / NO_PENDING_INTERACTION guards, ownership-scoped); per-connection `supports` capture at create_conversation_session drives the rich-vs-fallback branch; teardown/cancel unpark. The catalog is a DI-provided capability (AddSmoothOperatorServer registers InteractionCatalog.Default), so direct FrameDispatcher construction is unchanged. Tests: xUnit validator unit tests (ported from choices.rs, + the shared choices_* conformance fixtures through the C# validator) and a WS park/resume integration suite (raise → interaction_required → submit → resume; invalid → stays parked → resubmit; decline; text-only fallback; no-pending error). Claude-Session: https://claude.ai/code/session_01YbN45JeWDbcjvFqGJvmVD3 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
brentrager
added a commit
that referenced
this pull request
Aug 18, 2026
…#486) Port the Rich Interactions framework runtime + the `choices` kind to the Go LocalServer, mirroring the Rust reference (PR #475). Wave 2 of the polyglot rollout. - interaction.go: kind-agnostic framework — InteractionKind interface, the InteractionKinds catalog, and a per-connection park/resume InteractionRegistry (the analog of the write-confirmation ConfirmationRegistry). - choices.go: the `choices` kind (request_choices raise tool, validateChoices, fallback directive; capability choice_chips), mirroring choices.rs. - turn_runner.go: registers one raise tool per hosted kind — a kind whose declared capability parks the turn (emit interaction_required, block awaiting submit_interaction), the rest degrade to the conversational fallback. The raise tool's toolCall chunk is deferred + emitted before the park (as the confirmation path does) so ordering is deterministic. - dispatcher.go: captures `supports` at create_conversation_session and adds the submit_interaction action — validate via the kind, invalid -> retryable interaction_invalid (turn stays parked), valid -> canonicalize + resume. - protocol.go: interaction_required / interaction_invalid event builders. - server.go: teardown rejects parked interactions (fail-open to continue). Tests: validator unit tests + shared choices-fixture validation, and a WS park/resume integration suite (rich resume, invalid-stays-parked, and the no-capability conversational fallback). Claude-Session: https://claude.ai/code/session_01YbN45JeWDbcjvFqGJvmVD3 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Merged
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.
Problem
Agents constantly need a structured multiple-choice answer mid-turn ("which plan?", "what can we help with?") but today they free-form the menu in prose, with no server-side validation and no channel-normalized card. Rich Interactions is the framework for exactly this;
identity_intakewas the first (reference) kind. The design doc named "choice chips / menus" as a candidate — this ships it.Solution
choices— the second Rich Interaction kind, modeled on Claude Code's AskUserQuestion. Wave 1: the Rust reference kind + the shared JSON-Schema contract the other four servers and the web SDK mirror.request_choices { questions, reason }— 1–4 questions, each{ question, header (short ≤12-char label), options: [{ label, description }] (2–4), multiSelect? }.choice_chips— a session that declares it gets a parked chips/menu card (interaction_required { kind: "choices" }); text/voice channels inherit the enumerated conversational fallback ("choices = enumerated ask").{ answers: [{ header, options: [labels], other? }] }— every question keyed byheader, with an always-available free-textotherescape hatch (mirrors AskUserQuestion's ever-present "Other").validate_choices(shared by the card path's WS handler and the fallback path'ssubmit_interactiontool): every question answered, each selected label offered, single-select takes exactly one pick (label XORother), multi-select one or more. Invalid → retryable per-questioninteraction_invalid(never a terminal error). No host effect (choices collects, it doesn't mutate the session).InteractionRegistry; the generic envelope is unchanged (no per-kind enums).Verification
cargo test -p smooai-smooth-operator -p smooai-smooth-operator-server— green (574 passed).other; unknown label, single-select-over-pick, unanswered → per-field errors) +parse_questionscontract tests.tests/choices_interaction.rs): rich card path (park →interaction_required→ invalid submit stays parked → valid submit resumes with canonical payload) and the capability-less conversational fallback.spec/interactions/choices.schema.json(Spec/Values/Payload) + conformance fixtures; the fixture conformance test passes. clippy + rustfmt clean.choicesmoved from candidate to implemented (Rust).The other-language servers (TS / Python / Go / .NET) + web SDK follow as parity work against this reference + schema.
🤖 Generated with Claude Code
https://claude.ai/code/session_01YbN45JeWDbcjvFqGJvmVD3