Skip to content

ADR-072: Remove intake assessment

Status: Accepted Date: 2026-05-25 Context: The 6-question intake assessment shown to every new user at first login (5 demographic + 1 attribution) was producing low-signal personalization and an actively bad mapping: users who selected "DAOs & crypto" got governance-heavy scenarios they didn't want. The expertise→theme routing was hardcoded in the scheduler with no override path short of bypassing intake entirely.

Intake had accumulated more responsibilities than its name suggested: 1. Sector → scenario category affinity (+0.10 score bonus in scheduler) 2. Permanent player context block injected into Claude prediction prompts 3. Group sector aggregation for multiplayer session context 4. Storage piggyback for unrelated solo-mode generator settings (scenario_theme, scenario_voice, world_memory, consequence_mode, custom_theme_description) 5. Mandatory onboarding gate in auth callback for new users 6. heard_from attribution analytics

Of these, only (4) was load-bearing for active gameplay. The rest were either producing noise (1, 2, 3) or imposing friction without proportional value (5, 6).

At a glance

What it decides: The 6-question first-login intake assessment is gone; new users land directly in the app, and the one load-bearing piece (solo-generator settings) moved to a profiles.scenario_settings JSONB column.

  • Removed in six staged commits so any regression is isolated to one change, ending with dropping the intake_profiles table
  • Identity-vs-process (ADR-027) is the deeper reason — demographic self-description shouldn't bias predictions or scenario routing; signal should come from gameplay
  • Rejected keeping a trimmed intake or a theme picker for v1; new users default to the boardroom theme and discover others through play
  • Watch for lower first-scenario quality for new users and prediction-accuracy regression under ~5 games played (intake was a thin prior there)

Decision

Remove the intake assessment entirely, in six staged commits:

  1. Drop DOMAIN_SCENARIO_AFFINITY map and the affinity bonus from scenario-scheduler.ts.
  2. Drop formatPermanentPlayerContext calls from solo-predict and multiplayer-predict route prompt builders.
  3. Drop group sector aggregation from sessions/start.
  4. Migrate the five solo-generator settings from intake_profiles.demographics JSONB to a new profiles.scenario_settings JSONB column. Update the single reader (solo/page.tsx) and single writer (api/profile/update-theme).
  5. Remove the intake onboarding flow: /speed-round/intake page, intake-questions.ts, intake-service.ts, the auth-callback redirect, and dev seed/login upserts.
  6. Drop the intake_profiles table.

Each stage commits + pushes independently so any regression is isolated to a single change.

Rationale

  • The harmful mapping was the trigger, but the removal is broader because the underlying premise — that 6 questions at first login produce useful personalization signal — was never validated. The crypto→DAO complaint is one observable instance; the scheduler had no other domain-mapping affinities that were producing visibly better matches either.
  • Sync's identity-vs-process principle (ADR-027) argues against demographic priors. The product position is that Sync measures how you actually decide, not how you self-describe. Intake demographics are exactly the kind of self-description that the rest of the system deliberately avoids weighting.
  • Permanent player context in prediction prompts conflicts with the same principle. Injecting "Domain: DAOs & crypto, Decision focus: Money & resources" as a static prompt block biases the prediction model toward stereotype-consistent guesses before the player has accumulated game signal.
  • The solo-generator settings are not intake data. They piggybacked on the JSONB bag because it was already there. Moving them to profiles.scenario_settings clarifies what they are (per-user config) and lets the intake concept die cleanly.
  • Removing the mandatory onboarding gate reduces drop-off. Six tap-select questions before the first scenario is friction with no compensating retention or quality benefit observed in the metrics.

Alternatives Considered

  • Keep intake, kill the expertise→theme mapping only. Rejected — addresses the specific complaint but leaves the broader low-signal-onboarding problem untouched, and the demographic prompt-injection still biases predictions.
  • Replace intake with a "pick a setting for your first campaign" theme picker. Rejected for v1 — theme can be set later in solo settings; new users default to boardroom (existing default) and discover other themes through play. If new-user theme drop-off becomes a problem post-removal, revisit.
  • Keep heard_from as a one-question post-signup survey. Rejected — attribution is useful but not enough to justify keeping any post-signup survey infrastructure. Move attribution to a marketing-side mechanism (UTM, referrer) if needed.
  • Move scenario settings to dedicated scenario_settings table instead of JSONB on profiles. Rejected — a separate table is what intake_profiles became, and the problem we're fixing. Single reader, single writer, small stable config bag → JSONB column on profiles is the right shape.
  • Individual columns on profiles for each setting. Rejected — custom_theme_description is free text, world_memory/consequence_mode may grow shape; columns are a bad fit for evolving config. Adds migration churn for fields that are inherently a bag.

Discussion

The trigger was a casual observation that crypto-tagged users were getting DAO scenarios they didn't want. Initial instinct was to remove intake entirely. Audit surfaced that intake was load-bearing in non-obvious ways — particularly the prompt injection and the scenario-settings piggyback — so the removal needed to be staged rather than ripped out in one commit.

Key trade-off debated: where to relocate the scenario settings. Three options weighed (JSONB on profiles, dedicated table, individual columns). JSONB chosen because (a) it mirrors current storage shape so the migration is a copy, (b) single reader / single writer means the table-level governance benefits don't apply, (c) the dedicated-table alternative is structurally identical to the intake_profiles mistake — a separate table that ended up holding settings.

Generator prompt contract was a constraint: the five settings have been rigorously tuned and must flow through to the same prompt slots unchanged. The migration preserves field names and values; only the storage location moves.

Consequences

  • New users land directly in the app without a 6-question gate. First scenario quality depends on default theme (boardroom) instead of sector-influenced routing.
  • Prediction prompts lose the static demographic block. Predictions for low-game-count users will be less personalized but also less stereotyped; signal accrues from gameplay instead of self-description.
  • intake_profiles table dropped; any external tooling reading it will break (none known internally).
  • Group sector context disappears from multiplayer session starts. Multiplayer scenario selection falls back to category novelty + difficulty + group-level patterns from gameplay.
  • heard_from attribution data lost going forward. Existing rows preserved in DB until table drop in stage 6.
  • Schema simplification: profiles.scenario_settings JSONB becomes the single home for per-user generator config.

Watch for: - Drop in first-scenario quality for new users (revisit theme picker if observed). - Prediction accuracy regression for users with <5 games played (intake context was a thin prior here). - Any downstream consumer of intake_profiles that the audit missed.

Key files: - src/lib/game/scenario-scheduler.ts — sector affinity map removed (stage 1) - src/app/api/ai/solo-predict/route.ts, src/app/api/ai/multiplayer-predict/route.ts — permanent player context removed (stage 2) - src/app/api/sessions/start/route.ts — group sector aggregation removed (stage 3) - src/app/api/profile/update-theme/route.ts, src/app/solo/page.tsx — moved to profiles.scenario_settings (stage 4) - src/app/auth/callback/route.ts, src/app/speed-round/intake/page.tsx, src/lib/speed-round/intake-*.ts — removed (stage 5) - migrations/NNN_drop_intake_profiles.sql — table drop (stage 6)