Skip to content

feat(onboarding): agent-driven first win in one paste - #1371

Merged
kentcdodds merged 4 commits into
mainfrom
cursor/onboarding-agent-driven-first-win-e178
Aug 10, 2026
Merged

kentcdodds merged 4 commits into
mainfrom
cursor/onboarding-agent-driven-first-win-e178

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Aug 10, 2026 •

Copy link
Copy Markdown
Owner

Intent

Onboarding/first-use feedback (Cameron Pak) said the first-run loop was
roundabout: people bounced between the Kody web page and their agent to send a
welcome email, reply, and then save memories, with a separate paste for each
leg. This makes the agent own the whole loop and keeps new users in the chat
they already have open.

Summary

A first-win guide the agent follows. docs/guides/first-win.md
(id first_win) is an agent playbook for the whole loop, modeled on
what-is-kody.md with the steering embedded as HTML comments: send the welcome
email with a findable subject (Welcome to Kody — reply to introduce yourself),
send the person to their own inbox to reply, explicitly do not poll or
busy-wait
for the reply, look it up when they come back and say "replied",
save memories and confirm what was saved, then offer a one-click
/connect/oauth?provider=…. Every Kody path in the guide is relative to the
origin the guide was fetched from, so preview and self-hosted deployments never
route someone to a different Kody. Troubleshooting covers mail that never
arrived and Claude Desktop needing a new chat before MCP tools bind. Registering
it in packages/worker/src/guides/catalog.ts serves it from coding_guide_get,
/guides/first-win, and /guides/first-win.md with no MCP changes.

Step 2 collapses to one paste. buildFirstWinPrompt replaces
buildIntroEmailPrompt / buildIntroEmailLookupPrompt, points the agent at the
guide URL on the requesting origin, and keeps the proven
"Ask the connected Kody server" phrasing (see #1319 — some hosts read
"Hey Kody" as impersonation and skip tools). The Send/Reply/Remember pills stay
as passive signal-derived progress; server derivation in
packages/worker/src/mcp/onboarding-checklist.ts is untouched. Copy shifts to
"your agent will take it from here", and /account/email stays as the
stored-copy fallback.

The Reply state names the real email. The onboarding payload carries
welcomeEmail — the subject and sender of the stored welcome message, matched
against outbound mail by the subject the guide prescribes (falling back to the
newest outbound message for agents that write their own subject, and to null
on any Mailbox failure) — so someone who lost the message knows exactly what to
search for instead of guessing at the subject the prompt suggested. The progress
poll compares those fields too, since the send meter can mark Send done before
the mailbox mirror stores the message.

New social accounts land on onboarding. issueLogin takes a default
redirect; a freshly created social-OAuth account gets
defaultPostVerificationRedirect (/onboarding) instead of /account, which
is where password signups already land after verification. An explicit
redirectTo still wins.

packages/worker/src/mcp/tools/search-onboarding-notice.ts is deliberately
untouched — the in-search nudge stays lightweight.

Testing

npm run validate green on ea523451 (format, lint, typecheck, 2047 unit
tests, MCP suite, 8 Playwright tests, primitives, migrations, deploy
guardrails, docs). CI green on the same commit with Bugbot passing.

Step 2 — one paste, pills as progress, guide link:

Onboarding step 2 with a single prompt to paste, Send/Reply/Remember pills, and a link to the first-win guide

Reply state showing the real stored subject and sender (payload stubbed in the
capture script to render the post-send state; the mailbox DO has no shell
seeding path):

Reply sub-step showing the real welcome email subject and from address in a details well

The guide serving on the web:

The first-win guide page rendered at /guides/first-win

