Files
muzick/vibe-v2-spec.md

34 KiB
Raw Permalink Blame History

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 2050 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 2050 tracks, chosen from track duration and session conditions.
  • Publish only 38 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 todays 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<string, number>;
  genreDistribution: Record<string, number>;
  languageDistribution: Record<string, number>;
  decadeDistribution: Record<string, number>;
  audioTrajectory: Array<{ energy: number; tempo?: number; valence?: number }>;
  discoveryRate: number;
  generatorDistribution: Record<string, number>;
  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 listeners 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 5002,000 deduplicated candidates;
  • keep beam width 2050 with incremental objective and projected state;
  • expand only compatible candidates per slot;
  • plan 2050 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 01 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.