# 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.