Shipping an AI Onboarding Flow in a Weekend
How I replaced a static, five-step wizard with a model-driven onboarding that adapts to each user — and what it took to make it feel instant.

Our activation numbers were flat. New users landed in a generic five-step wizard that asked the same questions regardless of who they were or why they signed up. It worked, but it never felt like the product understood them.
The Problem
The wizard collected data the product barely used. Worse, it front-loaded friction: people had to answer questions before they saw a single moment of value. Drop-off between step one and step three was 34%.
Investigation
I pulled a week of session recordings and traced where attention dropped. The pattern was clear — users abandoned when a step felt irrelevant to them. A designer signing up for a portfolio tool does not care about team seats on step two.
- Most fields were optional but presented as required.
- The order was fixed, so relevance decayed fast.
- We already had signup context we were throwing away.
The Solution
Instead of a fixed sequence, I let a model plan the onboarding. Given the signup context and a small set of tools, it decides which questions matter and in what order, then hands control back to deterministic UI for anything sensitive.
import { streamText, tool } from 'ai'
import { z } from 'zod'
const result = streamText({
model: 'openai/gpt-4o',
system: ONBOARDING_PLANNER,
messages,
tools: {
askQuestion: tool({
description: 'Ask the user one focused onboarding question',
inputSchema: z.object({
prompt: z.string(),
options: z.array(z.string()).optional(),
}),
}),
completeStep: tool({
description: 'Persist an answer and advance the flow',
inputSchema: z.object({ key: z.string(), value: z.string() }),
}),
},
})
The key detail: the model never renders UI directly. It emits intents, and the client owns rendering. That keeps the experience fast, accessible, and fully under design control.
The Result
Median time-to-first-value dropped from 4m10s to 1m50s. Completion climbed 22 points. Because the model adapts, we removed three entire screens without losing any data we actually use.
Lessons Learned
- Stream everything. Perceived speed is the feature.
- Constrain the model with tools; never let it touch the DOM.
- Keep a deterministic fallback for every model decision.
- Measure time-to-value, not steps completed.

