# Onboarding

Guided first-run setup for new Maxy installations. Runs automatically at every session start when `onboarding-get` returns `currentStep` less than 8. Resume from the first incomplete step — skip any step whose number is at or below `currentStep`.

After completing each step, persist progress immediately by calling `onboarding-complete-step` with the step number just completed. This ensures that if the session ends, the next session resumes from the right place.

Every user selection and every document presentation in onboarding renders through `render-component` — never as markdown text, bullet lists, or inline chat content. Selections use `single-select` or `multi-select`; file content (SOUL, KNOWLEDGE, config) uses `document-editor`. Describing options in prose or pasting file content into a chat message bypasses the UI and the user's ability to review and edit before saving. Never use directional words ("above", "below") when referring to a rendered component — just name it or refer to "the browser", "the editor", etc. The `document-editor` component renders one file at a time — never call `render-component` with `document-editor` twice in the same turn. Present one file, wait for the user's approval or edit, then present the next.

## Step 1 — Plugin selection

*(skip if `currentStep` >= 1)*

Call `render-component` to surface a checkbox UI — do not list plugins as markdown. Pass `name: "multi-select"` and data as an object with `selectAll: true`, `submitMessage: "Install the following plugins: {{values}}"`, `skipLabel: "Skip"`, `skipMessage: "Skip plugin installation"`, and `options` as a single array containing every item below (core, then optional Maxy, then Claude Official — all in one array):

`{ value: "admin", label: "admin", description: "Platform management — system status, account settings, logs, session control.", badge: "Core", group: "Core — always active", locked: true, defaultSelected: true }`,
`{ value: "memory", label: "memory", description: "Graph memory — search, write, reindex, and ingest knowledge.", badge: "Core", group: "Core — always active", locked: true, defaultSelected: true }`,
`{ value: "maxy-guide", label: "maxy-guide", description: "User guide and platform documentation — loaded on demand.", badge: "Core", group: "Core — always active", locked: true, defaultSelected: true }`,
`{ value: "cloudflare", label: "cloudflare", description: "Cloudflare Tunnel — expose your assistant publicly via a custom domain.", badge: "Core", group: "Core — always active", locked: true, defaultSelected: true }`,
`{ value: "anthropic", label: "anthropic", description: "Claude connection — API key acquisition and configuration.", badge: "Core", group: "Core — always active", locked: true, defaultSelected: true }`,
`{ value: "deep-research", label: "deep-research", description: "Structured multi-source research — query decomposition, source evaluation, citation formatting.", badge: "Core", group: "Core — always active", locked: true, defaultSelected: true }`,
`{ value: "business-assistant", label: "business-assistant", description: "Customer enquiries, scheduling, quoting, invoicing, daily briefings.", badge: "Recommended", group: "Maxy", defaultSelected: true }`,
`{ value: "sales", label: "sales", description: "Buying signal detection, closing techniques, objection handling.", badge: "Recommended", group: "Maxy", defaultSelected: true }`,
`{ value: "scheduling", label: "scheduling", description: "Calendar and scheduling — events, appointments, recurring triggers.", badge: "Recommended", group: "Maxy", defaultSelected: true }`,
`{ value: "tasks", label: "tasks", description: "Graph-backed task lifecycle — create, update, list, relate, complete.", badge: "Recommended", group: "Maxy", defaultSelected: true }`,
`{ value: "email", label: "email", description: "Agent email account — setup, read, send, search.", badge: "Recommended", group: "Maxy", defaultSelected: true }`,
`{ value: "contacts", label: "contacts", description: "CRM contact management — create, lookup, update, list.", badge: "Recommended", group: "Maxy", defaultSelected: true }`,
`{ value: "workflows", label: "workflows", description: "Persistent named workflows — reusable instruction sets.", badge: "Recommended", group: "Maxy", defaultSelected: true }`,
`{ value: "documents", label: "documents", description: "Document management and PDF generation.", badge: "Recommended", group: "Maxy", defaultSelected: true }`,
`{ value: "telegram", label: "telegram", description: "Telegram bot setup — BotFather, admin and public channels.", group: "Maxy" }`,
`{ value: "waitlist", label: "waitlist", description: "Waitlist lifecycle — extract sign-ups, review, schedule automation.", group: "Maxy" }`,
`{ value: "stripe", label: "stripe", description: "Live access to payment and business data.", group: "Claude Official" }`,
`{ value: "zapier", label: "zapier", description: "Cross-app automation — connect thousands of apps.", group: "Claude Official" }`

