Skip to content

python-server: identity_intake interaction kind + host-effect seam - #501

Merged
brentrager merged 1 commit into
mainfrom
identity-intake-py
Aug 19, 2026
Merged

python-server: identity_intake interaction kind + host-effect seam#501
brentrager merged 1 commit into
mainfrom
identity-intake-py

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

The Python server hosts the Rich Interactions framework and the reference choices kind, but choices has no side effect — so the framework never grew the host-effect seam a stateful kind needs, and it hosts only one kind. The Rust reference already has a second kind, identity_intake (structured name/email/phone lead capture), whose accepted submit stamps the captured contacts onto the session (the keys the OTP contact seam and the pre-chat/create readers consume).

Solution

Port identity_intake to the Python server, mirroring the Rust reference, and add the kind-routed host-effect seam.

Host-effect seam (kind-agnostic). InteractionKind.host_effect(store, session_id, values) defaults to a no-op. It fires on a valid submit on both paths:

  • the dispatcher's submit_interaction action (rich path) — before the park resumes, mirroring the Rust handle_submit_interaction;
  • the conversational-fallback submit_interaction tool — the same seam the Rust InteractionConfig.attach callback fires.

choices inherits the no-op, so it is unaffected.

identity_intake kind (identity_intake.py):

  • raise tool request_identity_intake { fields, reason } (structured or shorthand fields; closed set name/email/phone);
  • one server-side validator — required fields present, email shape, phone → E.164, every failed field reported in one round-trip;
  • conversational fallback directive; capability identity_form;
  • host_effect stamps user_name / contact_email / contact_phone onto the session.

Session + OTP wiring: StoredSession gains user_name + contact_phone (the pre-chat create path now persists the name too); attach_session_identity is the in-memory store's stamp (durable/CRM attach stays a host concern, as in Rust); the OTP contact seam now offers the captured phone (SMS) alongside email, so a captured contact is immediately OTP-verifiable.

Registered in the default interaction catalog alongside choices.

Verification

  • New test_identity_intake_validator.py: validator/parser units mirroring the Rust reference tests + the shared spec/conformance/fixtures.json golden shapes.
  • New test_identity_intake_e2e.py: park/resume WS integration test that also asserts the host effect — after a valid submit the session metadata carries the normalized user_name / contact_email / contact_phone, on both the rich and conversational-fallback paths; an invalid submit stamps nothing and stays parked.
  • Full python/server suite green (379 passed), ruff check + ruff format --check clean, compileall clean.
  • Changeset added (@smooai/smooth-operator-server, patch).

🤖 Generated with Claude Code

https://claude.ai/code/session_01YbN45JeWDbcjvFqGJvmVD3

Port the identity_intake Rich Interaction kind (structured name/email/phone lead
capture) to the Python server, mirroring the Rust reference, and add the
kind-routed host-effect seam the choices wave omitted.

Why: choices landed the Rich Interactions framework but had no side effect, so
the framework never grew the seam a stateful kind needs. identity_intake is that
kind — a valid submit must stamp the captured contacts onto the session so the
OTP contact seam (and pre-chat/create readers) pick them up. The seam is
kind-agnostic (InteractionKind.host_effect defaults to a no-op) and fires on both
submit paths — the dispatcher's submit_interaction action AND the conversational-
fallback submit_interaction tool — so choices is unaffected and future kinds get
the same hook.

- identity_intake.py: request_identity_intake { fields, reason } raise tool,
  validator (required present, email shape, phone -> E.164, per-field errors),
  conversational fallback directive, capability identity_form, and a host_effect
  that stamps user_name / contact_email / contact_phone.
- interaction.py: host_effect no-op on the base kind; identity_intake registered
  in the default catalog alongside choices.
- session_store: user_name + contact_phone on StoredSession (persisted pre-chat
  too), attach_session_identity seam (in-memory impl; durable is a host concern),
  OTP contact seam now offers the captured phone (SMS) as well as email.
- Tests: validator units + a park/resume WS integration test asserting the host
  effect on both the rich and conversational-fallback paths.

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 19, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 770a7a7

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 Patch

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 af092bf into main Aug 19, 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