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