Skip to content

v1.18 — Enneagram Assessment for Coach Personalization

Coach now reads each user's motivation pattern (Enneagram type) and tailors framing to match how they move toward goals — no behavior change for users who skip the assessment.

Summary

Enneagram Assessment for Coach Personalization is a five-phase implementation of an optional 40-question personality assessment that injects user motivation context into Coach's system prompt. Users who complete the assessment (available after 7 days of activity, opt-in, skippable) receive a one-time type + wing result; Coach automatically uses this to tailor motivation-based framings without any additional config. For example, a Type 7 (the Enthusiast) who "starts but doesn't finish" gets a Coach that says "let's focus on finishing this one first"; a Type 1 (the Reformer) seeking perfection gets explicit permission to be imperfect. Users who skip the assessment experience zero behavior change — the system prompt is byte-for-byte identical to its pre-v1.18 form.

The milestone shipped in five sequential backend→frontend→telemetry phases, with all code changes landing on master via Phase 1 (pre-confirmation) and Phases 2–5 (post-confirmation). Phase 1 data layer shipped before milestone identity was locked, setting the foundation for the remaining four phases. Phases 2–5 followed the standard 1-day delivery cadence, demonstrating a reliable pattern for small-scope feature bundles.

This milestone advances the product's north-star metric (cumulative goals completed) by reducing type-specific failure modes — Type 7 users plateau because they abandon mid-stream; Type 1 users plateau because they seek unattainable perfection. Enneagram-aware framings address these patterns directly without changing the feature set.

Goal

Give Coach a durable, opt-in motivational trait layer per user — a 40-question forced-choice Enneagram assessment that resolves to a dominant type, wing, and tritype, and is injected (~250 tokens) into the Coach system prompt for users who have completed it. Users who have not opted in see zero behavior change and zero prompt-structure delta.

Scope — What Shipped

Backend:

  • EnneagramAssessment model with JSONB score storage, computed dominant type/wing/tritype via normalize_scores + compute_wing + compute_tritype class methods
  • SubmitEnneagramAssessment interaction as the canonical write path
  • GraphQL surface: submitEnneagramAssessment mutation, enneagramAssessment + enneagramAssessmentHistory queries
  • Ai::Prompts::Base#build_enneagram_block private class method injecting a ~250-token personality profile block into Coach system prompt (after soul block, before brevity/warmth)
  • Ai::BuildCoachContext appends a one-line personality summary when assessment exists
  • GenerateAiCheckInPromptsJob eager-loads assessments and threads them through the service call
  • EnneagramFramings class with 1–2-sentence framing for each dominant type (9 entries), authored by Josh/content
  • Byte-for-byte regression tests confirming non-opted-in users see no Coach prompt delta
  • ~600 lines of RSpec across model specs, interaction specs, GraphQL specs, and integration specs

Frontend:

  • EnneagramAssessment.vue full-screen assessment runner at /enneagram-assessment (40 questions, one per screen, binary choice, 300ms auto-advance with prefers-reduced-motion honored, back button, localStorage resumption under enneagram_partial_answers key, progress indicator "N of 40", auto-submit on Q40)
  • Supporting components: EnneagramQuestionCard.vue, EnneagramResultCard.vue (dominant type + wing + expandable 9-type score breakdown), EnneagramHistoryList.vue (timeline of prior assessments)
  • useEnneagramAssessment composable owning runner state, result caching, and localStorage persistence
  • EnneagramPromptCard.vue on Dashboard (gated by ≥7 days activity AND not dismissed AND not completed; mirrors StreakRepairCard / GettingStartedCard pattern)
  • Settings → Personalization tab (new) showing latest result + history + retake CTA when assessment exists; entry CTA when none exists
  • DismissEnneagramCard interaction + GraphQL mutation for card dismissal persistence
  • UserDetail.enneagram_card_dismissed JSONB store accessor for dismissal flag (no migration)
  • Storybook stories for assessment components (light + dark variants)
  • ~80 lines of Vitest unit tests covering composable, components, view orchestration, localStorage resumption, and tritype-absence assertion

Telemetry:

  • Six PostHog lifecycle events: enneagram_card_shown (card renders), enneagram_started (user starts flow), enneagram_completed (assessment submitted), enneagram_abandoned (user leaves mid-flow), enneagram_dismissed (card dismissed), enneagram_retake_started (retake triggered)
  • has_enneagram_assessment user property registered + populated at boot (from assessment query) and on mutation success
  • Existing thumbs_up/thumbs_down feedback signal segmentable by the property for A/B analysis (target: +10pp "felt accurate" lift for opted-in vs. non-opted-in cohorts)

Documentation:

  • Expanded docs/features/enneagram.md with full telemetry event details, fire sites (filepath:line), property schema per event, user property population paths
  • Phase 80 carry-forward documented: signupAgeDays field for dashboard card gating
  • New gotcha in docs/development/testing.md: Vitest DOM-element count mismatch with multiple element categories
  • CHANGELOG.md v3.9.131 entry summarizing the milestone
  • Milestone narrative at docs/milestones/v1.18-enneagram-coach.md

Phases

