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_profilestable - 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
boardroomtheme 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:
- Drop
DOMAIN_SCENARIO_AFFINITYmap and the affinity bonus fromscenario-scheduler.ts. - Drop
formatPermanentPlayerContextcalls fromsolo-predictandmultiplayer-predictroute prompt builders. - Drop group sector aggregation from
sessions/start. - Migrate the five solo-generator settings from
intake_profiles.demographicsJSONB to a newprofiles.scenario_settingsJSONB column. Update the single reader (solo/page.tsx) and single writer (api/profile/update-theme). - Remove the intake onboarding flow:
/speed-round/intakepage,intake-questions.ts,intake-service.ts, the auth-callback redirect, and dev seed/login upserts. - Drop the
intake_profilestable.
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_settingsclarifies 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_fromas 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_settingstable instead of JSONB onprofiles. Rejected — a separate table is whatintake_profilesbecame, and the problem we're fixing. Single reader, single writer, small stable config bag → JSONB column onprofilesis the right shape. - Individual columns on
profilesfor each setting. Rejected —custom_theme_descriptionis free text,world_memory/consequence_modemay 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_profilestable 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_fromattribution data lost going forward. Existing rows preserved in DB until table drop in stage 6.- Schema simplification:
profiles.scenario_settingsJSONB 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)