Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions docs/memory/feature-flows/onboarding-wizard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# First-Run Onboarding Wizard (trinity-enterprise#52)

**Status:** Phase 1 shipped (guided first-agent deploy). Ambient layers tracked as follow-ups.

## Problem

A self-hoster who gets Trinity running and opens the web UI had **no guided path
to first value**. The only in-app onboarding artifact (`OnboardingChecklist.vue`
+ `useOnboarding.js`) was orphaned dead code modeled on the **removed** Process
Engine — mounted nowhere, linking to routes (`/processes/wizard`) that no longer
exist.

The user tension to design around: people who "just want to get things done"
bounce off instructions, but they still need to grasp basic concepts (what an
agent is, chat) to get value. So: **teach by doing, not by reading.**

## Design principles

- **Not a click-through tour.** No mandatory tooltip walkthrough — that's the
exact thing the "don't make me read" user rage-quits.
- **One question → a running agent.** "What do you want to do?" maps an intent
to a starter template and deploys a *tailored* first agent, not a blind sample.
- **Soft-guide the one hard gate.** Trinity agents can't think without Claude
auth, but front-loading every credential recreates the "wall of setup."
Surface the Claude-auth gate as a non-blocking hint; defer everything else
(e.g. the GitHub PAT) to just-in-time.
- **Never nags.** Auto-opens once for a fresh, empty install; dismissal is
remembered.

## Flow

The wizard **drives the real UI** — it does not reimplement agent creation. It
guides the user into the actual create form, then to the one credential the new
agent still needs.

```
Fresh install → admin account (existing SetupPassword.vue) → login
→ Dashboard, zero agents
→ OnboardingWizard auto-opens (once)
1. intro — one question, "what do you want it to do?"
(a purpose card → prefills a starter template)
2. create — opens the REAL CreateAgentModal, template
preselected; user creates the agent (the right
buttons, not a clone)
3. credential — "connect a Claude subscription so it can think"
→ Settings → Integrations → Claude Subscriptions
(or "Open chat with <agent>")
```

Dismiss at any step → remembered in `localStorage['trinity_onboarding_dismissed_v1']`.
Re-openable any time from the Dashboard empty state ("Get started") or via
`/?onboarding=1` (re-run / QA preview, ignores the dismissed flag and fleet size).

## Purpose → template mapping

The intent cards map to the real local starter templates shipped in
`config/agent-templates/`. Each card's template existence is verified against
`GET /api/templates` on mount; a missing template falls back to a blank Claude
Code agent so deploy never 404s.

| Purpose card | Template id | Default name |
|-------------------------------|---------------|--------------|
| Research a market or topic | `local:scout` | `scout` |
| Advise on strategy | `local:sage` | `sage` |
| Write content & reports | `local:scribe`| `scribe` |
| Start from scratch | `''` (blank) | `assistant` |

## Implementation

**Frontend**
- `components/OnboardingWizard.vue` — the guidance overlay. Step `create`
delegates to the real `CreateAgentModal` (`:initial-template` prefilled,
`@created`); step `credential` routes to Settings → Integrations. It never
calls `createAgent` itself.
- `views/Dashboard.vue` — mounts the wizard; auto-opens via
`maybeAutoOpenOnboarding()` (gated on `!isFleetLoading && agents.length === 0 &&
!dismissed`, or forced by `?onboarding=1`); empty-state "Get started" re-opens it.
- `stores/sessions.js` — caches `claudeAuthConfigured` from feature-flags
(decides whether the credential step nudges to connect, or confirms + offers chat).

**Backend**
- `routers/settings.py` `GET /api/settings/feature-flags` gains
`claude_auth_configured` — `bool(anthropic_api_key)` (DB or env) **or** any
registered subscription. A boolean only; never the key itself.

**Removed**
- Dead `components/OnboardingChecklist.vue` + `composables/useOnboarding.js`
(Process-Engine-based, orphaned).

## Follow-ups (separate build issues, from trinity-enterprise#52)

- Phase 1 first-run setup: add a **mandatory** Claude-auth step to the setup
wizard (currently the wizard only *hints*; nothing blocks).
- Ambient auto-completing checklist on real concepts (created → chatted →
scheduled → shared), non-blocking, dismissible.
- Contextual empty-state guidance on other key views.
- Just-in-time GitHub PAT prompt (only when a GitHub-template agent needs it).
28 changes: 28 additions & 0 deletions docs/user-docs/getting-started/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,34 @@ Create and interact with a Trinity agent using the Web UI, API, or MCP tools.

> 📺 **Watch:** [Build an AI Recruiter Agent — zero to deployed](https://youtu.be/K7hFWyFIf-Y) *(Jun 2026)* · [From Zero to Deployed AI Agent](https://youtu.be/-TSZyekDS6o) *(Apr 2026)* · [all videos](../videos.md)

## Guided Onboarding (First Run)

The fastest path: let Trinity guide you. On a **fresh install with no agents yet**,
a short onboarding wizard opens automatically the first time you reach the Dashboard
after logging in. It asks one question — *what do you want your first agent to do?* —
then:

1. Opens the **Create Agent** form with a matching starter template pre-selected.
2. After the agent is created, walks you to the **Claude subscription** step so your
agent can actually think (Settings → Integrations → Claude Subscriptions), or
straight to chat if Claude auth is already configured.

You can dismiss it at any time ("Skip for now") — it won't nag you again.

**Relaunch the wizard any time** (e.g. to spin up another agent, or if you skipped it):
open the Dashboard with the `?onboarding=1` query parameter:

```
http://localhost/?onboarding=1
```

This works regardless of how many agents you already have. **Log in first**, then
open the link (opening it while signed out sends you through the login page, which
drops the `?onboarding=1` parameter). On a fresh, empty install the Dashboard also
shows a **Get started** button in the empty state that opens the same wizard.

> Prefer to do it manually? Skip the wizard and follow **How It Works** below.

## How It Works

1. Open http://localhost and log in as admin.
Expand Down
3 changes: 3 additions & 0 deletions docs/user-docs/getting-started/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,8 @@ The following endpoints do not require authentication:

## See Also

- [Quick Start](quick-start.md) -- After setup, a **guided onboarding wizard** opens on
your first Dashboard visit to launch your first agent (relaunch any time at
`http://localhost/?onboarding=1`).
- [Overview](../overview.md) -- Platform overview and core concepts.
- [Creating Agents](../agents/creating-agents.md) -- Deploy your first agent.
3 changes: 3 additions & 0 deletions src/backend/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -1587,6 +1587,9 @@ def get_subscription_token(self, subscription_id: str):
def list_subscriptions(self, owner_id: int = None):
return self._subscription_ops.list_subscriptions(owner_id)

def has_any_subscription(self):
return self._subscription_ops.has_any_subscription()

def list_subscriptions_with_agents(self, owner_id: int = None):
return self._subscription_ops.list_subscriptions_with_agents(owner_id)

Expand Down
11 changes: 11 additions & 0 deletions src/backend/db/subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,17 @@ def list_subscriptions(self, owner_id: Optional[int] = None) -> List[Subscriptio

return [self._row_to_subscription(row) for row in rows]

def has_any_subscription(self) -> bool:
"""Cheap existence check — does ANY subscription exist?

Used by the hot ``/api/settings/feature-flags`` path (onboarding's
``claude_auth_configured``) to avoid materializing + decrypting every
subscription row just to test presence.
"""
stmt = select(func.count()).select_from(subscription_credentials)
with get_engine().connect() as conn:
return (conn.execute(stmt).scalar() or 0) > 0

def list_subscriptions_with_agents(self, owner_id: Optional[int] = None) -> List[SubscriptionWithAgents]:
"""
List subscriptions with their assigned agents.
Expand Down
7 changes: 7 additions & 0 deletions src/backend/routers/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ async def get_public_feature_flags(
# failure controls are armed during a soak. NOT a UI surface.
"redelivery_governor_enabled": REDELIVERY_GOVERNOR_ENABLED,
"platform_default_model": settings_service.get_platform_default_model(),
# Onboarding (trinity-enterprise#52) — is Claude auth configured at all?
# Trinity agents can't think without it, so the first-run wizard uses
# this to surface the one hard setup gate. True if a platform-wide
# Anthropic key exists (DB or env) OR any Claude subscription is
# registered. Non-sensitive: a boolean, never the key itself.
"claude_auth_configured": bool(settings_service.get_anthropic_api_key())
or db.has_any_subscription(),
# #847 Phase 0 — enterprise entitlements. Empty list means OSS
# build (or TRINITY_OSS_ONLY=1). UI uses this to hide
# enterprise-only tabs cleanly without server-side conditional
Expand Down
11 changes: 11 additions & 0 deletions src/frontend/src/components/CreateAgentModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,17 @@ const fetchTemplates = async () => {
headers: authStore.authHeader
})
templates.value = response.data
// If a caller injected an initialTemplate that doesn't exist in this
// deploy (e.g. the onboarding wizard prefilling `local:scout` on an
// install without that template), fall back to the blank agent so the
// form never points at a missing template.
if (
form.template &&
form.template !== 'github-custom' &&
!templates.value.some(t => t.id === form.template)
) {
form.template = ''
}
} catch (err) {
console.error('Failed to fetch templates:', err)
templatesError.value = 'Failed to load templates'
Expand Down
Loading
Loading