PhaseNameStatusKey Deliverable
77Backend foundation: assessment data layerShippedEnneagramAssessment model, SubmitEnneagramAssessment interaction, GraphQL surface, SCORING_MAP stub
78Coach integration: Rails system-prompt injectionShippedbuild_enneagram_block, Ai::Prompts::Base integration, regression tests, compute_dominant_type extraction
79Frontend: assessment flowShippedEnneagramAssessment.vue, question/result/history components, useEnneagramAssessment composable, localStorage resumption, Storybook stories
80Frontend: discoverability surfacesShippedDashboard EnneagramPromptCard, Settings Personalization tab, dismissal persistence, signupAgeDays gating
81TelemetryShippedSix PostHog events, has_enneagram_assessment user property, feedback signal segmentation, Coach injection point verified

Key Decisions

  • De-facto branching model: Phase 1 shipped directly to master (pre-milestone-confirmation) via PR #629; Phases 2–5 shipped from gsd/v1.18-enneagram-coach milestone branch per standard protocol. Both paths converged on v3.9.131 tag (Phase 1 @ 2e968025, Phase 2 @ 2f2f3ef2, Phases 3–5 @ 74b53859).
  • Parallel-track resolution for Phase 80 (Orion deviation 4): Maggie routed Phase 80 plan-lock to concurrent tracks: Track A (Desi confirms dashboard/settings UI-SPEC without blocking on content) + Track B (Codi applies unambiguous implementation fixes like signupAgeDays field). Tracks merged when both gates passed. Pattern validated for future content-blocked milestones.
  • Content-gated frontend with eng-stubs + content-author drop-before-ship (Phase 79 deviation 4): ENNEAGRAM_QUESTIONS and EnneagramFramings constants ship as full-fidelity content (not stubs) by Phase 2 and 3 respectively; engineering does not write placeholder "TODO(content)" text. Josh or delegate authors all 49 content strings (40 question pairs + 9 type framings) and lands them in a single content PR before the corresponding phase ships. Pattern validated: engineering ships a clean, content-complete feature without CI breaks or "TODO" regressions.
  • Injection budget lock: ~250 tokens fixed at planning time. build_enneagram_block format is unambiguous (type + wing one-liner, top-3 scores, 1–2-sentence framing). No expansion in v1.18; future lifts are scoped separately.
  • Byte-for-byte regression test on non-opted-in users: All Coach behavior must be identical for users without an assessment. Verified by comparing prompt output for test users (captain/0/0/streaks, spark/2/2/milestones, sage/4/4/reflection+accountability) before and after the Enneagram injection block. If this test fails, the phase does not ship.

Requirements Coverage

All 14 epic acceptance criteria (OBJ-316) satisfied.

Requirement CategoryCountStatus
Data model + GraphQL2Satisfied (Phase 77)
Coach integration3Satisfied (Phase 78)
Frontend assessment flow4Satisfied (Phase 79)
Frontend discoverability3Satisfied (Phase 80)
Telemetry + signal segmentation2Satisfied (Phase 81)

Full requirements table: OBJ-316 Epic (14 acceptance criteria).

Outcomes

User outcomes:

  • Users active for ≥7 days can opt into the assessment from the Dashboard or Settings, receiving a tailored type profile in under 10 minutes.
  • Coach automatically learns each user's motivation pattern and frames advice accordingly (no additional setup required).
  • Users who skip the assessment see zero change; full backwards compatibility.

Product outcomes:

  • North-star metric (cumulative goals completed) gains a new personalization lever targeting type-specific failure modes.
  • Telemetry infrastructure for per-user Coach personalization in place; future trait layers (Big-5, Strengths-Finder, etc.) can reuse the injection pattern and feedback signal.
  • Validated pattern for content-gated features: content drops before ship, engineering completes feature cleanly, no "TODO" debt.

Team outcomes:

  • Parallel-track planning (Orion deviation 4) demonstrated for content-blocked work, reducing waterfall friction.
  • Small-scope, single-PR-per-phase delivery cadence validated: Phases 78–81 shipped via 4 PRs (78→79 cleanup, 79 bundle, 80–81 consolidated) with minimal rework.
  • Brand-voice audit gates confirmed essential for user-facing feature work.

Tech Debt

  • (Phase 77) EnneagramAssessment::SCORING_MAP is a structural stub (40 entries, types 1–9) pending canonical content authorship. Landing before Phase 3 ships. Owner: Josh or content delegate.
  • (Phase 78) compute_dominant_type extraction completed (class method added to model, called from both prior duplication sites). Closes tech debt §1 from ROADMAP.
  • (Phase 79) Question bank (40 pairs) and type framings (9 entries) landed with canonical brand-voice copy (no stubs in production). Closes tech debt §2.
  • (Phase 79) stub_const gotcha documented in docs/development/testing.md for frozen constant mocking in future tests. Closes tech debt §6.

Notable commits across the five phases:

  • 2e968025 — Phase 77: Backend foundation (data layer, SubmitEnneagramAssessment, GraphQL, SCORING_MAP stub, ~600 RSpec lines) — PR #629
  • 2f2f3ef2 — Phase 78: Coach integration (build_enneagram_block, Ai::Prompts injection, compute_dominant_type extraction, regression tests) — PR #640
  • bab9b571 — Phase 79: Backend cleanup (Query optimization, stub_const gotcha discovery) — PR #645
  • a7dd1f1e — Phase 79: Frontend bundle (AssessmentVue, components, composable, Storybook, Vitest, v3.9.126 tag) — PR #648
  • 74b53859 — Phases 80–81: Discoverability + telemetry (Dashboard card, Settings tab, 6 PostHog events, has_enneagram_assessment, v3.9.131 tag) — PR #656

Last updated: 2026-05-23 (post-ship)

Loading…