docs: add Vibe v2 session director specification
This commit is contained in:
+926
@@ -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<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 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.
|
||||||
|
|
||||||
Reference in New Issue
Block a user