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:
EnneagramAssessmentmodel with JSONB score storage, computed dominant type/wing/tritype vianormalize_scores+compute_wing+compute_tritypeclass methodsSubmitEnneagramAssessmentinteraction as the canonical write path- GraphQL surface:
submitEnneagramAssessmentmutation,enneagramAssessment+enneagramAssessmentHistoryqueries Ai::Prompts::Base#build_enneagram_blockprivate class method injecting a ~250-token personality profile block into Coach system prompt (after soul block, before brevity/warmth)Ai::BuildCoachContextappends a one-line personality summary when assessment existsGenerateAiCheckInPromptsJobeager-loads assessments and threads them through the service callEnneagramFramingsclass 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.vuefull-screen assessment runner at/enneagram-assessment(40 questions, one per screen, binary choice, 300ms auto-advance withprefers-reduced-motionhonored, back button, localStorage resumption underenneagram_partial_answerskey, 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) useEnneagramAssessmentcomposable owning runner state, result caching, and localStorage persistenceEnneagramPromptCard.vueon Dashboard (gated by ≥7 days activity AND not dismissed AND not completed; mirrorsStreakRepairCard/GettingStartedCardpattern)- Settings → Personalization tab (new) showing latest result + history + retake CTA when assessment exists; entry CTA when none exists
DismissEnneagramCardinteraction + GraphQL mutation for card dismissal persistenceUserDetail.enneagram_card_dismissedJSONB 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_assessmentuser property registered + populated at boot (from assessment query) and on mutation success- Existing
thumbs_up/thumbs_downfeedback 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.mdwith full telemetry event details, fire sites (filepath:line), property schema per event, user property population paths - Phase 80 carry-forward documented:
signupAgeDaysfield for dashboard card gating - New gotcha in
docs/development/testing.md: Vitest DOM-element count mismatch with multiple element categories CHANGELOG.mdv3.9.131 entry summarizing the milestone- Milestone narrative at
docs/milestones/v1.18-enneagram-coach.md
Phases
| Phase | Name | Status | Key Deliverable |
|---|---|---|---|
| 77 | Backend foundation: assessment data layer | Shipped | EnneagramAssessment model, SubmitEnneagramAssessment interaction, GraphQL surface, SCORING_MAP stub |
| 78 | Coach integration: Rails system-prompt injection | Shipped | build_enneagram_block, Ai::Prompts::Base integration, regression tests, compute_dominant_type extraction |
| 79 | Frontend: assessment flow | Shipped | EnneagramAssessment.vue, question/result/history components, useEnneagramAssessment composable, localStorage resumption, Storybook stories |
| 80 | Frontend: discoverability surfaces | Shipped | Dashboard EnneagramPromptCard, Settings Personalization tab, dismissal persistence, signupAgeDays gating |
| 81 | Telemetry | Shipped | Six 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-coachmilestone 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
signupAgeDaysfield). 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_QUESTIONSandEnneagramFramingsconstants 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_blockformat 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 Category | Count | Status |
|---|---|---|
| Data model + GraphQL | 2 | Satisfied (Phase 77) |
| Coach integration | 3 | Satisfied (Phase 78) |
| Frontend assessment flow | 4 | Satisfied (Phase 79) |
| Frontend discoverability | 3 | Satisfied (Phase 80) |
| Telemetry + signal segmentation | 2 | Satisfied (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_MAPis 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_typeextraction 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_constgotcha documented indocs/development/testing.mdfor frozen constant mocking in future tests. Closes tech debt §6.
Related Artifacts
- Roadmap: v1.18-enneagram-coach-ROADMAP.md
- Milestone Audit: v1.18-MILESTONE-AUDIT.md (generated post-ship)
- PRD: Enneagram Assessment for Coach Personalization
- Feature docs: Enneagram Assessment
- Git tag: v3.9.131
- Phase 77 PR: #629 (backend data layer, 2026-05-13, merged to master)
- Phase 78 PR: #640 (Coach integration, 2026-05-14)
- Phase 79 PRs: #645 (backend cleanup) + #648 (frontend bundle, 2026-05-14)
- Phases 80–81 PR: #656 (discoverability + telemetry, 2026-05-14)
Related Commits
Notable commits across the five phases:
2e968025— Phase 77: Backend foundation (data layer, SubmitEnneagramAssessment, GraphQL, SCORING_MAP stub, ~600 RSpec lines) — PR #6292f2f3ef2— Phase 78: Coach integration (build_enneagram_block, Ai::Prompts injection, compute_dominant_type extraction, regression tests) — PR #640bab9b571— Phase 79: Backend cleanup (Query optimization, stub_const gotcha discovery) — PR #645a7dd1f1e— Phase 79: Frontend bundle (AssessmentVue, components, composable, Storybook, Vitest, v3.9.126 tag) — PR #64874b53859— 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)