Skip to content

Rich Interactions: extensible structured-interaction framework (identity intake = first kind)#176

Merged
brentrager merged 1 commit into
mainfrom
rich-interactions
Jul 3, 2026
Merged

Rich Interactions: extensible structured-interaction framework (identity intake = first kind)#176
brentrager merged 1 commit into
mainfrom
rich-interactions

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Why

Design directive from Brent: identity intake (shipped in 1.19.0, zero external consumers yet) must be the first instance of a general pattern, not a bespoke feature. Future kinds — date/appointment picker, choice chips, file upload, address, rating/CSAT, payment handoff, e-sign — must be cheap to add.

The pattern (docs/Architecture/Rich Interactions.md)

  • Generic envelope, typed kinds: interaction_required { interactionId, kind, spec, reason } / interaction_invalid { errors } + ONE resume verb submit_interaction { interactionId, kind?, values | declined }. Justified in the ADR: with a generic wire, a new kind is server-side only — no new events, no client-library release (old clients simply don't declare the capability and get the fallback). Per-kind precision lives in spec/interactions/<kind>.schema.json + the per-kind raise tools.
  • Per-kind capability list: supports: ["identity_form", …] at session create; the server picks rich vs conversational fallback per kind.
  • Server extension recipe: the InteractionKind trait — raise-tool schema (tool_schema/parse_request), server-side validator (validate → canonical payload or per-field errors), conversational degradation (fallback_directive) — registered in an InteractionRegistry. All park/bridge/resume machinery is kind-agnostic (one implementation).
  • Widget card registry (chat-widget repo, follow-up PR there): kind → overlay card; registering a card declares the capability. ADR notes the OTP + tool-approval overlays as prior instances to retrofit later.

identity_intake through the framework (unchanged behavior)

Validated inline form on capable clients; field-by-field validated conversational collection on text channels; identical canonical payload either way; session identity attach on the OTP contact keys. interactionId echo means a stale card can never resolve a newer park.

Supersedes 1.19.0's typed identity_intake_* events (removed — same release train, no consumers).

Tests

  • Rust: 399 passed (framework unit tests + reworked tests/interactions.rs: rich park/mismatch/invalid-stays-parked/valid-resume/decline, conversational directive + required-ness stash + re-ask, capability recording incl. unknown-capability forward-compat, no-pending error) — fmt + clippy clean
  • TS 39, TS server 84, Go 46, Python 50+125, .NET 77 — all green (validators in TS/Python/.NET now load spec/interactions/)

🤖 Generated with Claude Code

https://claude.ai/code/session_01FczKhti6h4s6yhYXG6V86o

…raction framework

Brent's design directive: identity intake must be the FIRST INSTANCE of a
general pattern, not a bespoke feature. This reshapes the seam shipped in
1.19.0 (zero external consumers) into 'Rich Interactions':

- Generic wire envelope: interaction_required / interaction_invalid events
  + the single submit_interaction resume verb (interactionId echo guards
  stale submits). Per-kind precision lives in spec/interactions/<kind>
  schemas and the per-kind raise tools — so adding a kind (date picker,
  choice chips, file upload, address, CSAT, payment, e-sign) needs NO new
  events and NO client-library release.
- Capability negotiation is a per-kind supports list; the server picks
  rich card vs conversational fallback PER KIND.
- Server extension recipe: the InteractionKind trait (raise-tool schema +
  server-side validator + conversational-degradation directive) +
  InteractionRegistry; the park/bridge/resume machinery is kind-agnostic.
- identity_intake (capability identity_form) ships as the first kind
  through the framework, behavior unchanged: validated form on the widget,
  field-by-field validated conversational collection on SMS/voice, same
  canonical payload either way, session identity attach on the OTP
  contact keys.

Design record: docs/Architecture/Rich Interactions.md (names the pattern,
documents the extension recipe end-to-end, lists candidate kinds, and
notes the OTP/tool-approval overlays as prior instances to retrofit).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FczKhti6h4s6yhYXG6V86o
@changeset-bot

changeset-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ae7cf79

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 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

@brentrager brentrager merged commit 85e5643 into main Jul 3, 2026
7 checks passed
brentrager added a commit that referenced this pull request Jul 5, 2026
…I) (#183)

pr-kind-deploy-smoke.yml checked out SmooAI/smooth-operator-core with no `ref`,
so when core@main advanced (multimodal `Message.images` field), this repo's main
stopped compiling against it and `cargo build --locked` failed the lock check —
turning EVERY open PR red for reasons unrelated to its own diff.

Why pin: the cross-repo path-dep means a moving core@main is an uncontrolled
input to this repo's CI. Pinning to a known-good SHA makes core adoption a
deliberate, reviewable bump instead of a silent break. Interim until core
publishes to crates.io and the cross-repo checkout goes away.

- runtime.rs / runner.rs: add `images: vec![]` to the replayed-history
  EngineMessage constructions (text-only, no attachments).
- Fix stale test literals missing new struct fields: suggested_replies.rs
  (`identity_intake` → `interactions`, folded in #176) and serve_smoke.rs
  (ServerConfig + TurnRequest new fields).
- Regenerate Cargo.lock against core@main (`cargo update -p
  smooai-smooth-operator-core`) so `--locked` passes.
- Pin the CI core checkout to 3c7b21dbde4f31519b2eab3d5343f154119fe655.

Gates: cargo fmt --check, cargo clippy (no errors), cargo test --workspace
(69 test binaries, 0 failed), and `cargo build --locked` all green against the
pinned core.

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