Files
muzick/vibe-v2-spec.md
T

927 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.