Repository navigation
feat(onboarding): agent-driven first win in one paste - #1371
Conversation
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.
|
Warning Review limit reached
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 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThe 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. ChangesFirst-win onboarding
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ 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.
|
🔎 Preview deployed: https://kody-pr-1371.kody-a99.workers.dev Worker: 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.
There was a problem hiding this comment.
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
📒 Files selected for processing (15)
docs/guides/README.mddocs/guides/first-win.mde2e/onboarding.spec.tse2e/social-login.spec.tspackages/worker/client/routes/onboarding-payload.tspackages/worker/client/routes/onboarding.tsxpackages/worker/src/app/handlers/auth-provider.node.test.tspackages/worker/src/app/handlers/auth-provider.tspackages/worker/src/app/handlers/onboarding.node.test.tspackages/worker/src/app/handlers/onboarding.tspackages/worker/src/app/onboarding-data.node.test.tspackages/worker/src/app/onboarding-data.tspackages/worker/src/app/ssr-render.node.test.tspackages/worker/src/guides/catalog.tspackages/worker/universal/loader-data.ts
| 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] |
There was a problem hiding this comment.
🎯 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.

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 onwhat-is-kody.mdwith the steering embedded as HTML comments: send the welcomeemail 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 theorigin 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.tsserves it fromcoding_guide_get,/guides/first-win, and/guides/first-win.mdwith no MCP changes.Step 2 collapses to one paste.
buildFirstWinPromptreplacesbuildIntroEmailPrompt/buildIntroEmailLookupPrompt, points the agent at theguide 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.tsis untouched. Copy shifts to"your agent will take it from here", and
/account/emailstays as thestored-copy fallback.
The Reply state names the real email. The onboarding payload carries
welcomeEmail— the subject and sender of the stored welcome message, matchedagainst outbound mail by the subject the guide prescribes (falling back to the
newest outbound message for agents that write their own subject, and to
nullon 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.
issueLogintakes a defaultredirect; a freshly created social-OAuth account gets
defaultPostVerificationRedirect(/onboarding) instead of/account, whichis where password signups already land after verification. An explicit
redirectTostill wins.packages/worker/src/mcp/tools/search-onboarding-notice.tsis deliberatelyuntouched — the in-search nudge stays lightweight.
Testing
npm run validategreen onea523451(format, lint, typecheck, 2047 unittests, 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:
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):
The guide serving on the web:
Conductor report
ea523451with Bugbot passing; all AI-reviewerfeedback triaged; squash-merging per the granted merge authority.
docs/guides/first-win.md+ catalog + READMEindex entry. Verified serving via
/guides/first-winand/guides/first-win.mdin e2e.buildFirstWinPrompt, single-paste Sendstate, passive pills, real subject/sender in Reply,
/account/emailfallback kept.
/onboarding,explicit
redirectTopreserved, covered by unit + e2e.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).
packages/worker/src/mcp/**(includingsearch-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:ea523451Classification: extends — no new primitives. The onboarding loader payload
changes shape (two prompt fields replaced by one, plus
welcomeEmail), thesocial-login default redirect changes behavior, and the bundled guide catalog
gains an entry.
primitives.yamlis unchanged: guides are content served byexisting surfaces.
Primitives touched
app-uiapp-sessions/onboardinginstead of/accountmcp-servercoding_guide_getpicks upfirst_winfrom the catalog with no capability editmailboxmemoriesSystem map
The onboarding page hands the whole first win to the agent: one paste points at
the bundled guide (served by both
/guidesandcoding_guide_get), and theReply 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:#fffBefore / after
Onboarding payload (
OnboardingLoaderData/OnboardingPayload):Social-OAuth callback destination:
redirectTo/account/onboardingredirectTo/account/accountInvariants
Per-user isolation is preserved: the welcome-email reads go through
searchOwnerEmailMessages/listOwnerEmailMessagesscoped to theauthenticated user's own Mailbox DO, and they fail open to
nullrather thansurfacing 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.appto deployment-relative paths.Summary by CodeRabbit
New Features
Improvements