Conductor report

  • Status: CI green on ea523451 with Bugbot passing; all AI-reviewer
    feedback triaged; squash-merging per the granted merge authority.
  • Scope 1 (guide): done — docs/guides/first-win.md + catalog + README
    index entry. Verified serving via /guides/first-win and
    /guides/first-win.md in e2e.
  • Scope 2 (single paste): done — buildFirstWinPrompt, single-paste Send
    state, passive pills, real subject/sender in Reply, /account/email
    fallback kept.
  • Scope 3 (social redirect): done — new social accounts → /onboarding,
    explicit redirectTo preserved, covered by unit + e2e.
  • Reviewer feedback: Bugbot "wrong outbound shown as welcome" — valid, fixed
    by matching the guide's subject before falling back to the newest outbound.
    CodeRabbit: hard-coded production origin (fixed — paths are now
    deployment-relative), poll skipping welcome-email metadata (fixed), MD040
    fence language (fixed), exact-full-subject match (declined: agents paraphrase
    the subject, so an exact match would miss most real sends and fall through to
    the looser fallback anyway).
  • Out of scope, untouched: packages/worker/src/mcp/** (including
    search-onboarding-notice.ts), docs/guides/providers/*,
    byok-explainer.tsx, onboarding-starter-card.tsx,
    community-detail.tsx.

System changes

System recap — extends existing primitives (medium risk)

Mode: recap · Base: main @ 89cf0b4b · Head: ea523451

Classification: extends — no new primitives. The onboarding loader payload
changes shape (two prompt fields replaced by one, plus welcomeEmail), the
social-login default redirect changes behavior, and the bundled guide catalog
gains an entry. primitives.yaml is unchanged: guides are content served by
existing surfaces.

Primitives touched

Primitive Group Impact
app-ui surfaces extends — step 2 is one paste; Reply names the stored subject/sender
app-sessions auth extends — new social accounts default to /onboarding instead of /account
mcp-server surfaces composes — coding_guide_get picks up first_win from the catalog with no capability edit
mailbox storage composes — subject-scoped outbound search plus a newest-outbound fallback, failing open
memories assistant composes — the guide steers verify-first memory writes; no memory code changed

System map

The onboarding page hands the whole first win to the agent: one paste points at
the bundled guide (served by both /guides and coding_guide_get), and the
Reply state reads the stored welcome email out of the Mailbox DO.

Legend: green = composes (wiring only) · amber = extended by this PR · red =
new primitive · gray = context (unchanged, included only when an edge crosses
it).

flowchart LR
	appUi["app-ui<br/>Browser app (Remix 3)"]:::extended
	appSessions["app-sessions<br/>Browser sessions"]:::extended
	mcpServer["mcp-server<br/>MCP endpoint (/mcp)"]:::touched
	mailbox["mailbox<br/>Mailbox"]:::touched
	memories["memories<br/>Memories"]:::untouched
	appUi -->|"/onboarding.json adds welcomeEmail via subject-scoped outbound search"| mailbox
	appUi -->|"single paste points at /guides/first-win"| mcpServer
	appSessions -->|"new social account redirects to /onboarding"| appUi
	mcpServer -->|"coding_guide_get({ guide: 'first_win' }) from the bundled catalog"| appUi
	mcpServer -->|"guide steers meta_memory_verify then upsert"| memories
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Before / after

Onboarding payload (OnboardingLoaderData / OnboardingPayload):

- introEmailPrompt: string
- introEmailLookupPrompt: string
+ firstWinPrompt: string
+ welcomeEmail: { subject: string; fromAddress: string | null } | null

Social-OAuth callback destination:

Case Before After
New account, no redirectTo /account /onboarding
New account with redirectTo target target
Returning account /account /account

Invariants

Per-user isolation is preserved: the welcome-email reads go through
searchOwnerEmailMessages / listOwnerEmailMessages scoped to the
authenticated user's own Mailbox DO, and they fail open to null rather than
surfacing partial state.

Plan vs actual

Shipped as scoped. Two drifts, both from review: the welcome-email lookup
started as "newest outbound message" and became "match the guide's subject,
then fall back to newest outbound", and the guide's Kody URLs moved from
hard-coded heykody.app to deployment-relative paths.

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Added a guided “first win” onboarding flow for sending a welcome email, replying from a personal inbox, and saving key facts.
    • Added welcome email subject and sender details to onboarding when available.
    • Added the new first-win guide to the Platform guides catalog and documentation.
  • Improvements

    • New social-login accounts now continue to onboarding before accessing the account area.
    • Simplified onboarding by replacing separate email prompts with one guided prompt.

Add a first-win guide (docs/guides/first-win.md, id first_win) that is the
agent's playbook for the whole loop: send the welcome email with a findable
subject, hand the person off to their own inbox, explicitly do not poll for
the reply, look it up when they say they replied, save memories, then offer a
one-click integration. Register it in the guides catalog so it serves via
coding_guide_get, /guides, and raw markdown.

Collapse onboarding step 2 to a single paste that routes the agent through
that guide, keeping the proven "Ask the connected Kody server" phrasing.
The Send/Reply/Remember pills stay as passive signal-derived progress, and
the Reply state now names the real subject and sender of the stored welcome
email so a lost message is still findable.

Send brand-new social-OAuth accounts to /onboarding instead of /account,
matching where password signups land after verification; an explicit
redirectTo still wins.
…nding

Seed a dedicated verified account instead of reusing the reserved 'kody'
fixture username, and fold the guide check into the same journey.
@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cursor[bot], you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5719786d-99f4-4edd-8cb3-48adf0c75280

📥 Commits

Reviewing files that changed from the base of the PR and between b78c256 and ea52345.

📒 Files selected for processing (2)
  • docs/guides/first-win.md
  • packages/worker/client/routes/onboarding.tsx
📝 Walkthrough

Walkthrough

The PR replaces introductory email prompts with a first-win onboarding guide. It adds welcome-email lookup and metadata, updates the onboarding interface and tests, registers the guide, and redirects new OAuth accounts to onboarding.

Changes

First-win onboarding

Layer / File(s) Summary
Onboarding contracts and first-win prompt
packages/worker/universal/loader-data.ts, packages/worker/client/routes/onboarding-payload.ts, packages/worker/src/app/onboarding-data.ts, packages/worker/src/app/*test.ts
The onboarding payload now exposes firstWinPrompt and nullable welcomeEmail data. Verified users receive the unified prompt.
Welcome-email loading
packages/worker/src/app/handlers/onboarding.ts, packages/worker/src/app/handlers/onboarding.node.test.ts
The handlers search outbound owner messages for a matching welcome subject, fall back to the newest message, and return null when mailbox access fails or no message exists.
First-win guide and onboarding experience
docs/guides/first-win.md, docs/guides/README.md, packages/worker/src/guides/catalog.ts, packages/worker/client/routes/onboarding.tsx, e2e/onboarding.spec.ts
The guide and onboarding page implement the email, reply, and memory workflow. Tests cover the prompt, sub-steps, guide routes, and removed second-prompt controls.
OAuth onboarding redirect
packages/worker/src/app/handlers/auth-provider.ts, packages/worker/src/app/handlers/auth-provider.node.test.ts, e2e/social-login.spec.ts
New OAuth accounts default to /onboarding. Explicit redirects remain higher priority, and returning accounts continue to use /account.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Onboarding
  participant Kody
  participant Mailbox
  participant Guide
  User->>Onboarding: Open first-win step
  Onboarding->>Kody: Send firstWinPrompt
  Kody->>Guide: Read first-win instructions
  Kody->>Mailbox: Send welcome email
  User->>Mailbox: Reply from personal inbox
  User->>Kody: Notify Kody after replying
  Kody->>Mailbox: Look up inbound reply
  Kody->>Onboarding: Provide welcomeEmail metadata
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description covers intent, implementation summary, testing, system impact, scope, and remaining work.
Title check ✅ Passed The title clearly summarizes the main change: an agent-driven onboarding first win using one paste.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/onboarding-agent-driven-first-win-e178

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3f3735f. Configure here.

Comment thread packages/worker/src/app/handlers/onboarding.ts
@github-actions

github-actions Bot commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-1371.kody-a99.workers.dev

Worker: kody-pr-1371
D1: kody-pr-1371-db
KV: kody-pr-1371-oauth-kv

Mocks:

… outbound

The Reply sub-step presents its facts as the welcome message to search for, so
taking whichever outbound row is newest showed the wrong subject and sender once
an account had any other outbound mail. Match the subject the first-win guide
prescribes first, and keep the newest-outbound fallback for agents that write
their own subject line.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/guides/first-win.md`:
- Around line 57-59: Specify the text language on the fenced block containing
“Welcome to Kody — reply to introduce yourself” by adding the `text` fence
identifier, preserving the block’s content unchanged.
- Around line 125-129: Update the built-in integration instructions around the
OAuth link and guide URL to avoid hard-coding https://heykody.app; derive both
from the origin used by buildFirstWinPrompt, preserving the provider query
parameter and ensuring preview, local, and custom deployments link to the same
Kody origin.

In `@packages/worker/client/routes/onboarding.tsx`:
- Around line 185-188: Update the unchanged-payload comparison in
pollOnboardingProgress to also compare welcomeEmail.subject and
welcomeEmail.fromAddress, so metadata changes trigger applyPayload even when the
four progress signals remain unchanged.

In `@packages/worker/src/app/handlers/onboarding.ts`:
- Around line 68-99: The welcome-email lookup in loadWelcomeEmail must use the
complete prescribed subject “Welcome to Kody — reply to introduce yourself” and
verify the returned message has an exact subject match before accepting it.
Preserve the fallback to the newest outbound message when no exact match is
found.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 87cad38b-2d8e-40a3-8f44-96fa689d9c1f

📥 Commits

Reviewing files that changed from the base of the PR and between 21e2a02 and b78c256.

📒 Files selected for processing (15)
  • docs/guides/README.md
  • docs/guides/first-win.md
  • e2e/onboarding.spec.ts
  • e2e/social-login.spec.ts
  • packages/worker/client/routes/onboarding-payload.ts
  • packages/worker/client/routes/onboarding.tsx
  • packages/worker/src/app/handlers/auth-provider.node.test.ts
  • packages/worker/src/app/handlers/auth-provider.ts
  • packages/worker/src/app/handlers/onboarding.node.test.ts
  • packages/worker/src/app/handlers/onboarding.ts
  • packages/worker/src/app/onboarding-data.node.test.ts
  • packages/worker/src/app/onboarding-data.ts
  • packages/worker/src/app/ssr-render.node.test.ts
  • packages/worker/src/guides/catalog.ts
  • packages/worker/universal/loader-data.ts

Comment thread docs/guides/first-win.md Outdated
Comment thread docs/guides/first-win.md Outdated
Comment thread packages/worker/client/routes/onboarding.tsx
Comment on lines +68 to +99
const welcomeEmailSubjectMatch = 'Welcome to Kody'

/**
* Subject and sender of the stored welcome email, so the Reply sub-step can
* name exactly what to search a personal inbox for. Agents write their own
* subject line, so a mailbox with no match falls back to the newest outbound
* message — during the first win that is the mail to reply to. Fails open to
* null: the sub-step reads fine without it, and a Mailbox blip must not break
* the payload.
*/
export async function loadWelcomeEmail(
env: Env,
userId: string,
): Promise<OnboardingWelcomeEmail | null> {
try {
const [matched] = await searchOwnerEmailMessages({
env,
ownerId: userId,
direction: 'outbound',
query: welcomeEmailSubjectMatch,
limit: 1,
})
const message =
matched ??
(
await listOwnerEmailMessages({
env,
ownerId: userId,
direction: 'outbound',
limit: 1,
})
)[0]

@coderabbitai coderabbitai Bot Aug 10, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Match the complete prescribed welcome subject.

welcomeEmailSubjectMatch uses only Welcome to Kody. With limit: 1, another outbound message with that prefix can become the Reply metadata. Use Welcome to Kody — reply to introduce yourself as the configured match, and select an exact subject match before falling back to the newest outbound message.

Proposed fix
-const welcomeEmailSubjectMatch = 'Welcome to Kody'
+const welcomeEmailSubjectMatch =
+	'Welcome to Kody — reply to introduce yourself'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/app/handlers/onboarding.ts` around lines 68 - 99, The
welcome-email lookup in loadWelcomeEmail must use the complete prescribed
subject “Welcome to Kody — reply to introduce yourself” and verify the returned
message has an exact subject match before accepting it. Preserve the fallback to
the newest outbound message when no exact match is found.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declining this one. Welcome to Kody is a deliberately loose match: the guide gives the full subject as an example, but agents write their own line (paraphrasing, localizing, appending a name), so requiring the exact Welcome to Kody — reply to introduce yourself would miss most real sends and drop straight through to the newest-outbound fallback — strictly worse than the substring match. The substring is already much narrower than "newest outbound", and searchMessages orders by created_at DESC, so when several messages match (a resend) the newest wins, which is the one to reply to.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipped: comment is from another GitHub bot.

…come-email facts

The guide is fetched from whichever origin serves it, so hard-coded
heykody.app URLs would send preview and self-hosted users to a different
deployment; state the Kody paths relative to the guide's own origin and say so
in the agent notes.

The progress poll short-circuited on the four progress signals alone, so a
subject and sender that arrived after the send meter settled never reached the
Reply sub-step.
@kentcdodds
kentcdodds merged commit 3206f6a into main Aug 10, 2026
10 checks passed
@kentcdodds
kentcdodds deleted the cursor/onboarding-agent-driven-first-win-e178 branch August 10, 2026 11:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants