By Sagar Shankaran, Founder of CallSphere
Build an AI onboarding agent that adapts to each user's role, experience level, and goals to guide them through your SaaS product with personalized walkthroughs and recommendations.
Key takeaways
Most SaaS products have a fixed onboarding flow: five steps, same for everyone. A CEO sees the same tutorial as an analyst. A power user who has used three competing products gets the same walkthrough as someone who has never seen software in this category. Static onboarding leads to two failure modes — experienced users skip everything and miss important differences, while new users feel overwhelmed by irrelevant features.
An AI-powered onboarding agent solves this by adapting the flow based on who the user is and what they need.
The onboarding agent starts by gathering context through a brief conversational intake. Instead of a static form, the AI asks follow-up questions based on previous answers.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent for IT support in your browser — 60 seconds, no signup.
flowchart LR
SIGNUP(["New signup"])
AGENT["AI onboarding agent"]
GOAL["Detect goal and<br/>persona"]
PATH{"Personalized path"}
ACT1["Activation step 1<br/>configure profile"]
ACT2["Activation step 2<br/>connect data"]
ACT3["Activation step 3<br/>first value moment"]
NUDGE["In-app and email<br/>nudges"]
CSM(["CSM handoff if<br/>account flagged"])
DONE(["Activated"])
SIGNUP --> AGENT --> GOAL --> PATH
PATH --> ACT1 --> ACT2 --> ACT3 --> DONE
AGENT --> NUDGE
AGENT --> CSM
style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
style DONE fill:#059669,stroke:#047857,color:#fff
style CSM fill:#f59e0b,stroke:#d97706,color:#1f2937
from pydantic import BaseModel
from enum import Enum
class ExperienceLevel(str, Enum):
BEGINNER = "beginner"
INTERMEDIATE = "intermediate"
EXPERT = "expert"
class UserProfile(BaseModel):
role: str
experience_level: ExperienceLevel
goals: list[str]
team_size: int | None = None
previous_tools: list[str] = []
industry: str | None = None
INTAKE_SYSTEM_PROMPT = """You are an onboarding assistant for a project management SaaS.
Your job is to learn about the new user in 3-5 questions so you can personalize their setup.
Ask about:
1. Their role (manager, individual contributor, executive)
2. Their experience with similar tools
3. Their primary goal for using this product
4. Their team size
Be conversational and concise. After gathering enough info, respond with a JSON
block containing the UserProfile fields.
Do NOT ask all questions at once. Ask one at a time and adapt based on answers."""
class OnboardingAgent:
def __init__(self, llm_client):
self.llm_client = llm_client
self.conversations: dict[str, list[dict]] = {}
async def process_message(self, user_id: str, message: str) -> dict:
if user_id not in self.conversations:
self.conversations[user_id] = []
self.conversations[user_id].append({"role": "user", "content": message})
response = await self.llm_client.chat(
system=INTAKE_SYSTEM_PROMPT,
messages=self.conversations[user_id],
)
reply = response.content
self.conversations[user_id].append({"role": "assistant", "content": reply})
# Check if the AI has gathered enough info
profile = self.try_extract_profile(reply)
if profile:
return {"type": "profile_complete", "profile": profile, "reply": reply}
return {"type": "question", "reply": reply}
def try_extract_profile(self, reply: str) -> UserProfile | None:
import json
import re
match = re.search(r'{[^}]+}', reply, re.DOTALL)
if match:
try:
data = json.loads(match.group())
return UserProfile(**data)
except (json.JSONDecodeError, ValueError):
return None
return None
Once the user profile is captured, the agent generates a custom sequence of feature walkthroughs.
from dataclasses import dataclass
@dataclass
class TourStep:
feature_key: str
title: str
description: str
target_selector: str # CSS selector for the UI element to highlight
action_url: str # Page to navigate to for this step
priority: int
FEATURE_CATALOG = [
{"key": "dashboard", "name": "Dashboard", "roles": ["all"],
"complexity": "beginner"},
{"key": "kanban", "name": "Kanban Board", "roles": ["ic", "manager"],
"complexity": "beginner"},
{"key": "gantt", "name": "Gantt Charts", "roles": ["manager", "executive"],
"complexity": "intermediate"},
{"key": "time_tracking", "name": "Time Tracking", "roles": ["ic"],
"complexity": "beginner"},
{"key": "reports", "name": "Reports & Analytics", "roles": ["manager", "executive"],
"complexity": "beginner"},
{"key": "automations", "name": "Workflow Automations", "roles": ["manager"],
"complexity": "expert"},
{"key": "api_access", "name": "API & Integrations", "roles": ["ic"],
"complexity": "expert"},
]
async def generate_tour(profile: UserProfile, llm_client) -> list[TourStep]:
# Filter features relevant to this user
role_map = {"manager": "manager", "individual contributor": "ic",
"executive": "executive"}
user_role = role_map.get(profile.role.lower(), "ic")
relevant_features = [
f for f in FEATURE_CATALOG
if "all" in f["roles"] or user_role in f["roles"]
]
# Further filter by experience level
complexity_order = {"beginner": 0, "intermediate": 1, "expert": 2}
user_level = complexity_order.get(profile.experience_level.value, 0)
filtered = [
f for f in relevant_features
if complexity_order.get(f["complexity"], 0) <= user_level + 1
]
prompt = f"""Generate an onboarding tour for a {profile.role} with
{profile.experience_level.value} experience. Their goals: {', '.join(profile.goals)}.
Previous tools: {', '.join(profile.previous_tools) or 'None'}.
Available features to highlight:
{[f['name'] for f in filtered]}
Return a JSON array of tour steps ordered by relevance to the user's goals.
Each step: {{"feature_key": "...", "title": "...", "description": "...",
"priority": 1-5}}. Limit to 5-7 steps."""
response = await llm_client.chat(
messages=[{"role": "user", "content": prompt}],
response_format={"type": "json_object"},
)
return parse_tour_steps(response.content, filtered)
During onboarding, users have questions that do not fit neatly into tour steps. The agent handles free-form questions using product documentation as context.
class OnboardingQAAgent:
def __init__(self, llm_client, doc_retriever):
self.llm_client = llm_client
self.doc_retriever = doc_retriever
async def answer_question(self, question: str,
user_profile: UserProfile,
current_page: str) -> str:
# Retrieve relevant documentation chunks
docs = await self.doc_retriever.search(
query=question, limit=5
)
doc_context = "\n\n".join([d.content for d in docs])
system = f"""You are an onboarding assistant. The user is a
{user_profile.experience_level.value}-level {user_profile.role}.
They are currently on the {current_page} page.
Answer their question using ONLY the documentation below.
If the answer is not in the documentation, say so and suggest
contacting support.
Documentation:
{doc_context}"""
response = await self.llm_client.chat(
system=system,
messages=[{"role": "user", "content": question}],
)
return response.content
As users complete onboarding steps, the agent suggests next features based on adoption patterns from similar users.
async def recommend_next_features(db, user_profile: UserProfile,
completed_features: list[str]) -> list[dict]:
# Find users with similar profiles who completed onboarding
similar_users = await db.fetch("""
SELECT u.id, array_agg(fa.feature_key ORDER BY fa.adopted_at) as adoption_order
FROM users u
JOIN feature_adoption fa ON fa.user_id = u.id
WHERE u.role = $1
AND u.experience_level = $2
AND fa.feature_key = ANY($3)
GROUP BY u.id
HAVING count(fa.feature_key) >= $4
LIMIT 100;
""", user_profile.role, user_profile.experience_level.value,
completed_features, len(completed_features))
# Count which features these similar users adopted next
from collections import Counter
next_features = Counter()
for user in similar_users:
order = user["adoption_order"]
for feature in order:
if feature not in completed_features:
next_features[feature] += 1
break # Only count the immediate next feature
return [
{"feature": feat, "adopted_by_similar_users": count}
for feat, count in next_features.most_common(3)
]
Provide a "Skip" button that sets sensible defaults (role: individual contributor, experience: intermediate, goals: general). Track which features they use in the first session and retroactively adjust recommendations. Offer to revisit personalization after their first week.
Still reading? Stop comparing — try CallSphere live.
See the IT support AI agent handle a real call — complete, industry-specific, and live in your browser. No signup.
During onboarding, the user typically has no data yet. The AI should have access to sample data and documentation only. If the user imported data before onboarding (e.g., via CSV), the agent can reference that to make the tour more concrete — "I see you imported 47 contacts. Let me show you how to organize them."
Compare three cohorts: users who completed AI onboarding, users who completed static onboarding, and users who skipped onboarding. Track activation rate (percentage reaching their first meaningful action), time-to-first-value, and 30-day retention. The AI cohort should outperform static by at least 15-20% on activation to justify the added complexity.
#AIOnboarding #SaaS #UserGuidance #FeatureRecommendation #Python #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Chengdu, Beijing, and Shenzhen SaaS teams lose trials to slow follow-up. CallSphere qualifies inbound leads 24/7 in Mandarin and English and books demos straight into the calendar.
Your Estonian SaaS scales globally, but your support line runs on one founder and a shared inbox. Here is how CallSphere AI voice and chat agents cover inbound demos and support 24/7 for Tallinn and Tartu tech teams.
The public MCP registry crossed 9,400 servers in April 2026. Here is a curated walkthrough of the SaaS MCP servers CallSphere mounts in production, with OAuth 2.1 PKCE patterns.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
OpenAI's Stargate with Oracle and SoftBank crossed a milestone in April 2026 with the first Texas site partially energized and three additional sites under construction.
Smolagents lets agents write Python instead of JSON. Why code-as-action reduces tool errors and where the security trade-offs are for production deployments.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco