Track Lab is an AI-powered music intelligence workstation for DJs, producers, and music researchers. It enriches track metadata, searches for remix candidates, and keeps each investigation grounded in provider evidence from sources such as Spotify, GetSongBPM, Beatport, SoundCloud, Wikipedia, and saved results.
The system combines deterministic provider pipelines with focused LLM judgement: providers fetch evidence, application code owns orchestration and persistence, and AI helps classify ambiguous dance-music signals, synthesize metadata, judge remix relevance, and drive the track-focused chat assistant.
apps/api- Express API.apps/web- React/Vite UI.packages/agent-chat- AI chat orchestration, session routing, and user-facing tool execution for track analysis and remix search.packages/metadata-enrichment- metadata enrichment strategy, provider planning, provider evidence merging, and AI-assisted metadata synthesis.packages/providers- external data providers. Providers fetch evidence only.packages/remix-search- Remix discovery providers, deterministic scoring, and compact LLM judging.packages/datastore- Prisma/PostgreSQL datastore repositories for track results, remix results, agent sessions, and jobs.
The canonical architecture diagram is docs/system-architecture.md.
The current strategy doc is packages/track-analysis/src/strategy/pipeline-strategy.md.
The workflow graph is packages/track-analysis/src/strategy/pipeline-workflow.md.
The agent workflow graph is docs/agent-workflow.md.
flowchart TD
A[User request] --> B[Normalize request]
B --> C{Operation}
C -->|analyze / enrich| D[Load known metadata]
D --> E[Run Spotify identity search]
E --> F[Run GetSongBPM if BPM or key is missing]
F --> G{Deterministic EDM signals clear?}
G -->|yes| H[Run Beatport + SoundCloud in code]
G -->|no| I[Skip EDM providers]
G -->|ambiguous| J[Ask LLM EDM classifier]
J --> H
H --> K[Normalize / dedupe evidence]
I --> K
K --> L[metadata synthesis]
L --> M[Final metadata]
C -->|remix_search| N[Resolve original track]
N --> O[Build remix queries]
O --> P[Run remix providers in code]
P --> Q[Deterministic scoring + dedupe]
Q --> R[Compact LLM judge batches]
R --> S{Batch yielded results?}
S -->|yes| T[Select final candidates]
S -->|no| U[Try one more batch]
U --> V{Any candidates?}
V -->|yes| T
V -->|no| W[Deterministic fallback]
M --> X[Store result / review queue]
T --> X
W --> X
The LLM appears only at decision points where deterministic code is not enough: ambiguous EDM provider planning, metadata synthesis from evidence, remix candidate judging, and the conversational agent's user-facing tool selection. Provider-specific calls remain inside application code.
yarn install
yarn dev
yarn dev:webDocker setup: docker/README.md
Run the full local stack with:
yarn dev:dockerThis starts the API, worker, web app, PostgreSQL, and the one-shot migration service that applies Prisma migrations.
Useful checks:
yarn typecheck
yarn workspace @track-lab/web buildCreate .env in the repo root from .env.example.
cp .env.example .envSet DATABASE_URL to the real connection string you want everywhere, such as
your RDS URL with sslmode=no-verify.
QUEUE_PROVIDER=database keeps the current database-backed job queue.
QUEUE_PROVIDER=sqs uses AWS SQS for job delivery, while
track_analysis_jobs remains the source of truth for job state and history.
When using QUEUE_PROVIDER=sqs from Docker, mount your host ~/.aws
directory into the api and worker containers and set AWS_PROFILE in
.env so the SDK can read your local AWS credentials or SSO cache.
Provider credentials are optional for local wiring, but real enrichment quality
depends on them.
OPENAI_API_KEY enables the AI-backed chat assistant, ambiguous EDM planning,
metadata synthesis, and remix judging. Without it, deterministic wiring can
still run locally, but AI-backed paths will be unavailable or degraded.
Beatport currently uses public web search only and may be blocked by Cloudflare.
SoundCloud remix discovery can use SoundCloud's public search fallback directly.
SEARXNG_SEARCH_URL is optional and can provide another free search source.
Run SearXNG locally with Docker:
docker run --rm -p 8080:8080 searxng/searxngSaved results are stored in PostgreSQL through Prisma by default.
The datastore uses normalized Title + Artists keys for uniqueness.
DATASTORE_PROVIDER=prisma
DATABASE_URL=postgresql://track_lab:track_lab@your-rds-endpoint:5432/track_lab?sslmode=no-verifyBackground work is stored in the track_analysis_jobs datastore table and
exposed as TrackAnalysisJob API objects.
Job operations:
analyze- enrich track metadata, preferring saved datastore results.enrich- fresh metadata enrichment using optional known metadata.remix_search- find remix candidates for a track or Spotify URL.
Job statuses:
queued- waiting for the worker to claim it.processing- claimed by the worker;attemptCounthas been incremented.completed- worker stored a JSON result andcompletedAt.failed- reserved retryable failure status.dead_lettered- exhaustedmaxAttempts; storeserrorMessageandcompletedAt.
Job payloads:
type TrackAnalysisPayload =
| {
operation: "analyze" | "enrich";
track: { title: string; artists: string };
knownMetadata?: {
album?: string | null;
bpm?: number | null;
genre?: string | null;
subGenre?: string | null;
key?: string | null;
spotifyUrl?: string | null;
};
source?: "manual" | "saved_result" | "bulk_saved_results";
}
| {
operation: "remix_search";
request: {
title?: string | null;
artists?: string | null;
spotifyUrl?: string | null;
genre?: string | null;
};
};Job API shape:
type TrackAnalysisJob = {
id: number;
operation: "analyze" | "enrich" | "remix_search";
status: "queued" | "processing" | "completed" | "failed" | "dead_lettered";
payload: TrackAnalysisPayload;
result: unknown | null;
errorMessage: string | null;
attemptCount: number;
maxAttempts: number;
createdAt: string;
updatedAt: string;
completedAt: string | null;
notificationReadAt: string | null;
resolvedAt: string | null;
};Persisted columns use snake_case names: id, operation, status,
payload_json, result_json, error_message, attempt_count, max_attempts,
created_at, updated_at, completed_at, notification_read_at, and
resolved_at. The worker claims the oldest queued job, writes result_json on
success, and requeues failures until attempt_count >= max_attempts.
- The chat assistant can understand natural language requests such as "analyze Strobe by deadmau5", "find bass remixes for this track", or "analyze Levels by Avicii". It keeps follow-ups in the current track-focused session and starts a new session when the user explicitly names a different track.
- The metadata enrichment pipeline returns
Title,Artists,Album,BPM,Genre,SubGenre,Key, summary, provider status, provider URLs, and errors. - AI-assisted synthesis turns provider evidence into a concise final metadata result while deterministic code keeps provider execution, dedupe, and storage predictable.
- Remix search combines provider candidates, deterministic scoring, and compact LLM judging to identify likely remixes, edits, bootlegs, VIPs, flips, and reworks.
- By default, enrichment checks saved results first.
- The UI can skip saved results to force a fresh enrichment.
- Saved results can be viewed, re-enriched, saved again, or removed.
See docs/ai-terminology.md for the project naming
rules. In short: providers fetch external evidence, planners decide execution
paths, pipelines run multi-step flows, and tools are only capabilities directly
callable by an LLM or agent orchestrator.
POST /track-analysis- enqueue analyze/enrich metadata work.POST /agent- legacy compatibility alias for enqueueing analyze/enrich metadata work. New code should use/track-analysis.POST /remix-search- search remix candidates.GET /track-analysis/jobs- list worker jobs; supportsstatus,unresolved=true, andunread=true.GET /track-analysis/jobs/:id- get one worker job.POST /track-analysis/jobs/:id/retry- requeue a failed or dead-lettered job.POST /track-analysis/jobs/:id/resolve- mark a terminal job resolved.POST /track-analysis/jobs/:id/notification-read- mark a job notification read.GET /results- list saved results.GET /results/:id- get one saved result.POST /results- save an enrichment response.POST /results/:id/enrich- fresh enrichment for a saved result.DELETE /results/:id- remove a saved result.
The web action icons use inline SVGs from Heroicons, which is MIT licensed.