autoplay_sdk.user_adoption_state to track a userโs longer-term product maturity, independent of the per-session SessionState FSM.
What it is
SessionState tracks one conversationโs moment-to-moment mode (thinking / proactive_assistance / reactive_assistance). user_adoption_state is a separate, longer-lived dimension keyed by user_id: where the user is in their lifecycle, how proficient they are, and how their onboarding is progressing.
The journey and mastery values are an agent (LLM) decision via a versioned judge prompt; the resolved state is fed back into chat and proactive prompts so the assistant can adapt โ more teaching during onboarding, lighter nudges for power users.
The SDK ships pure logic and prompt definitions: the state model, the explore-first gate, onboarding plans, the tour matcher, and versioned prompts. The
acall_llm runners that call these prompts remain integration-owned. For persistence, the SDK now also ships optional storage adapters (Redis / in-memory, plus write-only Amplitude / PostHog sinks) and session lifecycle helpers โ see State storage & session capture.The two axes
Adoption is modeled as two independent axes set by the same judge call. A user can beonboarded yet still intermediate, or onboarding yet a fast proficient.
| Axis | Enum | Values |
|---|---|---|
| Lifecycle stage (coarse) | JourneyState | onboarding, onboarded |
| Competence (fine) | MasteryLevel | novice, beginner, intermediate, proficient, power_user |
| Enum | Values |
|---|---|
WorkflowStatus | not_started, in_progress, completed |
State model
UserAdoptionState
| Field | Type | Purpose |
|---|---|---|
user_id | str | Mandatory scope key. Raises if empty. |
metadata | dict[str, Any] | Fully dynamic, schema-less identity bag. No fixed keys; values must be JSON-serializable. |
journey_state | JourneyState | Coarse lifecycle stage. Defaults to onboarding. |
mastery | Mastery | Rating (0-10) + level + reason. |
discovered_features | list[str] | Features the user has already discovered. |
onboarding | OnboardingState | Welcome lifecycle + per-flow progress. |
first_seen_at / last_updated_at / last_judged_at | float | Lifecycle timestamps. |
role is not special-cased in storage โ it is just one conventional metadata key. A thin state.role convenience property reads metadata.get("role").
Sub-objects
Masteryโrating: int(0-10, clamped),level: MasteryLevel,description: str.WorkflowProgressโ the agentโs evolving belief about one target flow:status,confidence(0-1),evidence, andfirst_detected_at/completed_at/last_evaluated_at. Also holds asteps: dict[str, StepProgress]map of per-step beliefs, withcompleted_step_idsand anupsert_step_progress(step_id, *, status, confidence, evidence="", now=None)mutation (write-oncecompleted_at, mirroringupsert_workflow_progress).StepProgressโ the agentโs belief about one step of a workflow:step_id,status(sameWorkflowStatusenum),confidence(0-1),evidence,completed_at,last_evaluated_at. A deliberate parallel toWorkflowProgress, one level down. Steps are signal for the LLM and for explainability โ they never gate workflow completion (see Onboarding plans).StepInAssessmentโ the latest โshould I step in?โ decision:should_step_in,timing_assessment,reason,suggested_workflow_ids. Persisted whether or not anything was sent, so timing is explainable.OnboardingStateโwelcomed,distinct_features_seen,explore_gate_passed, aworkflow_progress: dict[str, WorkflowProgress]map,last_step_in, cooldown/cadence timestamps, andproactive_support_count. Derived helpers:completed_workflow_ids,in_progress_workflow_ids,outstanding_workflow_ids(plan_ids). Mutations:upsert_workflow_progress(...),record_step_in(...).
Persistence and prompt injection
to_dict() / from_dict() round-trip the whole record (with _v snapshot versioning and defensive enum coercion, so an unknown enum value degrades to a safe default rather than raising). to_prompt_block() renders a compact [USER ADOPTION STATE] block (role + journey + mastery + a one-line guidance hint) to prepend to a chat prompt.
Explore-first gate
evaluate_hard_gate() is a pure, side-effect-free hard floor that guarantees a user is never interrupted too early. Intelligent timing on top of the floor is the LLMโs job.
(passed, reason) and blocks when the role is not allowlisted, the user has not explored enough yet, they are already onboarded, a cooldown is in effect, or the nudge cap is reached. When it returns True, control passes to the LLM to decide whether now is the right moment and what to say.
Onboarding plans
The host configures, per product (and optionally per role), the set of workflows a user should complete. The agent is handed this list and reasons about progress against it โ it is never hardcoded in the SDK. Each workflow can optionally declare an ordered list of steps โ the things a user typically does to achieve the workflow. Steps are context and signal for the agent (they describe what โdoneโ looks like so the LLM can recognise progress in the live activity stream); they are not a hard checklist. A user may finish a workflow via a variant path without hitting every step.workflow_id lines up with a tour id so a suggested workflow can be delivered as a โshow meโ tour offer.
Steps
AWorkflowStep has step_id (required), name, description, and required (default True). required is only a hint to the LLM about which steps are core vs. nice-to-have โ it does not gate completion. OnboardingWorkflow.required_step_ids lists the required step ids in declared order. A workflow with no declared steps behaves exactly as before (the LLM judges completion from activity alone), so steps are fully backward compatible. Steps are configured in the product config under integration_config.onboarding_workflows[].steps and round-trip through to_dict() / from_dict() (the steps key is omitted when empty).
Per-user, the agent records its belief about each step in WorkflowProgress.steps[step_id] (a StepProgress) via wp.upsert_step_progress(...).
Completion
split_workflows() is a deterministic prefilter/guardrail โ the authoritative completion call is the LLMโs, reflected in WorkflowProgress.status. Steps never gate this: a workflow counts as complete iff the LLM set its status to completed, regardless of per-step state.
is_plan_complete(plan, workflow_progress)โ a thin convenience wrapper oversplit_workflowsreturningTruewhen there are no outstanding workflows (an empty plan is never โcompleteโ).step_completion_ratio(workflow, wp)โ the fraction of a workflowโs declared steps the LLM currently believes arecompleted. For display/telemetry only (e.g. a progress bar); never used to decide completion. Returns0.0for a workflow with no declared steps or whenwpisNone.
Query to tour matching
A reusable primitive that generalizes role-filtered tour selection. The SDK builds the structured prompt input and parses/validates the LLM output; theacall_llm call itself is integration-owned.
parse_tour_match_output() validates returned ids against the role-filtered catalog and falls back to an empty result on malformed JSON, so a bad model response never raises into the caller.
Prompts
Three versioned prompts ship inautoplay_sdk.prompts (each a dict with name / description / version / content, all returning a single JSON object). Call them via your integrationโs LLM client with response_format={"type": "json_object"}.
| Constant | Name | Decides |
|---|---|---|
ADOPTION_STATE_JUDGE_PROMPT | adoption_state_judge (v0.1) | journey_state + mastery from aggregated activity. |
TOUR_MATCH_PROMPT | tour_match (v0.1) | The primary + related tours for a query. |
ONBOARDING_WELCOME_PROMPT | onboarding_welcome (v0.1) | Per-workflow completion, whether/when to send a welcome, and the next-flow โshow meโ offers. |
See also
- Agent session states โ the per-session FSM this model complements.
- Changelog โ added in
0.8.0.