Wait for the user's component submission before continuing. Their response will either list the plugins they selected or indicate they skipped. The submission contains only non-locked (optional) plugin names — core plugins are always active and not included in the submission.

- For each selected **Claude Official plugin**, run: `claude plugin install <name>@claude-plugins-official`
- For each selected **Maxy plugin**, read the current `account.json` via `account-manage`, add the plugin name to the `enabledPlugins` array (create the array if absent), and write back via `Edit`. Use the `value` field (not `label`) as the plugin name — e.g., `docs` not `maxy-guide`. This activates the plugin's behaviour and MCP server from the next session.

After installing/enabling (or skipping), call `onboarding-complete-step` with step 1.

If any Maxy plugins were enabled in this step, call `session-reset` immediately after completing step 1. Maxy plugin MCP servers only load when a new subprocess spawns — the current session cannot access them. After the reset, the new session calls `onboarding-get` (returning `currentStep = 2`) and resumes from Step 2 with all newly enabled plugins available. If no Maxy plugins were enabled (user skipped or selected only Claude Official plugins), proceed directly to Step 2 — no reset needed.

## Step 2 — Anthropic API key

*(skip if `currentStep` >= 2)*

The public-facing agent uses the Anthropic API directly. Without a key, visitors cannot chat with the business. Check if the key exists by calling the `api-key-verify` tool — it returns `missing` if no key file is found.

If missing, use `plugin-read` to load `anthropic/skills/get-api-key.md` and follow its instructions. The skill delegates browser automation to `specialists:browser-specialist` — all Playwright interaction runs in the specialist's isolated context, keeping the main session lightweight. The user can see the browser via the VNC viewer rendered in the chat.

If the key already exists, skip this step.

After the key is stored (or was already present), call `onboarding-complete-step` with step 2.

## Step 3 — Cloudflare Tunnel

*(skip if `currentStep` >= 3)*

Remote access via a custom domain. Without this, the platform is only reachable on the local network. Check `tunnel-status` — if the tunnel is already running, skip this step.

If the tunnel is not configured or `tunnel-status` shows a CRITICAL nameserver warning, ask the user: "Would you like to set up remote access now? This connects your platform to a custom domain so you and your customers can reach it from anywhere. You will need a Cloudflare account and a domain. If you do not have these yet, we will set them up together now."

If the user wants to proceed: call `render-component` with `name: "browser-viewer"` and `data: { title: "Cloudflare" }` FIRST, then use `plugin-read` to load `cloudflare/skills/setup-tunnel.md` and follow its instructions exactly. The entire Cloudflare setup — sign-in, domain addition, nameserver changes at the registrar — happens in the VNC browser via `specialists:browser-specialist`. The user never leaves the chat. Do not improvise an alternative flow. Do not tell the user to go to a website themselves.

If the user skips, let them know they can set up Cloudflare at any time by asking.

After the tunnel is set up (or the user skips), call `onboarding-complete-step` with step 3.

## Step 4 — Specialist verification

*(skip if `currentStep` >= 4)*

Specialists are pre-installed by the platform. Read `agents/admin/AGENTS.md` to get the list of installed specialists. For each specialist, also read `specialists/agents/{name}.md` and extract the `summary` field from its YAML frontmatter — this is the user-facing description. Present each specialist to the user using its name and `summary`. Do not use the `description` field for user display — it contains delegation routing information not intended for users. If AGENTS.md is empty, fall back to scanning: use `Bash` to list `specialists/agents/*.md`, read each file's frontmatter to extract name, summary, and description, and append one line per specialist to `agents/admin/AGENTS.md`: `- **specialists:{name}**: {one-line delegation rule — when to dispatch}`.

Use `Bash` to list `$PLATFORM_ROOT/templates/specialists/agents/` for any `.md` files whose description contains "Phase 1" or "not yet available". Mention these as coming soon.

After verifying, call `onboarding-complete-step` with step 4.

## Step 5 — Output style

*(skip if `currentStep` >= 5)*

Call `render-component` to surface a radio selector UI. Pass `name: "single-select"` and data with `submitMessage: "Set output style to {{value}}"` and `options` as an array:

- `{ value: "default", label: "Default", description: "Efficient and task-oriented — concise responses without extra explanation." }`
- `{ value: "explanatory", label: "Explanatory", description: "Adds educational insights between steps, great for learning." }`

Wait for the user's component submission before continuing. Their response will specify the chosen style. Call `account-update` with field `outputStyle` and the chosen value. Call `onboarding-complete-step` with step 5.

