diff --git a/vibe-v2-spec.md b/vibe-v2-spec.md new file mode 100644 index 0000000..8f46a4b --- /dev/null +++ b/vibe-v2-spec.md @@ -0,0 +1,926 @@ +# Vibe v2 — Session Director Specification + +## Status and purpose + +This is the target specification for Vibe v2. It turns Vibe from a +recommendation queue into a session director: an autonomous system that +continuously composes the listener's next hour of music. + +The unit of optimisation is not the next track. It is a listening experience +that remains coherent, fresh, and rewarding over hours and continues to feel +new tomorrow. + +This document supplements docs/architecture/09-recommendation-and-identity-v2.md. +Where the two documents conflict, this document controls the Session Director +and Vibe playback contract. + +## Product contract + +Vibe must feel like a thoughtful radio DJ: + +- It plays familiar music, adjacent music, and worthwhile discoveries in a + deliberate balance. +- It responds to a skip, full listen, or explicit action quickly enough that + the listener can perceive the change. +- It does not collapse into a few favourite artists, genres, languages, + decades, labels, producers, or audio-feature bands. +- It has a direction: energy, novelty, and intensity can evolve, but should + not jump without a reason. +- It returns to ideas introduced earlier (callbacks) and makes room for + surprises. +- It remembers prior sessions sufficiently to avoid replaying yesterday's + shape, not merely yesterday's tracks. + + durable history + live feedback + context + | + v + listener state + | + v + candidate retrieval and expansion + | + v + sequence planner / constraint engine + | + v + revisable next 20–50 tracks + | + v + playback + | + +------------ feedback and replan ------------+ + +Vibe is not a saved playlist. A client may display a small, revisable preview +of the future, but the server owns the authoritative plan and may rewrite every +unplayed item at any time. + +## Goals and non-goals + +### Goals + +1. Maximise expected session reward, not click-through rate or immediate + predicted enjoyment alone. +2. Make recommendations conditional on current state, listening context, and + the already-played portion of the session. +3. Balance comfort, discovery, diversity, coherence, freshness, and long-term + learning while penalising fatigue, repetition, and predictability. +4. Support a self-hosted local library first. Probation tracks are candidates + only when their audio is available locally and acquisition policy permits it. +5. Remain useful with sparse metadata. Missing attributes reduce confidence; + they must never silently become a hard negative. +6. Be explainable: every selected item has provenance, a reason for its slot, + and the constraints that affected it. + +### Non-goals + +- Do not acquire copyrighted audio or bypass existing acquisition gates. +- Do not infer sensitive personal attributes. Context is opt-in and coarse + (for example home, work, walking), never precise location. +- Do not treat a permanently fixed exploration ratio, genre quota, or score + weight as the final design. Defaults are bootstraps, not truth. +- Do not expose internal goals in a way that makes the experience feel + manipulative. Explanations remain human-scale. + +--- + +## 1. System model + +### 1.1 Inputs + +| Input | Examples | Role | +|---|---|---| +| Permanent taste | favourite artists, genre affinity, negative feedback | Establishes the comfort zone | +| Multi-horizon memory | 30 minutes, 7 days, 3 months, lifetime | Separates current obsession from durable taste | +| Current session | played/queued tracks, skips, callbacks, budget spend | Determines what fits now | +| Context | hour, weekday, device, activity, coarse location, optional weather | Changes interpretation of taste | +| Music knowledge | graph claims, metadata, audio features, embeddings, quality | Retrieves and describes candidates | + +### 1.2 Optimisation objective + +For sequence q = [t1, …, tn], optimise discounted sequence reward rather than +independently sorting tracks: + + J(q | state) = sum over i of gamma^i × ( + enjoyment(ti) + + discovery_value(ti) + + transition_quality(ti-1, ti) + + freshness(ti) + + diversity_gain(q through i) + + goal_progress(ti) + - fatigue(ti) + - repetition(ti, q through i) + - predictability(q through i) + - disruption(ti-1, ti) + ) + +Hard safety and availability constraints apply before optimisation. Gamma +discounts distant slots so the director is decisive about the next few tracks +without pretending it knows the exact state 40 tracks later. + +Initial weights may be hand-tuned, but must be versioned policy configuration. +They become learnable only after sufficient reliable events exist. + +### 1.3 Planning horizon + +- Keep an internal horizon of 20–50 tracks, chosen from track duration and + session conditions. +- Publish only 3–8 tracks to the client as a mutable preview. +- Treat only the immediate next track as committed. +- Replan after every material event and before the preview falls below three + playable tracks. +- Do not send duplicate tracks in one Vibe session unless repeat-one was + explicitly requested. + +--- + +## 2. Durable session model + +### 2.1 Session identity + +Every Vibe request, playback event, plan version, and feedback event MUST be +keyed by session_id and user_id. play_history.batch_id is not a session ID and +must not be used as one. + +Sessions end explicitly, after configurable inactivity, or when a new Vibe +session replaces the current one. A session can resume in a short grace period +without losing state or goals. + +### 2.2 Session context + +Context is optional, versioned, and privacy-preserving. + + interface VibeContext { + timeZone?: string; + localHour?: number; // normally server-derived + weekday?: number; // normally server-derived + dayKind?: 'weekday' | 'weekend' | 'holiday'; + device?: 'desktop' | 'phone' | 'speaker' | 'car' | 'headphones'; + activity?: 'focus' | 'relax' | 'walking' | 'workout' | 'social' | 'unknown'; + locationCategory?: 'home' | 'work' | 'gym' | 'travel' | 'unknown'; + weather?: 'clear' | 'rain' | 'snow' | 'hot' | 'cold' | 'unknown'; + source?: 'current_track' | 'artist' | 'genre' | 'surprise' | 'resume'; + } + +The frontend must provide an unobtrusive activity/context selector, beginning +with activity and device. Browser context is a hint and is never required. + +### 2.3 Event ledger + +Store immutable events before updating derived state. This makes feedback +auditable, supports offline evaluation, and prevents listener state being the +only record of why a plan changed. + + CREATE TABLE vibe_sessions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL, + status TEXT NOT NULL CHECK (status IN + ('active','paused','ended','expired','replaced')), + seed_track_id UUID REFERENCES tracks(id) ON DELETE SET NULL, + context JSONB NOT NULL DEFAULT '{}'::jsonb, + policy_version TEXT NOT NULL, + started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + last_event_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + ended_at TIMESTAMPTZ + ); + + CREATE TABLE vibe_events ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + client_event_id UUID, + session_id UUID NOT NULL REFERENCES vibe_sessions(id) ON DELETE CASCADE, + user_id UUID NOT NULL, + track_id UUID REFERENCES tracks(id) ON DELETE SET NULL, + type TEXT NOT NULL, + occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + position_ms INTEGER, + duration_ms INTEGER, + payload JSONB NOT NULL DEFAULT '{}'::jsonb, + UNIQUE (session_id, client_event_id) + ); + + CREATE TABLE vibe_plan_versions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + session_id UUID NOT NULL REFERENCES vibe_sessions(id) ON DELETE CASCADE, + version INTEGER NOT NULL, + reason TEXT NOT NULL, + state_snapshot JSONB NOT NULL, + objective_snapshot JSONB NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE (session_id, version) + ); + + CREATE TABLE vibe_plan_items ( + plan_version_id UUID NOT NULL REFERENCES vibe_plan_versions(id) ON DELETE CASCADE, + ordinal INTEGER NOT NULL, + track_id UUID NOT NULL REFERENCES tracks(id) ON DELETE CASCADE, + slot_role TEXT, + candidate_source TEXT NOT NULL, + score REAL NOT NULL, + score_breakdown JSONB NOT NULL, + explanation JSONB NOT NULL, + committed BOOLEAN NOT NULL DEFAULT false, + PRIMARY KEY (plan_version_id, ordinal), + UNIQUE (plan_version_id, track_id) + ); + +Create indexes on vibe_sessions(user_id, last_event_at DESC), +vibe_events(session_id, occurred_at), and vibe_events(user_id, occurred_at DESC). + +Initial event types: + + session_started, session_resumed, session_ended, context_changed, + plan_published, track_served, playback_started, progress, + completed, skipped, disliked, kept, favourite_added, queue_removed, + manual_search, album_opened, artist_opened, playlist_added, + track_replayed, volume_changed, playback_error + +Event writes must be idempotent. The client supplies client_event_id for any +action that might be retried after a network failure. + +### 2.4 Derived listener state + +session_state is a cache derived from events and recent history, not the sole +source of truth. It must be rebuildable. + + interface ListenerSessionState { + sessionId: string; + updatedAt: string; + ageMin: number; + + energy: number; + valence: number | null; + focus: number | null; + attention: number | null; + cognitiveLoad: number | null; + emotionalIntensity: number | null; + noveltyHunger: number; + noveltyTolerance: number; + danceabilityTarget: number | null; + acousticnessTarget: number | null; + instrumentalnessTarget: number | null; + vocalPreference: 'vocal' | 'instrumental' | 'mixed'; + tempoTarget: number | null; + moodTags: Array<{ tag: string; weight: number }>; + + explorationCoefficient: number; + discoveryRadius: number; + entropyTarget: number; + currentArc: ArcInstance; + activeGoals: SessionGoal[]; + callbackLedger: CallbackToken[]; + fatigue: FatigueSnapshot; + budgetSpend: BudgetSpendSnapshot; + recent: RecentSessionSummary; + } + +Every inferred value carries confidence and source. Low confidence causes +broader, safer choices; it never means the value is zero. + +### 2.5 State update rules + +- Completion shifts continuous state modestly toward reliable track features, + weighted by listen ratio and feature confidence. +- A quick skip is negative evidence for the track and recommendation route; it + is not automatically a dislike of every artist/genre/feature on the track. +- A saved discovery or intentional replay is strong evidence for exploration at + that distance and source. +- Repeated unfamiliar skips shrink exploration temporarily; repeated unfamiliar + completions expand it gradually. +- Context changes may reset desired trajectory while retaining fatigue and + long-term memory. +- Updates MUST be ordered per session. Concurrent feedback must be serialized + or use a session-version compare-and-swap. + +--- + +## 3. Memory horizons and listener model + +The listener has independent memories, each with its own decay and purpose. + +| Horizon | Window | Captures | Primary effect | +|---|---:|---|---| +| Immediate | 30 minutes | current direction, skips, active fatigue | transitions and next slots | +| Session | session lifetime | arc, budgets, callbacks, served tracks | sequence planning | +| Daily | 24 hours | today’s exposure and shape | fatigue and fresh starts | +| Weekly | 7 days | recent obsessions and routines | variety across days | +| Medium | 3 months | stable recent taste | affinity and discovery neighbourhood | +| Lifetime | slow decay | durable favourites and familiar anchors | comfort candidates | + +Existing long-term, obsession, discovery, negative, forgotten, and contextual +belief profiles remain useful. Vibe adds a session layer; it does not flatten +all profiles into one taste score. + +### 3.1 Session similarity memory + +After each session, generate a compact fingerprint: + + interface SessionFingerprint { + artistDistribution: Record; + genreDistribution: Record; + languageDistribution: Record; + decadeDistribution: Record; + audioTrajectory: Array<{ energy: number; tempo?: number; valence?: number }>; + discoveryRate: number; + generatorDistribution: Record; + acceptedDiscoveries: string[]; + durationMin: number; + } + +At the next session start, softly penalise similarity to recent fingerprints, +especially one or two days ago. An explicit artist/album/genre seed may +intentionally override this penalty. + +--- + +## 4. Music representation and candidate retrieval + +### 4.1 Feature completeness + +Each playable track should expose, when available: + +- main/featured artists, album, label, producers, composers, scenes; +- genres and tags with confidence; +- release year/decade, language, vocal/instrumental classification; +- BPM, key, energy, valence, danceability, acousticness, instrumentalness, + liveness, loudness, duration; +- live version, cover, remix, soundtrack, and side-project relationships; +- quality, availability, and feature-completeness indicators. + +Missing audio analysis is unknown, never low energy or instrumental. + +### 4.2 Embeddings and discovery radius + +The long-term target is a shared semantic space for tracks, artists, albums, +genres, sessions, and listener/context representation. It may begin with +feature-derived vectors and later use learned embeddings. Recommended +dimensions are 768 or 1024. Musical, lyrical, collaborative, and contextual +vectors can be blended at retrieval time. + +| Distance | Interpretation | +|---:|---| +| 0.00 | already familiar/favourite | +| 0.15 | same artist or tightly associated artist | +| 0.30 | strongly similar artist/sound | +| 0.45 | different artist in a known style | +| 0.65 | adjacent genre or scene | +| 0.90 | surprising but explainable | + +discoveryRadius is a range, not a switch. Sequences normally move through +adjacent distances and return to an anchor. Large jumps need a surprise slot or +listener reinforcement. + +### 4.3 Candidate pools + +Generate independently, retain all provenance, deduplicate by track and +canonical identity, then rank. + +| Pool | Default share | Purpose | +|---|---:|---| +| Familiar/favourites | 30% | comfort and callbacks | +| Similar tracks/artists | 20% | continuity | +| User-niche recent/trending | 15% | timely relevant discovery | +| Long-tail discovery | 10% | avoid popularity collapse | +| Contextual candidates | 10% | fit activity/time | +| Artist/graph traversal | 10% | explainable adjacency | +| Controlled serendipity | 5% | bounded surprise | + +These are retrieval targets, not mandatory final-plan shares. Allocation adapts +to exploration, candidate availability, fatigue, and the active arc. + +Required generator classes: + +1. Comfort: favourites and trusted artists with fatigue suppression. +2. Adjacent: graph/ANN neighbours of recent successes/current trajectory. +3. Discovery: unfamiliar artists at a safe distance with credible graph path. +4. Revival: forgotten favourites, old obsessions, accepted tracks after rest. +5. Deep dive: album/obsession exploration limited by album/artist budgets. +6. Contextual: candidates with comparable activity, hour, or device evidence. +7. Freshness: new releases/niche trends fitting the listener graph. +8. Serendipity: deliberately bounded unexplored route, never random catalogue. + +Candidate provenance retains every nominating path, not just the winning source. +It is required for explanations and source-level learning. + +--- + +## 5. Fatigue, repetition, and diversity + +### 5.1 Fatigue principle + +Everything can fatigue; everything recovers. Fatigue is decayed exposure, not +a permanent ban. + + fatigue(dimension, entity, now) = + min(1, sum of exposure_weight(event) × exp(-event_age / tau_dimension)) + +Exposure weight is higher for completion, lower for a short sample, and zero +for playback failure. Tau is configurable per dimension and may be adapted from +observed tolerance. + +Track fatigue must blend multiple windows: today, yesterday, week, and month. +It must not be a single recent-history query. + +### 5.2 Required fatigue dimensions + +| Dimension | Example | Planner action | +|---|---|---| +| Track | heard today/replayed yesterday | strong suppression unless requested | +| Artist/canonical identity | 12 tracks by one artist | widen artist pool | +| Album | half an album played | spread across hours/days | +| Genre/scene | 18 metal tracks | move to adjacent style | +| Language | 35 Japanese tracks | mix another language/instrumental | +| Vocal/instrumentation | all vocal, same vocal type | alternate texture | +| Tempo/energy/valence | narrow BPM/mood band | controlled transition | +| Producer/label | repeated creative lineage | force new route | +| Decade | all recent releases | reintroduce another era | +| Recommendation route | same graph edge repeatedly | diversify path | + +Unknown metadata may not claim to satisfy a specific quota. + +### 5.3 Repetition rules + +Hard rules, unless explicitly overridden: + +- A served track never reappears in one Vibe session. +- A track in its configured recent window is ineligible. +- No more than two tracks by a canonical artist in 20 tracks. +- No more than three tracks from an album in 40 tracks. +- Skipped/disliked tracks are ineligible for the rest of the session. + +Soft, adaptive minimum distances apply to artist, album, genre, language, +producer, and energy band. If the pool is too small, relax the least important +soft constraint, record it, and never silently relax hard track exclusion. + +### 5.4 Diversity budgets + +Budgets are planner resources, not passive analytics. Initial defaults: + +| Dimension | Target | Horizon | +|---|---:|---:| +| Any artist | at most 20% | 30 min | +| Any genre | at most 40% | 30 min | +| Any language | at most 60% | 30 min | +| Instrumental | at least 10% if inventory permits | 30 min | +| New artists | about 15% | 60 min | +| Familiar favourites | about 25% | 60 min | + +Budgets may be upper bounds, lower bounds, or target ranges. The planner +projects spend across the proposed sequence, not only completed history. + +Explicit user intent may temporarily override soft targets. Starting an album +or repeatedly selecting an artist narrows the session intentionally; diversity +prevents accidental loops, not explicit choice. + +### 5.5 Anti-loop detector + +Run after every state update and every plan proposal. Detect concentration across +artist/canonical identity, album, genre, scene, label, producer, language, +decade, BPM, energy, valence, vocal type, candidate source, and graph path. + +Use HHI and Shannon entropy accurately; HHI must not be called entropy. +Correct the detected dimension directly: + +| Loop | Required correction | +|---|---| +| Artist/album | retrieve other artists; reserve only later callback | +| Genre/scene | retrieve adjacent genres at compatible energy | +| Language/vocal | reserve next eligible alternate slot | +| Tempo/energy | alter next arc target gradually | +| Producer/label/route | exclude repeated relationship from retrieval | +| Low route diversity | require another generator/path | + +Do not merely boost an already available experimental candidate if it does not +fix the detected loop. + +--- + +## 6. Arcs, callbacks, surprise, and long-term rhythm + +### 6.1 Arc templates + +An arc is desired trajectory plus slot roles and transition tolerance, not just +a generator list. + +| Arc | Example trajectory | +|---|---| +| Comfort | known → known → adjacent → favourite | +| Discovery | favourite → similar → new → familiar callback | +| Energetic | medium → high → peak → cooldown | +| Late-night | soft → ambient → acoustic → slow electronic | +| Focus | instrumental/low-vocal → steady complexity → gentle reset | +| Album exploration | familiar anchor → album chapter → relief → callback | + +Slots define target ranges/deltas for energy, tempo, valence, acousticness, +instrumentality, novelty distance, and familiarity. Missing features reduce +confidence rather than reject a candidate. + +Arc selection depends on state, context, age, explicit intent, and weekly +schedule. It changes only when feedback/context makes the current arc +implausible. + +### 6.2 Transition model + +For every pair, compute transition quality from: + +- tempo delta and beat compatibility where reliable; +- harmonic/key compatibility where available; +- energy, valence, acousticness, danceability, instrumentality deltas; +- genre/scene continuity or an explainable bridge; +- artist/album separation; +- language and vocal contrast when fatigue calls for it; +- novelty-distance progression; and +- current arc fit. + +A good transition is not always similarity. A cooldown after a peak or a +surprise after an anchor is good if it fits the arc. + +### 6.3 Callbacks + +A callback token is created when an artist, genre, energy peak, theme, or +favourite is worth revisiting. It has minimum/maximum separation and cannot +violate fatigue/repetition rules. + + favourite → new artist → adjacent artist → callback to favourite + heavy → soft bridge → different heavy track + forgotten favourite → side project → return to old era + +Callbacks are optional and must never become repetition. + +### 6.4 Surprise budget + +Every sustained session needs bounded surprise. Attempt at least one eligible, +explainable surprise per hour when inventory permits. Types include forgotten +favourite, live/acoustic/cover/remix, producer/side project, old obsession, +soundtrack connection, and novel graph route. + +A surprise is paired with a recovery anchor. A quick skip lowers propensity for +that surprise type, not the listener’s whole taste profile. + +### 6.5 Invisible goals + +Goals are durable, bounded, and never override hard constraints or explicit +direction. Examples: + +- complete an album over several days without a block; +- introduce a promising artist gradually; +- revisit a favourite monthly after recovery; +- balance languages/decades over a week; +- rotate producer/scene routes; +- evaluate a probation discovery; +- preserve a surprise opportunity per hour. + + CREATE TABLE vibe_goals ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL, + type TEXT NOT NULL, + entity_type TEXT, + entity_id UUID, + state JSONB NOT NULL DEFAULT '{}'::jsonb, + priority REAL NOT NULL DEFAULT 0.5, + status TEXT NOT NULL CHECK (status IN ('active','paused','complete','expired')), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + due_at TIMESTAMPTZ, + completed_at TIMESTAMPTZ + ); + +### 6.6 Weekly rhythm + +Vibe may use an opt-in weak weekly prior: Monday discoveries, Tuesday album +exploration, Wednesday comfort, Thursday forgotten favourites, Friday high +energy, weekend broad/context-led mix. It must never override explicit intent +or current context. + +--- + +## 7. Candidate ranking and sequence planning + +### 7.1 Candidate proposal score + + candidate_score = + w_affinity × personal_affinity + + w_session × session_state_fit + + w_transition × transition_fit_from_previous + + w_discovery × discovery_value + + w_freshness × freshness + + w_quality × track_quality + + w_goal × goal_progress + - w_fatigue × fatigue_penalty + - w_repeat × repetition_penalty + - w_saturation × dimension_saturation + - w_risk × unsupported_metadata_or_route_risk + +This is a proposal score, not final selection. Persist normalised, +policy-versioned score breakdown with every selected plan item. + +### 7.2 Constraint engine + +Apply constraints during sequence construction: + +1. Exclude unavailable, hidden, retired, served, and blocked tracks. +2. Enforce hard repetition/safety constraints. +3. Satisfy arc slot requirements. +4. Project budgets across the horizon. +5. Penalise session similarity and path concentration. +6. Reserve callback, discovery, and surprise opportunities. +7. Select the highest-value feasible sequence. + +If no feasible sequence exists, return a degraded plan with structured +constraint_relaxations. Relax soft diversity targets, then arc precision, then +freshness. Never reintroduce an explicitly disliked or served track simply to +fill the plan. + +### 7.3 Planner algorithm + +Initial implementation is constrained beam search: + +- retrieve about 500–2,000 deduplicated candidates; +- keep beam width 20–50 with incremental objective and projected state; +- expand only compatible candidates per slot; +- plan 20–50 slots; commit first; publish preview only; +- replan from current state after feedback while preserving committed track and + still-valid callback/goal tokens. + +Later options include MCTS, transformer decoding, or model-predictive control. +The planner interface must stay algorithm-agnostic. + +### 7.4 Controlled unpredictability + +Measure predictability over recent and proposed sequence using artist, genre, +source/path, novelty distance, and feature distributions. + +- Too predictable: allocate adjacent/surprise candidate and protect a later + comfort callback. +- Too chaotic: shrink discovery radius and inject a familiar anchor. + +The target is intentional surprise, neither randomness nor maximum familiarity. + +--- + +## 8. Feedback and learning + +### 8.1 Feedback interpretation + +| Signal | Interpretation | Immediate action | +|---|---|---| +| Skip under 5 sec | strong mismatch | remove route/track, reduce local risk, replan | +| Skip under 20 sec | mismatch | penalise candidate/path, replan | +| Late skip | weak negative/transition issue | modest penalty | +| Completion | weak positive | update state/affinity modestly | +| Replay | strong positive | reinforce affinity and comfort | +| Keep/favourite | strong positive | reinforce entities and route | +| Playlist add/share | very strong positive | reinforce strongly | +| Mute/hide artist/genre | strong negative | exclude or suppress | +| Manual search/open album | intent evidence | bias session appropriately | +| Volume change | weak/noisy evidence | aggregate with other signals | +| Session abandonment | delayed negative reward | evaluate preceding sequence | + +Completion uses actual position and duration. Long intros must not be treated +exactly like short tracks. + +### 8.2 Dynamic exploration + +Maintain exploration coefficient E in range 0–1 and discovery radius. + + quick skip of unfamiliar track E -= 0.05 + completion of unfamiliar track E += 0.03 + save/favourite unknown artist E += 0.12 + repeated known completion E -= 0.01 only if comfort over budget + explicit more-discovery control bounded immediate increase + +Clamp and smooth updates so one event does not whiplash a session. Persist the +evidence. play_of_never_seen or equivalent MUST be emitted by production +playback; a read-only schema capability is not a feature. + +### 8.3 Learning rollout + +1. Ship deterministic rules with complete event and plan logging. +2. Replay historical sessions for offline evaluation. +3. Use contextual bandits for calibrated immediate/short-horizon weights and + generator routing under safety limits. +4. Consider offline RL/model-based sequence policy only after reliable + off-policy evaluation exists. + +Retain deterministic fallback and a kill switch for learned policy. + +--- + +## 9. API and frontend contract + +### 9.1 Start/resume + + POST /api/v2/vibe/sessions + + { + "seedTrackId": "uuid or optional", + "context": { "activity": "focus", "device": "headphones" }, + "intent": "optional mode", + "resumeSessionId": "uuid or optional" + } + +Response includes sessionId, planVersion, now, revisable preview, and concise +state summary. User identity comes from authenticated/trusted request context, +not a silent shared default UUID. + +### 9.2 Playback event + + POST /api/v2/vibe/sessions/:sessionId/events + + { + "eventId": "client UUID", + "type": "progress | completed | skipped | kept | disliked | ...", + "trackId": "uuid", + "positionMs": 12500, + "durationMs": 203000, + "payload": {} + } + +Response includes canonical planVersion, replacement preview, state summary, +and replan reason. + +The client MUST reconcile its unplayed Vibe buffer whenever replacement preview +is returned. It must not keep playing stale prefetched items only because they +were fetched before feedback. The currently loaded track is not interrupted +unless the listener explicitly skips it. + +### 9.3 Serve next + + POST /api/v2/vibe/sessions/:sessionId/next + { "expectedPlanVersion": 4 } + +The response is idempotent for request/plan version and marks the item served. +If a newer plan exists, return that preview instead of a stale track. + +### 9.4 UI requirements + +- Present Vibe as an evolving session, not static playlist. +- Show concise direction such as gentle discovery or late-night cooldown. +- Preview only revisable next tracks and label them adaptive. +- Offer Keep, Dislike, Skip, End, plus implicit progress tracking. +- Offer lightweight context and more-familiar/more-discovery controls. +- Explain a track on demand with provenance and human-readable reason. +- Do not let ordinary library actions accidentally write Vibe feedback. +- Preserve session identity through navigation and recover after transient + network failure. + +--- + +## 10. Operational requirements + +### 10.1 Consistency and concurrency + +- Serialize plan mutation per session. +- Use monotonic planVersion and client optimistic concurrency. +- Redis may cache active plans; Postgres event/plan records are authoritative. + Redis expiry must never erase only session history. +- Stale-session reaper ends inactive sessions and creates final fingerprint; it + never deletes listener history. +- Revalidate track state and availability immediately before serving. + +### 10.2 Performance + +- Candidate retrieval p95 under 250 ms for warm local catalogue. +- Replan p95 under 750 ms for a 20-track baseline horizon. +- Serve-next p95 under 150 ms when valid plan exists. +- If ANN, external metadata, or context is unavailable, fall back to local + graph/metadata/favourites and record degraded source state. + +### 10.3 Observability + +Log/measure per policy version and session: + +- candidate counts/rejection reasons per generator; +- metadata coverage; +- constraint violations and relaxations; +- replan latency/reason; +- client stale-preview replacement success; +- fatigue/budget/entropy trajectories; +- discovery source/distance acceptance; +- session outcome metrics. + +Never log raw precise location or unnecessary personal context. + +--- + +## 11. Success metrics + +Primary metrics: + +- average uninterrupted listening duration; +- completed-session duration and return probability; +- discovery acceptance by distance/source; +- new artists saved/favourited; +- playlist additions and intentional replays; +- perceived freshness; +- artist/genre/language/path diversity and repetition rate; +- sessions with a successful surprise and healthy comfort anchor. + +Secondary diagnostics: + +- quick/medium skips, hides, abandonments; +- plan replacement latency; +- no-eligible-candidate rate; +- metadata coverage/fallback frequency; +- hard-constraint satisfaction; +- similarity to recent sessions. + +CTR is diagnostic only. It must not become the objective that causes +favourite-artist loops. + +--- + +## 12. Delivery phases and acceptance criteria + +### Phase 0 — Correct session contract + +Deliver durable sessions/events/versioned plans, session IDs on all playback +events, and client replacement of unplayed preview after replan. + +Acceptance: + +- A quick skip changes unplayed preview within one successful event round trip. +- Completion/skip cannot be attributed to another session. +- Backend restart does not lose event history or plan audit. + +### Phase 1 — Enforced fatigue and diversity + +Wire every budget/fatigue dimension used by policy into construction. Add album, +language, vocal/instrumental, and route controls. + +Acceptance: + +- Fixtures prove artist, album, genre, language, and track constraints. +- Changing a budget changes the plan, not only debug output. +- Anti-loop response corrects the detected dimension. + +### Phase 2 — Musical arcs and sequence planner + +Add feature access, transition scoring, callbacks, surprise tokens, constrained +beam search. + +Acceptance: + +- Energetic arc rises then cools within tolerance when features exist. +- Discovery arc anchors a discovery between familiar items. +- Chosen plans beat greedy order on offline fixture objective. + +### Phase 3 — Context, long-term goals, session memory + +Add context capture, fingerprints, weekly priors, and scheduling. + +Acceptance: + +- Context affects ranking only with comparable evidence; otherwise fallback is safe. +- Consecutive unseeded sessions are less similar than baseline without reducing + completion rate. +- Album/artist goals spread exposure across sessions. + +### Phase 4 — Dynamic exploration and learning + +Emit complete feedback, calibrate radius, add offline evaluation, then guarded +online learning. + +Acceptance: + +- Unknown-track completion/skip changes exploration in expected bounded direction. +- Learned decisions have policy version, feature log, and fallback. +- No rollout proceeds without offline and guardrail metrics. + +### Phase 5 — Embeddings and world model + +Introduce ANN retrieval and, only when data quality warrants it, learn: + + listener_state(t) + selected_track -> listener_state(t + 1) + +The controller can then select tracks partly for the state they create, not +only immediate affinity. This phase is optional and never blocks the +deterministic director. + +--- + +## 13. Test matrix + +| Scenario | Required assertion | +|---|---| +| Fast skip of unknown | preview replaced; radius shrinks; track excluded | +| Accepted discovery | radius grows; source receives positive attribution | +| Artist loop | cap respected; compatible bridge used | +| Language fatigue | alternate language/instrumental appears if available | +| Album deep dive | tracks spread; no accidental completion block | +| Low entropy | explainable diversity plus familiar anchor | +| High entropy | comfort inserted without favourite collapse | +| Context change | new preview without losing fatigue history | +| Redis loss | durable event/plan restores coherent preview | +| Sparse metadata | safe plan and reduced-confidence record | +| Explicit artist intent | soft diversity may yield; served tracks never repeat | +| Long session | no served duplicate; callbacks/surprise/budgets bounded | + +Use deterministic catalogue fixtures with known artists, albums, languages, +audio features, and graph routes. Test resulting sequences, not merely whether +a candidate list was sorted. + +## Final product definition + +Vibe succeeds when a listener can spend six hours with it and feel it +understood both their taste and their moment: it mixed comfort with meaningful +discovery, maintained a coherent evolving arc, avoided fatigue and obvious +loops, made memorable returns, and still left tomorrow feeling fresh. +