## Step 6 — Thinking view

*(skip if `currentStep` >= 6)*

Call `render-component` to surface a radio selector UI. Pass `name: "single-select"` and data with `submitMessage: "Set thinking view to {{value}}"` and `options` as an array:

- `{ value: "default", label: "Thinking visible", description: "Shows reasoning expanded, collapses tool details." }`
- `{ value: "expanded", label: "Everything visible", description: "Expands all reasoning and tool details." }`
- `{ value: "collapsed", label: "Responses only", description: "Collapses all reasoning and tool details." }`

Wait for the user's component submission before continuing. Their response will specify the chosen view. Call `account-update` with field `thinkingView` and the chosen value. Call `onboarding-complete-step` with step 6.

After step 6, let the user know that everything configured during onboarding — plugins, the API key, Cloudflare, specialists, output style, and thinking view — can be changed at any time through conversation.

## Step 7 — Timezone

*(skip if `currentStep` >= 7)*

The platform auto-detected the server's timezone and stored it on the user's profile at first login. Confirm it with the user so scheduling, cron events, and time formatting use the correct locale.

Call `profile-read` to retrieve the current `UserProfile`. Extract the `timezone` field. If it is already set, present it for confirmation: "Your timezone is set to **{timezone}**. Is this correct?"

Call `render-component` with `name: "single-select"` and data with `submitMessage: "Set timezone to {{value}}"` and `options` as an array. Include the auto-detected timezone as the first (default) option, plus common alternatives:

- `{ value: "{current_timezone}", label: "{current_timezone}", description: "Auto-detected from your device." }`
- `{ value: "Europe/London", label: "Europe/London", description: "GMT / BST" }`
- `{ value: "America/New_York", label: "America/New_York", description: "Eastern Time" }`
- `{ value: "America/Los_Angeles", label: "America/Los_Angeles", description: "Pacific Time" }`

Omit any option that duplicates the auto-detected timezone. If the user selects "Other", ask them to type their IANA timezone identifier (e.g. `Asia/Tokyo`, `Australia/Sydney`).

After the user confirms or selects a timezone, call `profile-update` with `profileFields: { timezone: "<selected_value>" }`. Call `onboarding-complete-step` with step 7.

## Step 8 — Admin personality

*(skip if `currentStep` >= 8)*

Write the admin agent personality. All paths are relative to the account directory (the working directory).

First, check whether personalisation is already complete: read `agents/admin/SOUL.md`. If it contains actual business content (not just the `# Soul` header), call `onboarding-complete-step` with step 8 and skip to business onboarding — the personalisation is already in place.

If the admin SOUL file (`agents/admin/SOUL.md`) is empty or missing business content, the user must provide personalisation input through conversation. Product knowledge in the graph is background context — not a substitute for the user describing their business identity and preferences. Ask the user about:

- Their business: who the customers are, current stage, and any relevant context
- Tone and personality: how the agent should sound (formal, casual, warm, blunt, etc.), language preferences

Use `memory-search` to check for any business context already in the graph — reference it to avoid re-asking what's already known, but still confirm with the user. The conversation shapes the personality; the graph fills in factual detail.

SOUL is personality — not a product brochure. Factual business data belongs in the graph. SOUL contains only what the agent needs to *be itself*: the business name, your role within it, tone, language preferences, and working style.

Present the admin SOUL via `render-component` with `name: "document-editor"` and `data: { title: "Admin agent personality", content: <the markdown>, filePath: "agents/admin/SOUL.md" }`. The user must approve (or edit and approve) the content before any Write call. When the user approves or edits, you receive `{ action: "approve", content: "<markdown>", filePath: "<path>" }` or `{ action: "save", content: "<markdown>", filePath: "<path>" }`. Write the `content` to the specified `filePath` using the Write tool.

After the admin SOUL is written and approved, call `onboarding-complete-step` with step 8.

**Document ingestion.** If the user uploaded any documents during Step 8 (or earlier in the session), dispatch the `document-specialist` subagent to ingest them AFTER calling `onboarding-complete-step` — not before. Use the Agent tool with `run_in_background: true`. The critical path (SOUL file, step completion) must not depend on document ingestion succeeding. If no documents were uploaded, skip this step.

**Next steps.** After completing onboarding, mention that the user can create a public-facing agent whenever they are ready — just by asking. A public agent lets visitors chat with the business on a custom domain. This is a suggestion, not a requirement — the platform works fully without one.
